mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Sync to trunk.
This commit is contained in:
commit
4dc7c57917
@ -9,7 +9,7 @@ Create linux binary.
|
||||
'''
|
||||
|
||||
def freeze():
|
||||
import glob, sys, tarfile, os, textwrap, shutil
|
||||
import glob, sys, tarfile, os, textwrap, shutil, platform
|
||||
from contextlib import closing
|
||||
from cx_Freeze import Executable, setup
|
||||
from calibre.constants import __version__, __appname__
|
||||
@ -19,6 +19,9 @@ def freeze():
|
||||
from calibre.ebooks.lrf.fonts import FONT_MAP
|
||||
import calibre
|
||||
|
||||
is64bit = platform.architecture()[0] == '64bit'
|
||||
arch = 'x86_64' if is64bit else 'i686'
|
||||
|
||||
|
||||
QTDIR = '/usr/lib/qt4'
|
||||
QTDLLS = ('QtCore', 'QtGui', 'QtNetwork', 'QtSvg', 'QtXml',
|
||||
@ -47,7 +50,8 @@ def freeze():
|
||||
'/usr/lib/libxslt.so.1',
|
||||
'/usr/lib/libxslt.so.1',
|
||||
'/usr/lib/libgthread-2.0.so.0',
|
||||
'/usr/lib/gcc/i686-pc-linux-gnu/4.3.3/libstdc++.so.6',
|
||||
'/usr/lib/gcc/***-pc-linux-gnu/4.4.1/libstdc++.so.6'.replace('***',
|
||||
arch).replace('i686', 'i486'),
|
||||
'/usr/lib/libpng12.so.0',
|
||||
'/usr/lib/libexslt.so.0',
|
||||
'/usr/lib/libMagickWand.so',
|
||||
@ -89,7 +93,7 @@ def freeze():
|
||||
|
||||
includes = [x[0] for x in executables.values()]
|
||||
includes += ['calibre.ebooks.lrf.fonts.prs500.'+x for x in FONT_MAP.values()]
|
||||
includes += ['email.iterators', 'email.generator']
|
||||
includes += ['email.iterators', 'email.generator', 'sqlite3.dump']
|
||||
|
||||
|
||||
excludes = ['matplotlib', "Tkconstants", "Tkinter", "tcl", "_imagingtk",
|
||||
@ -228,7 +232,8 @@ def freeze():
|
||||
open(os.path.join(FREEZE_DIR, 'manifest'), 'wb').write('\n'.join(exes))
|
||||
|
||||
print 'Creating archive...'
|
||||
dist = open(os.path.join(DIST_DIR, 'calibre-%s-i686.tar.bz2'%__version__), 'wb')
|
||||
dist = open(os.path.join(DIST_DIR, 'calibre-%s-%s.tar.bz2'%(__version__,
|
||||
arch)), 'wb')
|
||||
with closing(tarfile.open(fileobj=dist, mode='w:bz2',
|
||||
format=tarfile.PAX_FORMAT)) as tf:
|
||||
for f in walk(FREEZE_DIR):
|
||||
|
@ -353,7 +353,7 @@ def main():
|
||||
'keyword', 'codeop', 'pydoc', 'readline',
|
||||
'BeautifulSoup', 'calibre.ebooks.lrf.fonts.prs500.*',
|
||||
'dateutil', 'email.iterators',
|
||||
'email.generator',
|
||||
'email.generator', 'sqlite3.dump',
|
||||
'calibre.ebooks.metadata.amazon',
|
||||
],
|
||||
'packages' : ['PIL', 'Authorization', 'lxml', 'dns'],
|
||||
|
@ -36,7 +36,7 @@ def run_install_jammer(installer_name='<%AppName%>-<%Version%><%Ext%>', build_fo
|
||||
compression = 'zlib'
|
||||
if build_for_release:
|
||||
cmdline += ['--build-for-release']
|
||||
#compression = 'lzma (solid)'
|
||||
compression = 'lzma (solid)'
|
||||
cmdline += ['-DCompressionMethod', compression]
|
||||
cmdline += ['--build', mpi]
|
||||
#print 'Running installjammer with cmdline:'
|
||||
|
@ -232,12 +232,10 @@ test
|
||||
}
|
||||
FileGroup ::BEF8D398-58BA-1F66-39D6-D4A63D5BEEF9 -setup Install -active Yes -platforms {AIX-ppc FreeBSD-4-x86 FreeBSD-x86 HPUX-hppa Linux-x86 Solaris-sparc Windows TarArchive ZipArchive FreeBSD-5-x86 FreeBSD-6-x86 FreeBSD-7-x86 Linux-x86_64 Solaris-x86} -name {Program Files} -parent FileGroups
|
||||
File ::8E5D85A4-7608-47A1-CF7C-309060D5FF40 -filemethod {Always overwrite files} -type dir -directory <%InstallDir%> -name /home/kovid/work/calibre/build/py2exe -parent BEF8D398-58BA-1F66-39D6-D4A63D5BEEF9
|
||||
File ::FC870EE7-667B-481F-113B-B4504DFCCFA5 -type dir -name bin -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
|
||||
File ::377C588B-B324-CA09-ED49-4DB5F82A15ED -type dir -name etc -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
|
||||
File ::55DE4B9F-0881-FF51-E2BA-EC72B5D3425C -type dir -name fonts -parent 377C588B-B324-CA09-ED49-4DB5F82A15ED
|
||||
File ::A27B68D9-43A6-B994-3091-E829AFBA340D -type dir -name conf.d -parent 55DE4B9F-0881-FF51-E2BA-EC72B5D3425C
|
||||
File ::974ADD48-88E5-BC7A-1963-928A245F133A -type dir -name conf.avail -parent 55DE4B9F-0881-FF51-E2BA-EC72B5D3425C
|
||||
File ::5E5273D8-3423-8DC8-83C4-BE000069A803 -name fonts.dtd -parent 55DE4B9F-0881-FF51-E2BA-EC72B5D3425C
|
||||
File ::32D7DBE0-E0B1-5BDD-66C5-2A13D8BC8F90 -name fonts.conf -parent 55DE4B9F-0881-FF51-E2BA-EC72B5D3425C
|
||||
File ::B95D03D4-EA59-F00E-59E1-BA05758879DA -type dir -name imageformats -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
|
||||
File ::A624029D-AE0F-49A5-4DAC-7720CDCAB271 -name qmng4.dll -parent B95D03D4-EA59-F00E-59E1-BA05758879DA
|
||||
@ -337,7 +335,6 @@ File ::2F90282D-B59F-B6BA-090B-45858AF7F3B2 -name IM_MOD_RL_clipboard_.dll -pare
|
||||
File ::B512D139-B295-D7C3-F0B4-43775849CF58 -name numpy.core._sort.pyd -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
|
||||
File ::A2C063AC-2F12-9260-501A-0E8BD0B8A932 -name calibre.exe.local -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
|
||||
File ::439B6D05-7DE6-061D-4BCC-3F04F4FA2FA2 -name IM_MOD_RL_png_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
|
||||
File ::BA464D11-BBCE-DEDA-C354-0C7BE60FAA05 -name IM_MOD_RL_braille_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
|
||||
File ::8F9FF823-AF6D-A288-8AE6-7D74F55DCE29 -name CORE_RL_bzlib_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
|
||||
File ::57A3F5D5-BFC8-CB38-5A57-548EE0DB033B -name QtNetwork4.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
|
||||
File ::4DB7E8DE-905A-822A-AF14-17BD5ACEF915 -name IM_MOD_RL_wmf_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
|
||||
@ -361,7 +358,6 @@ File ::A6419A84-6C22-784E-6D84-D09972770770 -name unicodedata.pyd -parent 8E5D85
|
||||
File ::E658FBE0-5860-D041-12D3-76ADD18F804B -name servicemanager.pyd -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
|
||||
File ::C98A6FC4-E341-7FD4-005C-DA2B384E11D8 -name win32api.pyd -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
|
||||
File ::ADA36EEA-7DE1-447C-B1AB-A4908E65E2CD -name IM_MOD_RL_ipl_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
|
||||
File ::53C2EC15-850F-8F49-6425-C228FB6E6D0E -name libfontconfig-1.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
|
||||
File ::EDE6F457-C83F-C5FA-9AF4-38FDFF17D929 -name PIL._imagingtk.pyd -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
|
||||
File ::09D0906E-3611-3DB7-32CF-A140585694A7 -name win32pdh.pyd -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
|
||||
File ::4C84F0DC-7157-0C90-2062-180139B03E25 -name IM_MOD_RL_rgb_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
|
||||
@ -384,17 +380,14 @@ File ::404A98F1-84FD-B6D0-B130-354EECD9253C -name IM_MOD_RL_emf_.dll -parent 8E5
|
||||
File ::17034C34-403E-B405-99C1-F80B7F00E27C -name log.xml -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
|
||||
File ::34E63A2C-65C5-0A84-ACF1-BD6A844D4579 -name pythoncom26.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
|
||||
File ::2F20484B-53B8-B08E-B691-C5B2D49A9CB4 -name QtWebKit4.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
|
||||
File ::8AF134C8-9189-3F9A-A081-9143FFD44C45 -name freetype6.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
|
||||
File ::E8A4442D-D0D3-31CD-997A-3CEB641CF5B7 -name IM_MOD_RL_mtv_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
|
||||
File ::0CA87D0B-5A04-1439-AEE8-C97072D47BA7 -name CORE_RL_tiff_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
|
||||
File ::AC24F520-88D4-D1CF-5797-27C715CE8ACA -name pyexpat.pyd -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
|
||||
File ::16848F38-71CD-55B8-4D96-1537F6773744 -name IM_MOD_RL_dps_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
|
||||
File ::33A46CC5-BAC4-5863-C83D-303DCCA0CAA1 -name tk85.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
|
||||
File ::81116DD3-1715-AA87-472F-544FC616EDAF -name IM_MOD_RL_dcm_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
|
||||
File ::335A4CFB-5C2D-44E4-C438-7018E8244C3D -name ebook-viewer.exe -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
|
||||
File ::62A16C3B-ED9C-5187-2807-58857DF3A990 -name calibre-debug.exe -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
|
||||
File ::55ECA7B7-279A-F51D-81C2-C8DC44CF0E22 -name select.pyd -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
|
||||
File ::A6AF5ECC-A981-4CBD-DBEE-303A9340C603 -name IM_MOD_RL_xps_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
|
||||
File ::5BCBF71F-18E7-5C52-E3F5-7D7F3028AD46 -name locale.xml -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
|
||||
File ::5C9FA94C-B8B0-A94B-548D-1D24FDEA5770 -name CORE_RL_wand_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
|
||||
File ::E39E60BE-DE77-AB8C-42C6-5A7D7DC073E3 -name IM_MOD_RL_ttf_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
|
||||
@ -443,12 +436,10 @@ File ::396B4F78-FB45-C0B2-ACB3-97769CF5CD5D -name msvcr90.dll -parent 8E5D85A4-7
|
||||
File ::1DE767EE-4891-4E54-422D-67A4DFF8C3B5 -name lrfviewer.exe -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
|
||||
File ::AFFEC28A-615C-E3E6-0026-CCE2594A6D25 -name calibre-server.exe.local -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
|
||||
File ::2C64F86B-9366-B52D-F7B2-5BBD51F6982A -name IM_MOD_RL_pwp_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
|
||||
File ::F4B2EF9C-EB18-B865-6E99-75CFB9B60D87 -name IM_MOD_RL_dds_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
|
||||
File ::38770D87-6CA9-9E3E-FBA1-A8CCFCD88FB5 -name IM_MOD_RL_fpx_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
|
||||
File ::BE2D7BC3-D294-AF3F-65E7-3B372DEFDE36 -name PIL._imaging.pyd -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
|
||||
File ::200B9AEC-809F-75B7-DC12-A51BFC2A6F93 -name PyQt4.QtSvg.pyd -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
|
||||
File ::52132A31-D3AE-C617-7568-BF2AF46B5D74 -name IM_MOD_RL_pcl_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
|
||||
File ::F94472C3-C8D0-950F-5ED9-1611D1CE30E5 -name IM_MOD_RL_inline_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
|
||||
File ::055ADB4B-20C5-E071-442F-4DA0A8D6F3C5 -name english.xml -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
|
||||
File ::B10B6B91-0C03-642D-90D8-37B607B164AD -name IM_MOD_RL_wpg_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
|
||||
File ::BFBB144B-1794-8304-9772-F103A42F2CA4 -name IM_MOD_RL_pdb_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
|
||||
@ -503,7 +494,6 @@ File ::9BA85EE5-1754-67AF-736D-481CDCC72DD2 -name _imagingft.pyd -parent 8E5D85A
|
||||
File ::6254DD0C-8F2C-D4AE-2107-2597D542C181 -name IM_MOD_RL_matte_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
|
||||
File ::F159D566-88D6-C347-3E3C-55C2DDFC5FD0 -name IM_MOD_RL_mono_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
|
||||
File ::B873CAA2-011F-94C3-7977-FF344E53C44F -name CORE_RL_jbig_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
|
||||
File ::7004FCB8-C6F4-C7AF-08E4-B6151B2F7050 -name tcl85.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
|
||||
File ::6921F62A-4015-4C9F-98A6-BCBBC43B698E -name msvcm90.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
|
||||
File ::7276E0CA-C205-4B18-19A3-157F1B8523FB -name IM_MOD_RL_xtrn_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
|
||||
File ::7B9624A9-88B4-C61E-6771-9A34FB6CA3B5 -name PyQt4.QtGui.pyd -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
|
||||
@ -567,6 +557,15 @@ File ::9E4E5E8F-30C0-E631-9516-2AE01A5CA0E9 -name ebook-device.exe.local -parent
|
||||
File ::7BE6B538-70D5-A7EB-5F91-E14CE57B394B -name calibre-complete.exe.local -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
|
||||
File ::C4E40030-3EE0-8B05-E6B9-89E81433EE1F -name phonon4.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
|
||||
File ::9E84342F-36ED-7ED3-8F90-1EC55267BCFC -name poppler-qt4.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
|
||||
File ::86BA442C-90C9-A4E6-1D3E-D144E5F326C1 -name msvcp71.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
|
||||
File ::11FBAD0B-A2DB-C28A-85B8-D6A22706864F -name mfc71.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
|
||||
File ::4B9FB3E6-B807-65CC-826D-A398E964D00C -name IM_MOD_RL_hdf_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
|
||||
File ::3E201C0C-C7CC-5785-74F6-A6CC7F50A15A -name msvcr71.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
|
||||
File ::2EE42149-1C12-CCA9-9089-AE1809098D0A -name jpeg62.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
|
||||
File ::B1FD37B4-E91B-DC1C-1C69-FB2E10EB93AE -name libtiff3.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
|
||||
File ::15E09D95-97D6-92A9-CC4D-120885E4DDAD -name freetype.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
|
||||
File ::D954BC75-8166-EC1B-D91B-C9779248AA14 -name fontconfig.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
|
||||
File ::1F3C052A-A5E0-5C65-8D42-EBF44FBE138D -name podofo.dll.manifest -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
|
||||
Component ::F6829AB7-9F66-4CEE-CA0E-21F54C6D3609 -setup Install -active Yes -platforms {AIX-ppc FreeBSD-4-x86 FreeBSD-x86 HPUX-hppa Linux-x86 Solaris-sparc Windows FreeBSD-5-x86 FreeBSD-6-x86 FreeBSD-7-x86 Linux-x86_64 Solaris-x86} -name Main -parent Components
|
||||
SetupType ::D9ADE41C-B744-690C-2CED-CF826BF03D2E -setup Install -active Yes -platforms {AIX-ppc FreeBSD-4-x86 FreeBSD-x86 HPUX-hppa Linux-x86 Solaris-sparc Windows FreeBSD-5-x86 FreeBSD-6-x86 FreeBSD-7-x86 Linux-x86_64 Solaris-x86} -name Typical -parent SetupTypes
|
||||
|
||||
|
@ -7,15 +7,12 @@ __docformat__ = 'restructuredtext en'
|
||||
Freeze app into executable using py2exe.
|
||||
'''
|
||||
QT_DIR = 'C:\\Qt\\4.5.2'
|
||||
LIBUSB_DIR = 'C:\\libusb'
|
||||
LIBUSB_DIR = r'C:\cygwin\home\kovid\win32\libusb'
|
||||
LIBUNRAR = 'C:\\Program Files\\UnrarDLL\\unrar.dll'
|
||||
PDFTOHTML = 'C:\\cygwin\\home\\kovid\\poppler-0.10.6\\rel\\pdftohtml.exe'
|
||||
POPPLER = 'C:\\cygwin\\home\\kovid\\poppler'
|
||||
IMAGEMAGICK_DIR = 'C:\\ImageMagick'
|
||||
PDFTK = 'C:\\pdftk.exe'
|
||||
PODOFO = 'C:\\podofo'
|
||||
FONTCONFIG_DIR = 'C:\\fontconfig'
|
||||
VC90 = r'C:\VC90.CRT'
|
||||
BINARIES = r'C:\cygwin\home\kovid\win32\bin'
|
||||
IMAGEMAGICK_DIR = r'C:\cygwin\home\kovid\win32\imagemagick'
|
||||
FONTCONFIG_DIR = r'C:\cygwin\home\kovid\win32\etc'
|
||||
VC90 = r'C:\Program Files\Microsoft Visual Studio 9.0\VC\redist\x86\Microsoft.VC90.CRT'
|
||||
|
||||
# ModuleFinder can't handle runtime changes to __path__, but win32com uses them
|
||||
import sys
|
||||
@ -98,25 +95,17 @@ class BuildEXE(py2exe.build_exe.py2exe):
|
||||
shutil.copyfile(f, os.path.join(tdir, os.path.basename(f)))
|
||||
print '\tAdding unrar'
|
||||
shutil.copyfile(LIBUNRAR, os.path.join(PY2EXE_DIR, os.path.basename(LIBUNRAR)))
|
||||
print '\tAdding poppler'
|
||||
for x in ('bin\\pdftohtml.exe', 'bin\\poppler-qt4.dll',
|
||||
'bin\\freetype.dll', 'bin\\jpeg62.dll'):
|
||||
shutil.copyfile(os.path.join(POPPLER, x),
|
||||
os.path.join(PY2EXE_DIR, os.path.basename(x)))
|
||||
print '\tAdding podofo'
|
||||
for f in glob.glob(os.path.join(PODOFO, '*.dll')):
|
||||
shutil.copyfile(f, os.path.join(PY2EXE_DIR, os.path.basename(f)))
|
||||
|
||||
print '\tAdding Binaries'
|
||||
for x in glob.glob(os.path.join(BINARIES, '*.dll')) + \
|
||||
[os.path.join(BINARIES, 'pdftohtml.exe')] + \
|
||||
glob.glob(os.path.join(BINARIES, '*.manifest')):
|
||||
shutil.copyfile(x, os.path.join(PY2EXE_DIR, os.path.basename(x)))
|
||||
print '\tAdding ImageMagick'
|
||||
for f in os.listdir(IMAGEMAGICK_DIR):
|
||||
shutil.copyfile(os.path.join(IMAGEMAGICK_DIR, f), os.path.join(PY2EXE_DIR, f))
|
||||
print '\tCopying fontconfig'
|
||||
for f in glob.glob(os.path.join(FONTCONFIG_DIR, '*')):
|
||||
tgt = os.path.join(PY2EXE_DIR, os.path.basename(f))
|
||||
if os.path.isdir(f):
|
||||
shutil.copytree(f, tgt)
|
||||
else:
|
||||
shutil.copyfile(f, tgt)
|
||||
tgt = os.path.join(PY2EXE_DIR, 'etc')
|
||||
shutil.copytree(FONTCONFIG_DIR, tgt)
|
||||
|
||||
print
|
||||
print 'Doing DLL redirection' # See http://msdn.microsoft.com/en-us/library/ms682600(VS.85).aspx
|
||||
@ -169,8 +158,7 @@ def main(args=sys.argv):
|
||||
'email.iterators',
|
||||
'email.generator',
|
||||
'win32process', 'win32api', 'msvcrt',
|
||||
'win32event', 'calibre.ebooks.lrf.any.*',
|
||||
'calibre.ebooks.lrf.feeds.*',
|
||||
'win32event', 'sqlite3.dump',
|
||||
'BeautifulSoup', 'pyreadline',
|
||||
'pydoc', 'IPython.Extensions.*',
|
||||
'calibre.web.feeds.recipes.*',
|
||||
@ -183,7 +171,8 @@ def main(args=sys.argv):
|
||||
'excludes' : ["Tkconstants", "Tkinter", "tcl",
|
||||
"_imagingtk", "ImageTk", "FixTk"
|
||||
],
|
||||
'dll_excludes' : ['mswsock.dll'],
|
||||
'dll_excludes' : ['mswsock.dll', 'tcl85.dll',
|
||||
'MSVCP90.dll', 'tk85.dll'],
|
||||
},
|
||||
},
|
||||
|
||||
|
21
setup.py
21
setup.py
@ -71,7 +71,7 @@ if __name__ == '__main__':
|
||||
tag_release, upload_demo, build_linux, build_windows, \
|
||||
build_osx, upload_installers, upload_user_manual, \
|
||||
upload_to_pypi, stage3, stage2, stage1, upload, \
|
||||
upload_rss, betas
|
||||
upload_rss, betas, build_linux32, build_linux64
|
||||
|
||||
entry_points['console_scripts'].append(
|
||||
'calibre_postinstall = calibre.linux:post_install')
|
||||
@ -94,8 +94,8 @@ if __name__ == '__main__':
|
||||
sources=['src/calibre/utils/windows/winutil.c'],
|
||||
libraries=['shell32', 'setupapi'],
|
||||
include_dirs=os.environ.get('INCLUDE',
|
||||
'C:/WinDDK/6001.18001/inc/api/;'
|
||||
'C:/WinDDK/6001.18001/inc/crt/').split(';'),
|
||||
'C:/WinDDK/7600.16385.0/inc/api/;'
|
||||
'C:/WinDDK/7600.16385.0/inc/crt/').split(';'),
|
||||
extra_compile_args=['/X']
|
||||
))
|
||||
|
||||
@ -103,8 +103,8 @@ if __name__ == '__main__':
|
||||
poppler_lib = '/usr/lib'
|
||||
poppler_libs = []
|
||||
if iswindows:
|
||||
poppler_inc = r'C:\cygwin\home\kovid\poppler\include\poppler\qt4'
|
||||
poppler_lib = r'C:\cygwin\home\kovid\poppler\lib'
|
||||
poppler_inc = r'C:\cygwin\home\kovid\win32\include\poppler\qt4'
|
||||
poppler_lib = r'C:\cygwin\home\kovid\win32\lib'
|
||||
poppler_libs = ['QtCore4', 'QtGui4']
|
||||
if isosx:
|
||||
poppler_inc = '/Volumes/sw/build/poppler-0.10.7/qt4/src'
|
||||
@ -124,9 +124,10 @@ if __name__ == '__main__':
|
||||
print 'POPPLER_LIB_DIR environment variables.'
|
||||
|
||||
podofo_inc = '/usr/include/podofo' if islinux else \
|
||||
'C:\\podofo\\include\\podofo' if iswindows else \
|
||||
r'C:\cygwin\home\kovid\win32\include\podofo' if iswindows else \
|
||||
'/usr/local/include/podofo'
|
||||
podofo_lib = '/usr/lib' if islinux else r'C:\podofo' if iswindows else \
|
||||
podofo_lib = '/usr/lib' if islinux else \
|
||||
r'C:\cygwin\home\kovid\win32\lib' if iswindows else \
|
||||
'/usr/local/lib'
|
||||
podofo_inc = os.environ.get('PODOFO_INC_DIR', podofo_inc)
|
||||
if os.path.exists(os.path.join(podofo_inc, 'podofo.h')):
|
||||
@ -141,10 +142,10 @@ if __name__ == '__main__':
|
||||
print 'PODOFO_LIB_DIR environment variables.'
|
||||
|
||||
fc_inc = '/usr/include/fontconfig' if islinux else \
|
||||
r'C:\cygwin\home\kovid\fontconfig\include\fontconfig' if iswindows else \
|
||||
r'C:\cygwin\home\kovid\win32\include\fontconfig' if iswindows else \
|
||||
'/Users/kovid/fontconfig/include/fontconfig'
|
||||
fc_lib = '/usr/lib' if islinux else \
|
||||
r'C:\cygwin\home\kovid\fontconfig\lib' if iswindows else \
|
||||
r'C:\cygwin\home\kovid\win32\lib' if iswindows else \
|
||||
'/Users/kovid/fontconfig/lib'
|
||||
|
||||
|
||||
@ -258,6 +259,8 @@ if __name__ == '__main__':
|
||||
'tag_release' : tag_release,
|
||||
'upload_demo' : upload_demo,
|
||||
'build_linux' : build_linux,
|
||||
'build_linux32' : build_linux32,
|
||||
'build_linux64' : build_linux64,
|
||||
'build_windows' : build_windows,
|
||||
'build_osx' : build_osx,
|
||||
'upload_installers': upload_installers,
|
||||
|
@ -69,7 +69,7 @@ def osx_version():
|
||||
return int(m.group(1)), int(m.group(2)), int(m.group(3))
|
||||
|
||||
|
||||
_filename_sanitize = re.compile(r'[\xae\0\\|\?\*<":>\+\[\]/]')
|
||||
_filename_sanitize = re.compile(r'[\xae\0\\|\?\*<":>\+/]')
|
||||
|
||||
def sanitize_file_name(name, substitute='_', as_unicode=False):
|
||||
'''
|
||||
|
@ -135,7 +135,8 @@ def debug_device_driver():
|
||||
print 'failed'
|
||||
continue
|
||||
success = True
|
||||
print 'Main memory:', repr(dev._main_prefix)
|
||||
if hasattr(dev, '_main_prefix'):
|
||||
print 'Main memory:', repr(dev._main_prefix)
|
||||
print 'Total space:', dev.total_space()
|
||||
break
|
||||
if not success and errors:
|
||||
@ -144,6 +145,10 @@ def debug_device_driver():
|
||||
print dev
|
||||
print msg
|
||||
print
|
||||
if isosx and os.path.exists('/tmp/ioreg.txt'):
|
||||
print
|
||||
print
|
||||
print "Don't forget to send the file /tmp/ioreg.txt as well"
|
||||
|
||||
|
||||
def add_simple_plugin(path_to_plugin):
|
||||
|
@ -46,7 +46,8 @@ class Plumber(object):
|
||||
'tags', 'book_producer', 'language'
|
||||
]
|
||||
|
||||
def __init__(self, input, output, log, report_progress=DummyReporter(), dummy=False):
|
||||
def __init__(self, input, output, log, report_progress=DummyReporter(),
|
||||
dummy=False, merge_plugin_recs=True):
|
||||
'''
|
||||
:param input: Path to input file.
|
||||
:param output: Path to output file/directory
|
||||
@ -483,7 +484,8 @@ OptionRecommendation(name='language',
|
||||
for x in getattr(self, w):
|
||||
temp.add(x.clone())
|
||||
setattr(self, w, temp)
|
||||
self.merge_plugin_recommendations()
|
||||
if merge_plugin_recs:
|
||||
self.merge_plugin_recommendations()
|
||||
|
||||
@classmethod
|
||||
def unarchive(self, path, tdir):
|
||||
|
@ -78,7 +78,7 @@ class EPUBOutput(OutputFormatPlugin):
|
||||
),
|
||||
|
||||
OptionRecommendation(name='no_default_epub_cover', recommended_value=False,
|
||||
help=_('Normally, if the input file ahs no cover and you don\'t'
|
||||
help=_('Normally, if the input file has no cover and you don\'t'
|
||||
' specify one, a default cover is generated with the title, '
|
||||
'authors, etc. This option disables the generation of this cover.')),
|
||||
|
||||
|
@ -18,10 +18,10 @@ class FB2Output(OutputFormatPlugin):
|
||||
options = set([
|
||||
OptionRecommendation(name='inline_toc',
|
||||
recommended_value=False, level=OptionRecommendation.LOW,
|
||||
help=_('Add Table of Contents to begenning of the book.')),
|
||||
help=_('Add Table of Contents to beginning of the book.')),
|
||||
])
|
||||
|
||||
def convert(self, oeb_book, output_path, input_plugin, opts, log):
|
||||
def convert(self, oeb_book, output_path, input_plugin, opts, log):
|
||||
fb2mlizer = FB2MLizer(log)
|
||||
fb2_content = fb2mlizer.extract_content(oeb_book, opts)
|
||||
|
||||
@ -33,11 +33,11 @@ class FB2Output(OutputFormatPlugin):
|
||||
out_stream = open(output_path, 'wb')
|
||||
else:
|
||||
out_stream = output_path
|
||||
|
||||
|
||||
out_stream.seek(0)
|
||||
out_stream.truncate()
|
||||
out_stream.write(fb2_content.encode('utf-8', 'replace'))
|
||||
|
||||
|
||||
if close:
|
||||
out_stream.close()
|
||||
|
||||
|
@ -6,6 +6,7 @@ __docformat__ = 'restructuredtext en'
|
||||
import sys, textwrap
|
||||
from urllib import urlencode
|
||||
from functools import partial
|
||||
from datetime import datetime
|
||||
|
||||
from lxml import etree
|
||||
from dateutil import parser
|
||||
@ -151,7 +152,9 @@ class ResultList(list):
|
||||
try:
|
||||
d = date(entry)
|
||||
if d:
|
||||
d = parser.parse(d[0].text)
|
||||
default = datetime.utcnow()
|
||||
default = datetime(default.year, default.month, 1)
|
||||
d = parser.parse(d[0].text, default=default)
|
||||
else:
|
||||
d = None
|
||||
except:
|
||||
|
@ -92,7 +92,10 @@ class CSSSelector(etree.XPath):
|
||||
|
||||
def __init__(self, css, namespaces=XPNSMAP):
|
||||
css = self.MIN_SPACE_RE.sub(r'\1', css)
|
||||
path = css_to_xpath(css)
|
||||
try:
|
||||
path = css_to_xpath(css)
|
||||
except UnicodeEncodeError: # Bug in css_to_xpath
|
||||
path = '/'
|
||||
path = self.LOCAL_NAME_RE.sub(r"local-name() = '", path)
|
||||
etree.XPath.__init__(self, path, namespaces=namespaces)
|
||||
self.css = css
|
||||
|
@ -29,7 +29,7 @@ class PDBOutput(OutputFormatPlugin):
|
||||
'formats.')),
|
||||
OptionRecommendation(name='inline_toc',
|
||||
recommended_value=False, level=OptionRecommendation.LOW,
|
||||
help=_('Add Table of Contents to begenning of the book.')),
|
||||
help=_('Add Table of Contents to beginning of the book.')),
|
||||
])
|
||||
|
||||
def convert(self, oeb_book, output_path, input_plugin, opts, log):
|
||||
|
@ -34,7 +34,7 @@ class PMLOutput(OutputFormatPlugin):
|
||||
'The default is cp1252.')),
|
||||
OptionRecommendation(name='inline_toc',
|
||||
recommended_value=False, level=OptionRecommendation.LOW,
|
||||
help=_('Add Table of Contents to begenning of the book.')),
|
||||
help=_('Add Table of Contents to beginning of the book.')),
|
||||
])
|
||||
|
||||
def convert(self, oeb_book, output_path, input_plugin, opts, log):
|
||||
|
@ -18,7 +18,7 @@ class RBOutput(OutputFormatPlugin):
|
||||
options = set([
|
||||
OptionRecommendation(name='inline_toc',
|
||||
recommended_value=False, level=OptionRecommendation.LOW,
|
||||
help=_('Add Table of Contents to begenning of the book.')),
|
||||
help=_('Add Table of Contents to beginning of the book.')),
|
||||
])
|
||||
|
||||
def convert(self, oeb_book, output_path, input_plugin, opts, log):
|
||||
|
@ -32,13 +32,13 @@ class TXTOutput(OutputFormatPlugin):
|
||||
'formats.')),
|
||||
OptionRecommendation(name='inline_toc',
|
||||
recommended_value=False, level=OptionRecommendation.LOW,
|
||||
help=_('Add Table of Contents to begenning of the book.')),
|
||||
help=_('Add Table of Contents to beginning of the book.')),
|
||||
])
|
||||
|
||||
def convert(self, oeb_book, output_path, input_plugin, opts, log):
|
||||
writer = TXTMLizer(log)
|
||||
txt = writer.extract_content(oeb_book, opts)
|
||||
|
||||
|
||||
log.debug('\tReplacing newlines with selected type...')
|
||||
txt = specified_newlines(TxtNewlines(opts.newline).newline, txt)
|
||||
|
||||
|
@ -32,7 +32,7 @@ BLOCK_STYLES = [
|
||||
]
|
||||
|
||||
class TXTMLizer(object):
|
||||
|
||||
|
||||
def __init__(self, log):
|
||||
self.log = log
|
||||
|
||||
@ -91,7 +91,7 @@ class TXTMLizer(object):
|
||||
|
||||
# Remove excessive newlines.
|
||||
text = re.sub('\n[ ]+\n', '\n\n', text)
|
||||
text = re.sub('\n{5,}', '\n\n\n\n', text)
|
||||
text = re.sub('\n{3,}', '\n\n', text)
|
||||
|
||||
# Replace spaces at the beginning and end of lines
|
||||
text = re.sub('(?imu)^[ ]+', '', text)
|
||||
|
@ -450,12 +450,13 @@ class FileDialog(QObject):
|
||||
if os.path.exists(f):
|
||||
self.selected_files.append(f)
|
||||
if self.selected_files:
|
||||
self.selected_files = [qstring_to_unicode(q) for q in self.selected_files]
|
||||
dynamic[self.dialog_name] = os.path.dirname(self.selected_files[0])
|
||||
self.selected_files = [unicode(q) for q in self.selected_files]
|
||||
saved_loc = self.selected_files[0]
|
||||
if os.path.isfile(saved_loc):
|
||||
saved_loc = os.path.dirname(saved_loc)
|
||||
dynamic[self.dialog_name] = saved_loc
|
||||
self.accepted = bool(self.selected_files)
|
||||
|
||||
|
||||
|
||||
def get_files(self):
|
||||
if islinux and self.fd.result() != self.fd.Accepted:
|
||||
return tuple()
|
||||
|
@ -5,7 +5,8 @@ import os, shutil, time
|
||||
from Queue import Queue, Empty
|
||||
from threading import Thread
|
||||
|
||||
from PyQt4.Qt import QThread, SIGNAL, QObject, QTimer, Qt
|
||||
from PyQt4.Qt import QThread, SIGNAL, QObject, QTimer, Qt, \
|
||||
QProgressDialog
|
||||
|
||||
from calibre.gui2.dialogs.progress import ProgressDialog
|
||||
from calibre.gui2 import question_dialog, error_dialog
|
||||
@ -13,6 +14,25 @@ from calibre.ebooks.metadata.opf2 import OPF
|
||||
from calibre.ebooks.metadata import MetaInformation
|
||||
from calibre.constants import preferred_encoding
|
||||
|
||||
class DuplicatesAdder(QThread):
|
||||
|
||||
def __init__(self, parent, db, duplicates, db_adder):
|
||||
QThread.__init__(self, parent)
|
||||
self.db, self.db_adder = db, db_adder
|
||||
self.duplicates = duplicates
|
||||
|
||||
def run(self):
|
||||
count = 1
|
||||
for mi, cover, formats in self.duplicates:
|
||||
id = self.db.create_book_entry(mi, cover=cover,
|
||||
add_duplicates=True)
|
||||
self.db_adder.add_formats(id, formats)
|
||||
self.db_adder.number_of_books_added += 1
|
||||
self.emit(SIGNAL('added(PyQt_PyObject)'), count)
|
||||
count += 1
|
||||
self.emit(SIGNAL('adding_done()'))
|
||||
|
||||
|
||||
class RecursiveFind(QThread):
|
||||
|
||||
def __init__(self, parent, db, root, single):
|
||||
@ -196,15 +216,19 @@ class Adder(QObject):
|
||||
self.callback(self.paths, self.names, self.infos)
|
||||
self.callback_called = True
|
||||
|
||||
def duplicates_processed(self):
|
||||
self.db_adder.end = True
|
||||
if not self.callback_called:
|
||||
self.callback(self.paths, self.names, self.infos)
|
||||
self.callback_called = True
|
||||
if hasattr(self, '__p_d'):
|
||||
self.__p_d.hide()
|
||||
|
||||
def update(self):
|
||||
if self.entry_count <= 0:
|
||||
self.timer.stop()
|
||||
self.process_duplicates()
|
||||
self.pd.hide()
|
||||
self.db_adder.end = True
|
||||
if not self.callback_called:
|
||||
self.callback(self.paths, self.names, self.infos)
|
||||
self.callback_called = True
|
||||
self.process_duplicates()
|
||||
return
|
||||
|
||||
try:
|
||||
@ -240,18 +264,28 @@ class Adder(QObject):
|
||||
def process_duplicates(self):
|
||||
duplicates = self.db_adder.duplicates
|
||||
if not duplicates:
|
||||
return
|
||||
return self.duplicates_processed()
|
||||
self.pd.hide()
|
||||
files = [x[0].title for x in duplicates]
|
||||
if question_dialog(self._parent, _('Duplicates found!'),
|
||||
_('Books with the same title as the following already '
|
||||
'exist in the database. Add them anyway?'),
|
||||
'\n'.join(files)):
|
||||
for mi, cover, formats in duplicates:
|
||||
id = self.db.create_book_entry(mi, cover=cover,
|
||||
add_duplicates=True)
|
||||
self.db_adder.add_formats(id, formats)
|
||||
self.db_adder.number_of_books_added += 1
|
||||
pd = QProgressDialog(_('Adding duplicates...'), '', 0, len(duplicates),
|
||||
self._parent)
|
||||
pd.setCancelButton(None)
|
||||
pd.setValue(0)
|
||||
pd.show()
|
||||
self.__p_d = pd
|
||||
self.__d_a = DuplicatesAdder(self._parent, self.db, duplicates,
|
||||
self.db_adder)
|
||||
self.connect(self.__d_a, SIGNAL('added(PyQt_PyObject)'),
|
||||
pd.setValue)
|
||||
self.connect(self.__d_a, SIGNAL('adding_done()'),
|
||||
self.duplicates_processed)
|
||||
self.__d_a.start()
|
||||
else:
|
||||
return self.duplicates_processed()
|
||||
|
||||
def cleanup(self):
|
||||
if hasattr(self, 'pd'):
|
||||
|
@ -34,7 +34,7 @@ class Widget(QWidget):
|
||||
def initialize_options(self, get_option, get_help, db=None, book_id=None):
|
||||
'''
|
||||
:param get_option: A callable that takes one argument: the option name
|
||||
and returns the correspoing OptionRecommendation.
|
||||
and returns the corresponding OptionRecommendation.
|
||||
:param get_help: A callable that takes the option name and return a help
|
||||
string.
|
||||
'''
|
||||
|
@ -28,11 +28,12 @@ class StructureDetectionWidget(Widget, Ui_Form):
|
||||
'remove_footer', 'footer_regex']
|
||||
)
|
||||
self.db, self.book_id = db, book_id
|
||||
for x in ('pagebreak', 'rule', 'both', 'none'):
|
||||
self.opt_chapter_mark.addItem(x)
|
||||
self.initialize_options(get_option, get_help, db, book_id)
|
||||
self.opt_chapter.set_msg(_('Detect chapters at (XPath expression):'))
|
||||
self.opt_page_breaks_before.set_msg(_('Insert page breaks before '
|
||||
'(XPath expression):'))
|
||||
|
||||
def pre_commit_check(self):
|
||||
for x in ('header_regex', 'footer_regex'):
|
||||
x = getattr(self, 'opt_'+x)
|
||||
|
@ -29,26 +29,6 @@
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QComboBox" name="opt_chapter_mark">
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>pagebreak</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>rule</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>both</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>none</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
|
@ -1,66 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
|
||||
from __future__ import with_statement
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
import textwrap
|
||||
|
||||
from PyQt4.Qt import QTabWidget
|
||||
|
||||
from calibre.gui2.dialogs.add_save_ui import Ui_TabWidget
|
||||
from calibre.library.save_to_disk import config, FORMAT_ARG_DESCS
|
||||
|
||||
|
||||
class AddSave(QTabWidget, Ui_TabWidget):
|
||||
|
||||
def __init__(self, parent=None):
|
||||
QTabWidget.__init__(self, parent)
|
||||
self.setupUi(self)
|
||||
c = config()
|
||||
opts = c.parse()
|
||||
for x in ('asciiize', 'update_metadata', 'save_cover', 'write_opf'):
|
||||
g = getattr(self, 'opt_'+x)
|
||||
g.setChecked(getattr(opts, x))
|
||||
help = '\n'.join(textwrap.wrap(c.get_option(x).help, 75))
|
||||
g.setToolTip(help)
|
||||
g.setWhatsThis(help)
|
||||
|
||||
for x in ('formats', 'timefmt'):
|
||||
g = getattr(self, 'opt_'+x)
|
||||
g.setText(getattr(opts, x))
|
||||
help = '\n'.join(textwrap.wrap(c.get_option(x).help, 75))
|
||||
g.setToolTip(help)
|
||||
g.setWhatsThis(help)
|
||||
|
||||
help = '\n'.join(textwrap.wrap(c.get_option('template').help, 75))
|
||||
self.opt_template.initialize('save_to_disk_template_history',
|
||||
opts.template, help=help)
|
||||
|
||||
variables = sorted(FORMAT_ARG_DESCS.keys())
|
||||
rows = []
|
||||
for var in variables:
|
||||
rows.append(u'<tr><td>%s</td><td>%s</td></tr>'%
|
||||
(var, FORMAT_ARG_DESCS[var]))
|
||||
table = u'<table>%s</table>'%(u'\n'.join(rows))
|
||||
self.template_variables.setText(table)
|
||||
|
||||
def save_settings(self):
|
||||
c = config()
|
||||
for x in ('asciiize', 'update_metadata', 'save_cover', 'write_opf'):
|
||||
c.set(x, getattr(self, 'opt_'+x).isChecked())
|
||||
for x in ('formats', 'template', 'timefmt'):
|
||||
c.set(x, unicode(getattr(self, 'opt_'+x).text()).strip())
|
||||
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
from PyQt4.Qt import QApplication
|
||||
app=QApplication([])
|
||||
a = AddSave()
|
||||
a.show()
|
||||
app.exec_()
|
||||
a.save_settings()
|
||||
|
@ -41,7 +41,8 @@ class ConfigTabs(QTabWidget):
|
||||
log = Log()
|
||||
log.outputs = []
|
||||
|
||||
self.plumber = Plumber('dummy.epub', 'dummy.epub', log, dummy=True)
|
||||
self.plumber = Plumber('dummy.epub', 'dummy.epub', log, dummy=True,
|
||||
merge_plugin_recs=False)
|
||||
|
||||
def widget_factory(cls):
|
||||
return cls(self, self.plumber.get_option_by_name,
|
||||
@ -298,7 +299,8 @@ class EmailAccounts(QAbstractTableModel):
|
||||
while y in self.accounts:
|
||||
c += 1
|
||||
y = x + str(c)
|
||||
self.accounts[y] = ['MOBI, EPUB', True,
|
||||
auto_send = len(self.accounts) < 1
|
||||
self.accounts[y] = ['MOBI, EPUB', auto_send,
|
||||
len(self.account_order) == 0]
|
||||
self.account_order = sorted(self.accounts.keys())
|
||||
self.reset()
|
||||
@ -756,7 +758,8 @@ class CheckIntegrity(QProgressDialog):
|
||||
|
||||
def __init__(self, db, parent=None):
|
||||
QProgressDialog.__init__(self, parent)
|
||||
self.setCancelButtonText('')
|
||||
self.db = db
|
||||
self.setCancelButton(None)
|
||||
self.setMinimum(0)
|
||||
self.setMaximum(100)
|
||||
self.setWindowTitle(_('Checking database integrity'))
|
||||
|
@ -26,7 +26,8 @@ class AddSave(QTabWidget, Ui_TabWidget):
|
||||
self.removeTab(2)
|
||||
c = config()
|
||||
opts = c.parse()
|
||||
for x in ('asciiize', 'update_metadata', 'save_cover', 'write_opf'):
|
||||
for x in ('asciiize', 'update_metadata', 'save_cover', 'write_opf',
|
||||
'replace_whitespace', 'to_lowercase'):
|
||||
g = getattr(self, 'opt_'+x)
|
||||
g.setChecked(getattr(opts, x))
|
||||
help = '\n'.join(textwrap.wrap(c.get_option(x).help, 75))
|
||||
@ -74,7 +75,8 @@ class AddSave(QTabWidget, Ui_TabWidget):
|
||||
if not self.validate():
|
||||
return False
|
||||
c = config()
|
||||
for x in ('asciiize', 'update_metadata', 'save_cover', 'write_opf'):
|
||||
for x in ('asciiize', 'update_metadata', 'save_cover', 'write_opf',
|
||||
'replace_whitespace', 'to_lowercase'):
|
||||
c.set(x, getattr(self, 'opt_'+x).isChecked())
|
||||
for x in ('formats', 'template', 'timefmt'):
|
||||
c.set(x, unicode(getattr(self, 'opt_'+x).text()).strip())
|
||||
|
@ -77,14 +77,14 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0" colspan="2">
|
||||
<item row="1" column="0">
|
||||
<widget class="QCheckBox" name="opt_save_cover">
|
||||
<property name="text">
|
||||
<string>Save &cover separately</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0" colspan="2">
|
||||
<item row="2" column="0">
|
||||
<widget class="QCheckBox" name="opt_update_metadata">
|
||||
<property name="text">
|
||||
<string>Update &metadata in saved copies</string>
|
||||
@ -163,6 +163,20 @@
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QCheckBox" name="opt_replace_whitespace">
|
||||
<property name="text">
|
||||
<string>Replace space with &underscores</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QCheckBox" name="opt_to_lowercase">
|
||||
<property name="text">
|
||||
<string>Change paths to &lowercase</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
|
BIN
src/calibre/gui2/images/news/axxon_news.png
Normal file
BIN
src/calibre/gui2/images/news/axxon_news.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 537 B |
BIN
src/calibre/gui2/images/news/darknet.png
Normal file
BIN
src/calibre/gui2/images/news/darknet.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.8 KiB |
BIN
src/calibre/gui2/images/news/livemint.png
Normal file
BIN
src/calibre/gui2/images/news/livemint.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 853 B |
@ -14,8 +14,9 @@ from PyQt4.Qt import Qt, SIGNAL, QObject, QCoreApplication, QUrl, QTimer, \
|
||||
QMessageBox, QStackedLayout
|
||||
from PyQt4.QtSvg import QSvgRenderer
|
||||
|
||||
from calibre import __version__, __appname__, \
|
||||
iswindows, isosx, prints, patheq
|
||||
from calibre import prints, patheq
|
||||
from calibre.constants import __version__, __appname__, \
|
||||
iswindows, isosx, filesystem_encoding
|
||||
from calibre.utils.filenames import ascii_filename
|
||||
from calibre.ptempfile import PersistentTemporaryFile
|
||||
from calibre.utils.config import prefs, dynamic
|
||||
@ -424,9 +425,18 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
|
||||
error_dialog(self, _('Bad database location'),
|
||||
_('Bad database location')+':'+self.library_path,
|
||||
det_msg=traceback.format_exc()).exec_()
|
||||
fname = _('Calibre Library')
|
||||
if isinstance(fname, unicode):
|
||||
try:
|
||||
fname = fname.encode(filesystem_encoding)
|
||||
except:
|
||||
fname = 'Calibre Library'
|
||||
x = os.path.expanduser('~'+os.sep+fname)
|
||||
if not os.path.exists(x):
|
||||
os.makedirs(x)
|
||||
dir = unicode(QFileDialog.getExistingDirectory(self,
|
||||
_('Choose a location for your ebook library.'),
|
||||
os.path.expanduser('~')))
|
||||
x))
|
||||
if not dir:
|
||||
QCoreApplication.exit(1)
|
||||
raise SystemExit(1)
|
||||
@ -1493,6 +1503,7 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
|
||||
self.search.clear_to_help()
|
||||
self.status_bar.reset_info()
|
||||
self.library_view.sortByColumn(3, Qt.DescendingOrder)
|
||||
self.library_view.model().count_changed()
|
||||
|
||||
############################################################################
|
||||
|
||||
@ -1565,14 +1576,23 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
|
||||
self.device_error_dialog.show()
|
||||
|
||||
def job_exception(self, job):
|
||||
if not hasattr(self, '_modeless_dialogs'):
|
||||
self._modeless_dialogs = []
|
||||
if self.isVisible():
|
||||
for x in list(self._modeless_dialogs):
|
||||
if not x.isVisible():
|
||||
self._modeless_dialogs.remove(x)
|
||||
try:
|
||||
if 'calibre.ebooks.DRMError' in job.details:
|
||||
error_dialog(self, _('Conversion Error'),
|
||||
d = error_dialog(self, _('Conversion Error'),
|
||||
_('<p>Could not convert: %s<p>It is a '
|
||||
'<a href="%s">DRM</a>ed book. You must first remove the '
|
||||
'DRM using 3rd party tools.')%\
|
||||
(job.description.split(':')[-1],
|
||||
'http://wiki.mobileread.com/wiki/DRM')).exec_()
|
||||
'http://wiki.mobileread.com/wiki/DRM'))
|
||||
d.setModal(False)
|
||||
d.show()
|
||||
self._modeless_dialogs.append(d)
|
||||
return
|
||||
except:
|
||||
pass
|
||||
@ -1582,9 +1602,12 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
|
||||
prints(job.details, file=sys.stderr)
|
||||
except:
|
||||
pass
|
||||
error_dialog(self, _('Conversion Error'),
|
||||
d = error_dialog(self, _('Conversion Error'),
|
||||
_('<b>Failed</b>')+': '+unicode(job.description),
|
||||
det_msg=job.details).exec_()
|
||||
det_msg=job.details)
|
||||
d.setModal(False)
|
||||
d.show()
|
||||
self._modeless_dialogs.append(d)
|
||||
|
||||
|
||||
def initialize_database(self):
|
||||
|
@ -93,7 +93,7 @@ class TagsModel(QStandardItemModel):
|
||||
QIcon(':/images/minus.svg')]
|
||||
QStandardItemModel.__init__(self)
|
||||
self.db = db
|
||||
self.ignore_next_search = False
|
||||
self.ignore_next_search = 0
|
||||
self._data = {}
|
||||
self.bold_font = QFont()
|
||||
self.bold_font.setBold(True)
|
||||
@ -129,19 +129,20 @@ class TagsModel(QStandardItemModel):
|
||||
self.refresh()
|
||||
|
||||
def reinit(self, *args, **kwargs):
|
||||
if not self.ignore_next_search:
|
||||
if self.ignore_next_search == 0:
|
||||
for category in self._data.values():
|
||||
for tag in category:
|
||||
tag.state = 0
|
||||
self.reset()
|
||||
self.ignore_next_search = False
|
||||
else:
|
||||
self.ignore_next_search -= 1
|
||||
|
||||
def toggle(self, index):
|
||||
if index.parent().isValid():
|
||||
category = self.row_map[index.parent().row()]
|
||||
tag = self._data[category][index.row()]
|
||||
self.invisibleRootItem().child(index.parent().row()).child(index.row()).toggle()
|
||||
self.ignore_next_search = True
|
||||
self.ignore_next_search = 2
|
||||
self.emit(SIGNAL('dataChanged(QModelIndex,QModelIndex)'), index, index)
|
||||
return True
|
||||
return False
|
||||
|
@ -17,6 +17,7 @@ from calibre import __appname__, patheq
|
||||
from calibre.library.database2 import LibraryDatabase2
|
||||
from calibre.library.move import MoveLibrary
|
||||
from calibre.resources import server_resources
|
||||
from calibre.constants import filesystem_encoding
|
||||
from calibre.gui2.wizard.send_email import smtp_prefs
|
||||
from calibre.gui2.wizard.device_ui import Ui_WizardPage as DeviceUI
|
||||
from calibre.gui2.wizard.library_ui import Ui_WizardPage as LibraryUI
|
||||
@ -423,7 +424,8 @@ def move_library(oldloc, newloc, parent, callback_on_complete):
|
||||
callback)
|
||||
else:
|
||||
rq = Queue()
|
||||
m = MoveLibrary(oldloc, newloc, db.data.count(), rq)
|
||||
m = MoveLibrary(oldloc, newloc,
|
||||
len(db.get_top_level_move_items()[0]), rq)
|
||||
global _mm
|
||||
_mm = MoveMonitor(m, rq, callback, parent)
|
||||
return
|
||||
@ -473,7 +475,18 @@ class LibraryPage(QWizardPage, LibraryUI):
|
||||
def initializePage(self):
|
||||
lp = prefs['library_path']
|
||||
if not lp:
|
||||
lp = os.path.expanduser('~')
|
||||
fname = _('Calibre Library')
|
||||
if isinstance(fname, unicode):
|
||||
try:
|
||||
fname = fname.encode(filesystem_encoding)
|
||||
except:
|
||||
fname = 'Calibre Library'
|
||||
lp = os.path.expanduser('~'+os.sep+fname)
|
||||
if not os.path.exists(lp):
|
||||
try:
|
||||
os.makedirs(lp)
|
||||
except:
|
||||
lp = os.path.expanduser('~')
|
||||
self.location.setText(lp)
|
||||
|
||||
def isComplete(self):
|
||||
|
@ -183,9 +183,7 @@ def do_list(db, fields, sort_by, ascending, search_text, line_width, separator,
|
||||
return template.generate(id="urn:calibre:main", data=data, subtitle=subtitle,
|
||||
sep=os.sep, quote=quote, updated=db.last_modified()).render('xml')
|
||||
|
||||
|
||||
|
||||
def command_list(args, dbpath):
|
||||
def list_option_parser():
|
||||
parser = get_parser(_(
|
||||
'''\
|
||||
%prog list [options]
|
||||
@ -208,6 +206,11 @@ List the books available in the calibre database.
|
||||
of = ['text', 'xml', 'stanza']
|
||||
parser.add_option('--output-format', choices=of, default='text',
|
||||
help=_('The format in which to output the data. Available choices: %s. Defaults is text.')%of)
|
||||
return parser
|
||||
|
||||
|
||||
def command_list(args, dbpath):
|
||||
parser = list_option_parser()
|
||||
opts, args = parser.parse_args(sys.argv[:1] + args)
|
||||
fields = [str(f.strip().lower()) for f in opts.fields.split(',')]
|
||||
if 'all' in fields:
|
||||
@ -316,9 +319,7 @@ def do_add(db, paths, one_book_per_directory, recurse, add_duplicates):
|
||||
finally:
|
||||
sys.stdout = orig
|
||||
|
||||
|
||||
|
||||
def command_add(args, dbpath):
|
||||
def add_option_parser():
|
||||
parser = get_parser(_(
|
||||
'''\
|
||||
%prog add [options] file1 file2 file3 ...
|
||||
@ -333,6 +334,11 @@ the directory related options below.
|
||||
help=_('Process directories recursively'))
|
||||
parser.add_option('-d', '--duplicates', action='store_true', default=False,
|
||||
help=_('Add books to database even if they already exist. Comparison is done based on book titles.'))
|
||||
return parser
|
||||
|
||||
|
||||
def command_add(args, dbpath):
|
||||
parser = add_option_parser()
|
||||
opts, args = parser.parse_args(sys.argv[:1] + args)
|
||||
if len(args) < 2:
|
||||
parser.print_help()
|
||||
@ -353,9 +359,8 @@ def do_remove(db, ids):
|
||||
if send_message is not None:
|
||||
send_message('refreshdb:', 'calibre GUI')
|
||||
|
||||
|
||||
def command_remove(args, dbpath):
|
||||
parser = get_parser(_(
|
||||
def remove_option_parser():
|
||||
return get_parser(_(
|
||||
'''\
|
||||
%prog remove ids
|
||||
|
||||
@ -363,6 +368,9 @@ Remove the books identified by ids from the database. ids should be a comma sepa
|
||||
list of id numbers (you can get id numbers by using the list command). For example, \
|
||||
23,34,57-85
|
||||
'''))
|
||||
|
||||
def command_remove(args, dbpath):
|
||||
parser = remove_option_parser()
|
||||
opts, args = parser.parse_args(sys.argv[:1] + args)
|
||||
if len(args) < 2:
|
||||
parser.print_help()
|
||||
@ -385,15 +393,18 @@ list of id numbers (you can get id numbers by using the list command). For examp
|
||||
def do_add_format(db, id, fmt, path):
|
||||
db.add_format_with_hooks(id, fmt.upper(), path, index_is_id=True)
|
||||
|
||||
|
||||
def command_add_format(args, dbpath):
|
||||
parser = get_parser(_(
|
||||
def add_format_option_parser():
|
||||
return get_parser(_(
|
||||
'''\
|
||||
%prog add_format [options] id ebook_file
|
||||
|
||||
Add the ebook in ebook_file to the available formats for the logical book identified \
|
||||
by id. You can get id by using the list command. If the format already exists, it is replaced.
|
||||
'''))
|
||||
|
||||
|
||||
def command_add_format(args, dbpath):
|
||||
parser = add_format_option_parser()
|
||||
opts, args = parser.parse_args(sys.argv[:1] + args)
|
||||
if len(args) < 3:
|
||||
parser.print_help()
|
||||
@ -410,8 +421,8 @@ by id. You can get id by using the list command. If the format already exists, i
|
||||
def do_remove_format(db, id, fmt):
|
||||
db.remove_format(id, fmt, index_is_id=True)
|
||||
|
||||
def command_remove_format(args, dbpath):
|
||||
parser = get_parser(_(
|
||||
def remove_format_option_parser():
|
||||
return get_parser(_(
|
||||
'''
|
||||
%prog remove_format [options] id fmt
|
||||
|
||||
@ -420,6 +431,10 @@ You can get id by using the list command. fmt should be a file extension \
|
||||
like LRF or TXT or EPUB. If the logical book does not have fmt available, \
|
||||
do nothing.
|
||||
'''))
|
||||
|
||||
|
||||
def command_remove_format(args, dbpath):
|
||||
parser = remove_format_option_parser()
|
||||
opts, args = parser.parse_args(sys.argv[:1] + args)
|
||||
if len(args) < 3:
|
||||
parser.print_help()
|
||||
@ -441,7 +456,7 @@ def do_show_metadata(db, id, as_opf):
|
||||
else:
|
||||
print unicode(mi).encode(preferred_encoding)
|
||||
|
||||
def command_show_metadata(args, dbpath):
|
||||
def show_metadata_option_parser():
|
||||
parser = get_parser(_(
|
||||
'''
|
||||
%prog show_metadata [options] id
|
||||
@ -451,6 +466,10 @@ id is an id number from the list command.
|
||||
'''))
|
||||
parser.add_option('--as-opf', default=False, action='store_true',
|
||||
help=_('Print metadata in OPF form (XML)'))
|
||||
return parser
|
||||
|
||||
def command_show_metadata(args, dbpath):
|
||||
parser = show_metadata_option_parser()
|
||||
opts, args = parser.parse_args(sys.argv[1:]+args)
|
||||
if len(args) < 2:
|
||||
parser.print_help()
|
||||
@ -468,8 +487,8 @@ def do_set_metadata(db, id, stream):
|
||||
if send_message is not None:
|
||||
send_message('refreshdb:', 'calibre GUI')
|
||||
|
||||
def command_set_metadata(args, dbpath):
|
||||
parser = get_parser(_(
|
||||
def set_metadata_option_parser():
|
||||
return get_parser(_(
|
||||
'''
|
||||
%prog set_metadata [options] id /path/to/metadata.opf
|
||||
|
||||
@ -478,6 +497,9 @@ from the OPF file metadata.opf. id is an id number from the list command. You
|
||||
can get a quick feel for the OPF format by using the --as-opf switch to the
|
||||
show_metadata command.
|
||||
'''))
|
||||
|
||||
def command_set_metadata(args, dbpath):
|
||||
parser = set_metadata_option_parser()
|
||||
opts, args = parser.parse_args(sys.argv[1:]+args)
|
||||
if len(args) < 3:
|
||||
parser.print_help()
|
||||
@ -501,7 +523,7 @@ def do_export(db, ids, dir, opts):
|
||||
prints('\t'+'\n\t'.join(tb.splitlines()))
|
||||
prints(' ')
|
||||
|
||||
def command_export(args, dbpath):
|
||||
def export_option_parser():
|
||||
parser = get_parser(_('''\
|
||||
%prog export [options] ids
|
||||
|
||||
@ -530,6 +552,16 @@ an opf file). You can get id numbers from the list command.
|
||||
parser.add_option(switch, default=opt.default,
|
||||
help=opt.help, dest=pref)
|
||||
|
||||
for pref in ('replace_whitespace', 'to_lowercase'):
|
||||
opt = c.get_option(pref)
|
||||
switch = '--'+pref.replace('_', '-')
|
||||
parser.add_option(switch, default=False, action='store_true',
|
||||
help=opt.help)
|
||||
|
||||
return parser
|
||||
|
||||
def command_export(args, dbpath):
|
||||
parser = export_option_parser()
|
||||
opts, args = parser.parse_args(sys.argv[1:]+args)
|
||||
if (len(args) < 2 and not opts.all):
|
||||
parser.print_help()
|
||||
@ -541,9 +573,11 @@ an opf file). You can get id numbers from the list command.
|
||||
do_export(get_db(dbpath, opts), ids, dir, opts)
|
||||
return 0
|
||||
|
||||
def main(args=sys.argv):
|
||||
commands = ('list', 'add', 'remove', 'add_format', 'remove_format',
|
||||
COMMANDS = ('list', 'add', 'remove', 'add_format', 'remove_format',
|
||||
'show_metadata', 'set_metadata', 'export')
|
||||
|
||||
|
||||
def option_parser():
|
||||
parser = OptionParser(_(
|
||||
'''\
|
||||
%%prog command [options] [arguments]
|
||||
@ -555,11 +589,16 @@ command is one of:
|
||||
|
||||
For help on an individual command: %%prog command --help
|
||||
'''
|
||||
)%'\n '.join(commands))
|
||||
)%'\n '.join(COMMANDS))
|
||||
return parser
|
||||
|
||||
|
||||
def main(args=sys.argv):
|
||||
parser = option_parser()
|
||||
if len(args) < 2:
|
||||
parser.print_help()
|
||||
return 1
|
||||
if args[1] not in commands:
|
||||
if args[1] not in COMMANDS:
|
||||
if args[1] == '--version':
|
||||
parser.print_version()
|
||||
return 0
|
||||
|
@ -1444,14 +1444,33 @@ class LibraryDatabase2(LibraryDatabase):
|
||||
if notify:
|
||||
self.notify('add', [id])
|
||||
|
||||
def get_top_level_move_items(self):
|
||||
items = set(os.listdir(self.library_path))
|
||||
paths = set([])
|
||||
for x in self.data.universal_set():
|
||||
path = self.path(x, index_is_id=True)
|
||||
path = path.split(os.sep)[0]
|
||||
paths.add(path)
|
||||
paths.add('metadata.db')
|
||||
path_map = {}
|
||||
for x in paths:
|
||||
path_map[x] = x
|
||||
if not self.is_case_sensitive:
|
||||
for x in items:
|
||||
path_map[x.lower()] = x
|
||||
items = set(path_map)
|
||||
paths = set([x.lower() for x in paths])
|
||||
items = items.intersection(paths)
|
||||
return items, path_map
|
||||
|
||||
def move_library_to(self, newloc, progress=lambda x: x):
|
||||
if not os.path.exists(newloc):
|
||||
os.makedirs(newloc)
|
||||
items = os.listdir(self.library_path)
|
||||
old_dirs = set([])
|
||||
for i, x in enumerate(items):
|
||||
items, path_map = self.get_top_level_move_items()
|
||||
for x in items:
|
||||
src = os.path.join(self.library_path, x)
|
||||
dest = os.path.join(newloc, x)
|
||||
dest = os.path.join(newloc, path_map[x])
|
||||
if os.path.isdir(src):
|
||||
if os.path.exists(dest):
|
||||
shutil.rmtree(dest)
|
||||
@ -1461,6 +1480,7 @@ class LibraryDatabase2(LibraryDatabase):
|
||||
if os.path.exists(dest):
|
||||
os.remove(dest)
|
||||
shutil.copyfile(src, dest)
|
||||
x = path_map[x]
|
||||
if not isinstance(x, unicode):
|
||||
x = x.decode(filesystem_encoding, 'replace')
|
||||
progress(x)
|
||||
@ -1675,13 +1695,12 @@ books_series_link feeds
|
||||
user_version = self.user_version
|
||||
sql = self.conn.dump()
|
||||
self.conn.close()
|
||||
dest = self.dbpath+'.old'
|
||||
dest = self.dbpath+'.tmp'
|
||||
if os.path.exists(dest):
|
||||
os.remove(dest)
|
||||
shutil.copyfile(self.dbpath, dest)
|
||||
conn = None
|
||||
try:
|
||||
os.remove(self.dbpath)
|
||||
ndb = DBThread(self.dbpath, None)
|
||||
ndb = DBThread(dest, None)
|
||||
ndb.connect()
|
||||
conn = ndb.conn
|
||||
conn.executescript(sql)
|
||||
@ -1690,15 +1709,21 @@ books_series_link feeds
|
||||
conn.commit()
|
||||
conn.close()
|
||||
except:
|
||||
if os.path.exists(self.dbpath):
|
||||
os.remove(self.dbpath)
|
||||
shutil.copyfile(dest, self.dbpath)
|
||||
os.remove(dest)
|
||||
if conn is not None:
|
||||
try:
|
||||
conn.close()
|
||||
except:
|
||||
pass
|
||||
if os.path.exists(dest):
|
||||
os.remove(dest)
|
||||
raise
|
||||
else:
|
||||
os.remove(dest)
|
||||
os.remove(self.dbpath)
|
||||
shutil.copyfile(dest, self.dbpath)
|
||||
self.connect()
|
||||
self.refresh()
|
||||
if os.path.exists(dest):
|
||||
os.remove(dest)
|
||||
callback(0.1, _('Checking for missing files.'))
|
||||
bad = {}
|
||||
us = self.data.universal_set()
|
||||
|
@ -6,7 +6,7 @@ __license__ = 'GPL v3'
|
||||
__copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
import os, traceback, cStringIO
|
||||
import os, traceback, cStringIO, re
|
||||
|
||||
from calibre.utils.config import Config, StringConfig
|
||||
from calibre.utils.filenames import shorten_components_to, supports_long_names, \
|
||||
@ -17,7 +17,7 @@ from calibre.constants import preferred_encoding, filesystem_encoding
|
||||
|
||||
from calibre import strftime
|
||||
|
||||
DEFAULT_TEMPLATE = '{author_sort}/{title} - {authors}'
|
||||
DEFAULT_TEMPLATE = '{author_sort}/{title}/{title} - {authors}'
|
||||
FORMAT_ARG_DESCS = dict(
|
||||
title=_('The title'),
|
||||
authors=_('The authors'),
|
||||
@ -71,6 +71,10 @@ def config(defaults=None):
|
||||
x('timefmt', default='%b, %Y',
|
||||
help=_('The format in which to display dates. %d - day, %b - month, '
|
||||
'%Y - year. Default is: %b, %Y'))
|
||||
x('to_lowercase', default=False,
|
||||
help=_('Convert paths to lowercase.'))
|
||||
x('replace_whitespace', default=False,
|
||||
help=_('Replace whitespace with underscores.'))
|
||||
return c
|
||||
|
||||
def preprocess_template(template):
|
||||
@ -81,7 +85,9 @@ def preprocess_template(template):
|
||||
template = template.decode(preferred_encoding, 'replace')
|
||||
return template
|
||||
|
||||
def get_components(template, mi, id, timefmt='%b %Y', length=250, sanitize_func=ascii_filename):
|
||||
def get_components(template, mi, id, timefmt='%b %Y', length=250,
|
||||
sanitize_func=ascii_filename, replace_whitespace=False,
|
||||
to_lowercase=False):
|
||||
format_args = dict(**FORMAT_ARGS)
|
||||
if mi.title:
|
||||
format_args['title'] = mi.title
|
||||
@ -113,6 +119,11 @@ def get_components(template, mi, id, timefmt='%b %Y', length=250, sanitize_func=
|
||||
components = [str(id)]
|
||||
components = [x.encode(filesystem_encoding, 'replace') if isinstance(x,
|
||||
unicode) else x for x in components]
|
||||
if to_lowercase:
|
||||
components = [x.lower() for x in components]
|
||||
if replace_whitespace:
|
||||
components = [re.sub(r'\s', '_', x) for x in components]
|
||||
|
||||
return shorten_components_to(length, components)
|
||||
|
||||
|
||||
@ -134,7 +145,9 @@ def save_book_to_disk(id, db, root, opts, length):
|
||||
return True, id, mi.title
|
||||
|
||||
components = get_components(opts.template, mi, id, opts.timefmt, length,
|
||||
ascii_filename if opts.asciiize else sanitize_file_name)
|
||||
ascii_filename if opts.asciiize else sanitize_file_name,
|
||||
to_lowercase=opts.to_lowercase,
|
||||
replace_whitespace=opts.replace_whitespace)
|
||||
base_path = os.path.join(root, *components)
|
||||
base_name = os.path.basename(base_path)
|
||||
dirpath = os.path.dirname(base_path)
|
||||
|
@ -110,7 +110,7 @@ class LibraryServer(object):
|
||||
<title>${authors}</title>
|
||||
<id>urn:calibre:${record[FM['id']]}</id>
|
||||
<updated>${timestamp}</updated>
|
||||
<link type="application/atom+xml" href="/?authorid=${record[FM['id']]}" />
|
||||
<link type="application/atom+xml" href="/stanza/?authorid=${record[FM['id']]}" />
|
||||
</entry>
|
||||
'''))
|
||||
|
||||
@ -120,7 +120,7 @@ class LibraryServer(object):
|
||||
<title>calibre Library</title>
|
||||
<id>$id</id>
|
||||
<updated>${updated.strftime('%Y-%m-%dT%H:%M:%S+00:00')}</updated>
|
||||
<link rel="search" title="Search" type="application/atom+xml" href="/?search={searchTerms}"/>
|
||||
<link rel="search" title="Search" type="application/atom+xml" href="/stanza/?search={searchTerms}"/>
|
||||
<author>
|
||||
<name>calibre</name>
|
||||
<uri>http://calibre.kovidgoyal.net</uri>
|
||||
@ -140,7 +140,7 @@ class LibraryServer(object):
|
||||
<title>calibre Library</title>
|
||||
<id>$id</id>
|
||||
<updated>${updated.strftime('%Y-%m-%dT%H:%M:%S+00:00')}</updated>
|
||||
<link rel="search" title="Search" type="application/atom+xml" href="/?search={searchTerms}"/>
|
||||
<link rel="search" title="Search" type="application/atom+xml" href="/stanza/?search={searchTerms}"/>
|
||||
<author>
|
||||
<name>calibre</name>
|
||||
<uri>http://calibre.kovidgoyal.net</uri>
|
||||
@ -152,19 +152,19 @@ class LibraryServer(object):
|
||||
<title>By Author</title>
|
||||
<id>urn:uuid:fc000fa0-8c23-11de-a31d-0002a5d5c51b</id>
|
||||
<updated>${updated.strftime('%Y-%m-%dT%H:%M:%S+00:00')}</updated>
|
||||
<link type="application/atom+xml" href="/?sortby=byauthor" />
|
||||
<link type="application/atom+xml" href="/stanza/?sortby=byauthor" />
|
||||
</entry>
|
||||
<entry>
|
||||
<title>By Title</title>
|
||||
<id>urn:uuid:1df4fe40-8c24-11de-b4c6-0002a5d5c51b</id>
|
||||
<updated>${updated.strftime('%Y-%m-%dT%H:%M:%S+00:00')}</updated>
|
||||
<link type="application/atom+xml" href="/?sortby=bytitle" />
|
||||
<link type="application/atom+xml" href="/stanza/?sortby=bytitle" />
|
||||
</entry>
|
||||
<entry>
|
||||
<title>By Newest</title>
|
||||
<id>urn:uuid:3c6d4940-8c24-11de-a4d7-0002a5d5c51b</id>
|
||||
<updated>${updated.strftime('%Y-%m-%dT%H:%M:%S+00:00')}</updated>
|
||||
<link type="application/atom+xml" href="/?sortby=bynewest" />
|
||||
<link type="application/atom+xml" href="/stanza/?sortby=bynewest" />
|
||||
</entry>
|
||||
</feed>
|
||||
'''))
|
||||
@ -460,8 +460,11 @@ class LibraryServer(object):
|
||||
@expose
|
||||
def index(self, **kwargs):
|
||||
'The / URL'
|
||||
want_opds = cherrypy.request.headers.get('Stanza-Device-Name', 919) != \
|
||||
919 or cherrypy.request.headers.get('Want-OPDS-Catalog', 919) != 919
|
||||
ua = cherrypy.request.headers.get('User-Agent', '').strip()
|
||||
want_opds = \
|
||||
cherrypy.request.headers.get('Stanza-Device-Name', 919) != 919 or \
|
||||
cherrypy.request.headers.get('Want-OPDS-Catalog', 919) != 919 or \
|
||||
ua.startswith('Stanza')
|
||||
return self.stanza(search=kwargs.get('search', None), sortby=kwargs.get('sortby',None), authorid=kwargs.get('authorid',None)) if want_opds else self.static('index.html')
|
||||
|
||||
|
||||
|
@ -157,6 +157,8 @@ class DatabaseException(Exception):
|
||||
def proxy(fn):
|
||||
''' Decorator to call methods on the database connection in the proxy thread '''
|
||||
def run(self, *args, **kwargs):
|
||||
if self.closed:
|
||||
raise DatabaseException('Connection closed', '')
|
||||
with global_lock:
|
||||
if self.proxy.unhandled_error[0] is not None:
|
||||
raise DatabaseException(*self.proxy.unhandled_error)
|
||||
@ -174,10 +176,12 @@ class ConnectionProxy(object):
|
||||
|
||||
def __init__(self, proxy):
|
||||
self.proxy = proxy
|
||||
self.closed = False
|
||||
|
||||
def close(self):
|
||||
if self.proxy.unhandled_error is None:
|
||||
if self.proxy.unhandled_error[0] is None:
|
||||
self.proxy.requests.put((self.proxy.CLOSE, [], {}))
|
||||
self.closed = True
|
||||
|
||||
@proxy
|
||||
def get(self, query, all=True): pass
|
||||
|
@ -69,6 +69,46 @@ CLI_PREAMBLE='''\
|
||||
{usage}
|
||||
'''
|
||||
|
||||
def generate_calibredb_help(preamble, info):
|
||||
from calibre.library.cli import COMMANDS, get_parser
|
||||
import calibre.library.cli as cli
|
||||
preamble = preamble[:preamble.find('\n\n\n', preamble.find('code-block'))]
|
||||
preamble += textwrap.dedent('''
|
||||
|
||||
:command:`calibredb` is the command line interface to the |app| database. It has
|
||||
several sub-commands, documented below:
|
||||
|
||||
''')
|
||||
|
||||
global_parser = get_parser('')
|
||||
groups = []
|
||||
for grp in global_parser.option_groups:
|
||||
groups.append((grp.title.capitalize(), grp.description, grp.option_list))
|
||||
|
||||
global_options = '\n'.join(render_options('calibredb', groups, False, False))
|
||||
|
||||
|
||||
lines, toc = [], []
|
||||
for cmd in COMMANDS:
|
||||
parser = getattr(cli, cmd+'_option_parser')()
|
||||
toc.append(' * :ref:`calibredb-%s`'%cmd)
|
||||
lines += ['.. _calibredb-'+cmd+':', '']
|
||||
lines += [cmd, '~'*20, '']
|
||||
usage = parser.usage.strip()
|
||||
usage = [i for i in usage.replace('%prog', 'calibredb').splitlines()]
|
||||
cmdline = ' '+usage[0]
|
||||
usage = usage[1:]
|
||||
usage = [i.replace(cmd, ':command:`%s`'%cmd) for i in usage]
|
||||
lines += ['.. code-block:: none', '', cmdline, '']
|
||||
lines += usage
|
||||
groups = [(None, None, parser.option_list)]
|
||||
lines += ['']
|
||||
lines += render_options('calibredb '+cmd, groups, False)
|
||||
lines += ['']
|
||||
|
||||
toc = '\n'.join(toc)
|
||||
raw = preamble + '\n\n'+toc + '\n\n' + global_options+'\n\n'+'\n'.join(lines)
|
||||
update_cli_doc(os.path.join('cli', 'calibredb.rst'), raw, info)
|
||||
|
||||
def generate_ebook_convert_help(preamble, info):
|
||||
from calibre.ebooks.conversion.cli import create_option_parser
|
||||
@ -125,11 +165,12 @@ def update_cli_doc(path, raw, info):
|
||||
info('creating '+os.path.splitext(os.path.basename(path))[0])
|
||||
open(path, 'wb').write(raw)
|
||||
|
||||
def render_options(cmd, groups, options_header=True):
|
||||
lines = []
|
||||
def render_options(cmd, groups, options_header=True, add_program=True):
|
||||
lines = ['']
|
||||
if options_header:
|
||||
lines = ['[options]', '-'*15, '']
|
||||
lines += ['.. program:: '+cmd, '']
|
||||
if add_program:
|
||||
lines += ['.. program:: '+cmd, '']
|
||||
for title, desc, options in groups:
|
||||
if title:
|
||||
lines.extend([title, '~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~'])
|
||||
@ -153,6 +194,7 @@ def cli_docs(app):
|
||||
for script in entry_points['console_scripts']:
|
||||
module = script[script.index('=')+1:script.index(':')].strip()
|
||||
cmd = script[:script.index('=')].strip()
|
||||
if cmd in ('calibre-complete', 'calibre-parallel'): continue
|
||||
module = __import__(module, fromlist=[module.split('.')[-1]])
|
||||
if hasattr(module, 'option_parser'):
|
||||
documented_cmds.append((cmd, getattr(module, 'option_parser')()))
|
||||
@ -180,6 +222,8 @@ def cli_docs(app):
|
||||
preamble = CLI_PREAMBLE.format(cmd=cmd, cmdline=cmdline, usage=usage)
|
||||
if cmd == 'ebook-convert':
|
||||
generate_ebook_convert_help(preamble, info)
|
||||
elif cmd == 'calibredb':
|
||||
generate_calibredb_help(preamble, info)
|
||||
else:
|
||||
groups = [(None, None, parser.option_list)]
|
||||
for grp in parser.option_groups:
|
||||
|
@ -54,11 +54,11 @@ In order to convert a collection of HTML files in a specific oder, you have to c
|
||||
|
||||
Then just add this HTML file to the GUI and use the convert button to create your ebook.
|
||||
|
||||
How do I convert my file containing non-English characters?
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
How do I convert my file containing non-English characters, or smart quotes?
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
There are two aspects to this problem:
|
||||
1. Knowing the encoding of the source file: |app| tries to guess what character encoding your source files use, but often, this is impossible, so you need to tell it what encoding to use. This can be done in the GUI via the :guilabel:`Input character encoding` field in the :guilabel:`Look & Feel` section. The command-line tools all have an :option:`--input-encoding` option.
|
||||
2. When adding HTML files to |app|, you may need to tell |app| what encoding the files are in. To do this go to Preferences->Plugins->File Type plugins and customize the HTML2Zip plugin, telling it what encoding your HTML files are in. |app| will then automatically convert the HTML files into the UTF-8 encoding when adding them.
|
||||
2. When adding HTML files to |app|, you may need to tell |app| what encoding the files are in. To do this go to Preferences->Plugins->File Type plugins and customize the HTML2Zip plugin, telling it what encoding your HTML files are in. Now when you add HTML files to |app| they will be correctly processed. HTML files from different sources often have different encodings, so you may have to change this setting repeatedly. A common encoding for many files from the web is ``cp1252`` and I would suggest you try that first.
|
||||
3. Embedding fonts: If you are generating an LRF file to read on your SONY Reader, you are limited by the fact that the Reader only supports a few non-English characters in the fonts it comes pre-loaded with. You can work around this problem by embedding a unicode-aware font that supports the character set your file uses into the LRF file. You should embed atleast a serif and a sans-serif font. Be aware that embedding fonts significantly slows down page-turn speed on the reader.
|
||||
|
||||
|
||||
@ -95,13 +95,17 @@ turned into a collection on the reader. Note that the PRS-500 does not support c
|
||||
|
||||
How do I use |app| with my iPhone?
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
First install the Stanza reader on your iPhone from http://www.lexcycle.com . Then,
|
||||
First install the Stanza reader on your iPhone using iTunes.
|
||||
|
||||
* Set the output format for calibre to EPUB (The output format can be set next to the big red heart)
|
||||
* Set the Preferred Output Format in |app| to EPUB (The output format can be set under Preferences->General)
|
||||
* Convert the books you want to read on your iPhone to EPUB format by selecting them and clicking the Convert button.
|
||||
* Turn on the Content Server in |app|'s preferences and leave |app| running.
|
||||
|
||||
Now you should be able to access your books on your iPhone.
|
||||
Now you should be able to access your books on your iPhone by opening Stanza and going to "Shared Books". Under Shared Books you will see an entry "Book in calibre". If you don't, make sure your iPhone is connected using the WiFi network in your house, not 3G. If the |app| catalog is still not detected in Stanza, you can add it manually in Stanza, by clicking "Online Catalog" and the clicking the plus icon in the lower right corner to add a new catalog. In the Add Catalog screen enter whatever name you like and in the URL field, enter the following::
|
||||
|
||||
http://192.168.1.2:8080/
|
||||
|
||||
Replace ``192.168.1.2`` with the local IP address of the computer running |app|. If you have changed the port the |app| content server is running on, you will have to change ``8080`` as well to the new port. The local IP address is the IP address you computer is assigned on your home network. A quick Google search will tell you how to find out your local IP address.
|
||||
|
||||
Why is my device not detected in linux?
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
@ -136,6 +140,12 @@ Why doesn't |app| have a column for foo?
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|app| is designed to have columns for the most frequently and widely used fields. If it does not have a coulmn for your favorite field, you can always add a tag to the book for that piece of information. |app| also supports a general purpose "comments" fields for longer items.
|
||||
|
||||
How do I move my |app| library from one computer to another?
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Simply copy the |app| library folder from the old to the new computer. You can find out what the library folder is by clicking Preferences. The very first item is the path tot he library folder. Now on the new computer, start |app| for the first time. It will run the Welcome Wizard asking you for the location of the |app| library. Point it to the previously copied folder.
|
||||
|
||||
Note that if you are transferring between different types of computers (for example Windows to OS X) then after doing the above you should also go to Preferences->Advanced and click the Check database integrity button. It will warn you about missing files, if any, which you should then transfer by hand.
|
||||
|
||||
|
||||
Content From The Web
|
||||
---------------------
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -9,7 +9,7 @@ __docformat__ = 'restructuredtext en'
|
||||
import os, sys
|
||||
from threading import Thread
|
||||
|
||||
from calibre.constants import plugins
|
||||
from calibre.constants import plugins, iswindows
|
||||
|
||||
_fc, _fc_err = plugins['fontconfig']
|
||||
|
||||
@ -31,6 +31,12 @@ class FontConfig(Thread):
|
||||
if isinstance(config_dir, unicode):
|
||||
config_dir = config_dir.encode(sys.getfilesystemencoding())
|
||||
config = os.path.join(config_dir, 'fonts.conf')
|
||||
if iswindows and getattr(sys, 'frozen', False):
|
||||
config_dir = os.path.join(os.path.dirname(sys.executable),
|
||||
'etc', 'fonts')
|
||||
if isinstance(config_dir, unicode):
|
||||
config_dir = config_dir.encode(sys.getfilesystemencoding())
|
||||
config = os.path.join(config_dir, 'fonts.conf')
|
||||
try:
|
||||
_fc.initialize(config)
|
||||
except:
|
||||
|
@ -22,6 +22,7 @@ def get_metadata(stream, cover=True):
|
||||
raw = stream.read()
|
||||
doc = poppler.PDFDoc()
|
||||
doc.load(raw)
|
||||
del raw
|
||||
title = doc.title
|
||||
if not title or not title.strip():
|
||||
title = _('Unknown')
|
||||
@ -55,7 +56,6 @@ def get_metadata(stream, cover=True):
|
||||
if cdata is not None:
|
||||
mi.cover_data = ('jpg', cdata)
|
||||
del doc
|
||||
del raw
|
||||
return mi
|
||||
|
||||
|
||||
|
@ -176,15 +176,16 @@ poppler_PDFDoc_render_page(poppler_PDFDoc *self, PyObject *args, PyObject *kwarg
|
||||
int num;
|
||||
|
||||
if (!PyArg_ParseTuple(args, "i|ff", &num, &xdpi, &ydpi)) return ans;
|
||||
if ( num < 0 || num >= self->doc->numPages()) {
|
||||
PyErr_SetString(PyExc_ValueError, "Invalid page number");
|
||||
return ans;
|
||||
}
|
||||
if ( self->doc->isLocked()) {
|
||||
PyErr_SetString(PyExc_ValueError, "This document is copyrighted.");
|
||||
return ans;
|
||||
}
|
||||
|
||||
if ( num < 0 || num >= self->doc->numPages()) {
|
||||
PyErr_SetString(PyExc_ValueError, "Invalid page number");
|
||||
return ans;
|
||||
}
|
||||
|
||||
page = self->doc->page(num);
|
||||
img = page->renderToImage(xdpi, ydpi);
|
||||
if (img.isNull()) {
|
||||
|
@ -929,6 +929,7 @@ initwinutil(void) {
|
||||
PyModule_AddIntConstant(m, "CSIDL_COOKIES", CSIDL_COOKIES);
|
||||
PyModule_AddIntConstant(m, "CSIDL_FLAG_CREATE", CSIDL_FLAG_CREATE);
|
||||
PyModule_AddIntConstant(m, "CSIDL_FLAG_DONT_VERIFY", CSIDL_FLAG_DONT_VERIFY);
|
||||
PyModule_AddIntConstant(m, "CSIDL_FONTS", CSIDL_FONTS);
|
||||
PyModule_AddIntConstant(m, "CSIDL_HISTORY", CSIDL_HISTORY);
|
||||
PyModule_AddIntConstant(m, "CSIDL_INTERNET_CACHE", CSIDL_INTERNET_CACHE);
|
||||
PyModule_AddIntConstant(m, "CSIDL_LOCAL_APPDATA", CSIDL_LOCAL_APPDATA);
|
||||
|
@ -679,7 +679,7 @@ class BasicNewsRecipe(Recipe):
|
||||
fetcher.browser_lock = fetcher.DUMMY_LOCK
|
||||
res, path, failures = fetcher.start_fetch(url), fetcher.downloaded_paths, fetcher.failed_links
|
||||
if not res or not os.path.exists(res):
|
||||
raise Exception(_('Could not fetch article. Run with --debug to see the reason'))
|
||||
raise Exception(_('Could not fetch article. Run with -vv to see the reason'))
|
||||
return res, path, failures
|
||||
|
||||
def fetch_article(self, url, dir, f, a, num_of_feeds):
|
||||
@ -741,6 +741,9 @@ class BasicNewsRecipe(Recipe):
|
||||
url = self.print_version(article.url)
|
||||
except NotImplementedError:
|
||||
url = article.url
|
||||
except:
|
||||
self.log.exception('Failed to find print version for: '+article.url)
|
||||
url = None
|
||||
if not url:
|
||||
continue
|
||||
func, arg = (self.fetch_embedded_article, article) if self.use_embedded_content else \
|
||||
|
@ -42,7 +42,7 @@ recipe_modules = ['recipe_' + r for r in (
|
||||
'moneynews', 'der_standard', 'diepresse', 'nzz_ger', 'hna',
|
||||
'seattle_times', 'scott_hanselman', 'coding_horror', 'twitchfilms',
|
||||
'stackoverflow', 'telepolis_artikel', 'zaobao', 'usnews',
|
||||
'straitstimes', 'index_hu', 'pcworld_hu', 'hrt', 'rts',
|
||||
'straitstimes', 'index_hu', 'pcworld_hu', 'hrt', 'rts', 'axxon_news',
|
||||
'h1', 'h2', 'h3', 'phd_comics', 'woz_die', 'elektrolese',
|
||||
'climate_progress', 'carta', 'slashdot', 'publico',
|
||||
'the_budget_fashionista', 'elperiodico_catalan',
|
||||
@ -55,7 +55,7 @@ recipe_modules = ['recipe_' + r for r in (
|
||||
'eltiempo_hn', 'slate', 'tnxm', 'bbcvietnamese', 'vnexpress',
|
||||
'volksrant', 'theeconomictimes_india', 'ourdailybread',
|
||||
'monitor', 'republika', 'beta', 'beta_en', 'glasjavnosti',
|
||||
'esquire',
|
||||
'esquire', 'livemint', 'thedgesingapore', 'darknet',
|
||||
)]
|
||||
|
||||
|
||||
|
61
src/calibre/web/feeds/recipes/recipe_axxon_news.py
Normal file
61
src/calibre/web/feeds/recipes/recipe_axxon_news.py
Normal file
@ -0,0 +1,61 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2009, Darko Miletic <darko.miletic at gmail.com>'
|
||||
'''
|
||||
axxon.com.ar
|
||||
'''
|
||||
from calibre.web.feeds.news import BasicNewsRecipe
|
||||
from calibre.ebooks.BeautifulSoup import Tag
|
||||
|
||||
class Axxon_news(BasicNewsRecipe):
|
||||
title = 'Axxon noticias'
|
||||
__author__ = 'Darko Miletic'
|
||||
description = 'Axxon, Ciencia Ficcion en Bits'
|
||||
publisher = 'Axxon'
|
||||
category = 'news, SF, Argentina, science, movies'
|
||||
oldest_article = 7
|
||||
max_articles_per_feed = 100
|
||||
no_stylesheets = False
|
||||
use_embedded_content = False
|
||||
language = _('Spanish')
|
||||
lang = 'es-AR'
|
||||
|
||||
conversion_options = {
|
||||
'comment' : description
|
||||
, 'tags' : category
|
||||
, 'publisher' : publisher
|
||||
, 'language' : lang
|
||||
, 'pretty_print' : True
|
||||
}
|
||||
|
||||
|
||||
keep_only_tags = [dict(name='div', attrs={'class':'post'})]
|
||||
|
||||
remove_tags = [dict(name=['object','link','iframe','embed'])]
|
||||
|
||||
feeds = [(u'Noticias', u'http://axxon.com.ar/noticias/feed/')]
|
||||
|
||||
remove_attributes = ['style','width','height','font','border','align']
|
||||
|
||||
|
||||
def adeify_images2(cls, soup):
|
||||
for item in soup.findAll('img'):
|
||||
for attrib in ['height','width','border','align','style']:
|
||||
if item.has_key(attrib):
|
||||
del item[attrib]
|
||||
oldParent = item.parent
|
||||
if oldParent.name == 'a':
|
||||
oldParent.name == 'p'
|
||||
myIndex = oldParent.contents.index(item)
|
||||
brtag = Tag(soup,'br')
|
||||
oldParent.insert(myIndex+1,brtag)
|
||||
return soup
|
||||
|
||||
def preprocess_html(self, soup):
|
||||
soup.html['xml:lang'] = self.lang
|
||||
soup.html['lang'] = self.lang
|
||||
mlang = Tag(soup,'meta',[("http-equiv","Content-Language"),("content",self.lang)])
|
||||
soup.html.insert(0,mlang)
|
||||
return self.adeify_images2(soup)
|
||||
|
43
src/calibre/web/feeds/recipes/recipe_darknet.py
Normal file
43
src/calibre/web/feeds/recipes/recipe_darknet.py
Normal file
@ -0,0 +1,43 @@
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||
|
||||
'''
|
||||
Fetch darknet.
|
||||
'''
|
||||
|
||||
from calibre.web.feeds.news import BasicNewsRecipe
|
||||
|
||||
|
||||
class darknet(BasicNewsRecipe):
|
||||
|
||||
title = 'darknet'
|
||||
description = 'Ethical hacking and security news'
|
||||
__author__ = 'Oliver Niesner'
|
||||
language = _('English')
|
||||
use_embedded_content = False
|
||||
timefmt = ' [%b %d %Y]'
|
||||
max_articles_per_feed = 40
|
||||
no_stylesheets = True
|
||||
oldest_article = 180
|
||||
|
||||
remove_tags = [dict(id='navi_top'),
|
||||
dict(id='navi_bottom'),
|
||||
dict(id='logo'),
|
||||
dict(id='login_suche'),
|
||||
dict(id='navi_login'),
|
||||
dict(id='breadcrumb'),
|
||||
dict(id='subtitle'),
|
||||
dict(id='bannerzone'),
|
||||
dict(name='span', attrs={'class':'rsaquo'}),
|
||||
dict(name='span', attrs={'class':'next'}),
|
||||
dict(name='span', attrs={'class':'prev'}),
|
||||
dict(name='div', attrs={'class':'news_logo'}),
|
||||
dict(name='div', attrs={'class':'nextprev'}),
|
||||
dict(name='p', attrs={'class':'news_option'}),
|
||||
dict(name='p', attrs={'class':'news_foren'})]
|
||||
remove_tags_after = [dict(name='div', attrs={'class':'entrybody'})]
|
||||
|
||||
feeds = [ ('darknet', 'http://feedproxy.google.com/darknethackers') ]
|
||||
|
||||
|
||||
|
@ -32,7 +32,6 @@ class elektrolese(BasicNewsRecipe):
|
||||
|
||||
|
||||
|
||||
feeds = [ (u'electrolese', u'http://elektrolese.blogspot.com/feeds/posts/default?alt=rss') ]
|
||||
|
||||
feeds = [ (u'elektrolese', u'http://elektrolese.blogspot.com/feeds/posts/default?alt=rss') ]
|
||||
|
||||
|
||||
|
@ -19,16 +19,24 @@ class hnaDe(BasicNewsRecipe):
|
||||
timefmt = ' [%d %b %Y]'
|
||||
max_articles_per_feed = 40
|
||||
no_stylesheets = True
|
||||
remove_javascript = True
|
||||
encoding = 'iso-8859-1'
|
||||
|
||||
remove_tags = [dict(id='topnav'),
|
||||
dict(id='nav_main'),
|
||||
dict(id='teaser'),
|
||||
dict(id='suchen'),
|
||||
dict(id='superbanner'),
|
||||
dict(id='navigation'),
|
||||
dict(id='skyscraper'),
|
||||
dict(id=''),
|
||||
dict(name='span'),
|
||||
dict(name='ul', attrs={'class':'linklist'}),
|
||||
dict(name='a', attrs={'href':'#'}),
|
||||
dict(name='div', attrs={'class':'hlist'}),
|
||||
dict(name='div', attrs={'class':'subc noprint'}),
|
||||
dict(name='p', attrs={'class':'breadcrumb'}),
|
||||
dict(name='a', attrs={'style':'cursor:hand'}),
|
||||
dict(name='p', attrs={'class':'h5'})]
|
||||
#remove_tags_after = [dict(name='div', attrs={'class':'rahmenbreaking'})]
|
||||
remove_tags_after = [dict(name='a', attrs={'href':'#'})]
|
||||
@ -38,3 +46,4 @@ class hnaDe(BasicNewsRecipe):
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -8,7 +8,7 @@ import re
|
||||
from calibre.web.feeds.news import BasicNewsRecipe
|
||||
|
||||
|
||||
class Sueddeutsche(BasicNewsRecipe):
|
||||
class LinuxDevices(BasicNewsRecipe):
|
||||
|
||||
title = u'Linuxdevices'
|
||||
description = 'News about Linux driven Hardware'
|
||||
@ -16,22 +16,22 @@ class Sueddeutsche(BasicNewsRecipe):
|
||||
use_embedded_content = False
|
||||
timefmt = ' [%a %d %b %Y]'
|
||||
max_articles_per_feed = 50
|
||||
language = _('English')
|
||||
no_stylesheets = True
|
||||
html2epub_options = 'linearize_tables = True\nbase_font_size2=14'
|
||||
html2lrf_options = ['--ignore-tables']
|
||||
language = _('English')
|
||||
remove_javascript = True
|
||||
conversion_options = { 'linearize_tables' : True}
|
||||
encoding = 'latin1'
|
||||
|
||||
|
||||
remove_tags_after = [dict(id='nointelliTXT')]
|
||||
remove_tags_after = [dict(id='intelliTxt')]
|
||||
filter_regexps = [r'ad\.doubleclick\.net']
|
||||
|
||||
remove_tags = [dict(name='div', attrs={'class':'bannerSuperBanner'}),
|
||||
dict(name='div', attrs={'class':'bannerSky'}),
|
||||
dict(name='div', attrs={'border':'0'}),
|
||||
dict(name='div', attrs={'class':'footerLinks'}),
|
||||
dict(name='div', attrs={'class':'seitenanfang'}),
|
||||
dict(name='td', attrs={'class':'mar5'}),
|
||||
dict(name='td', attrs={'class':'mar5'}),
|
||||
dict(name='table', attrs={'class':'pageAktiv'}),
|
||||
dict(name='table', attrs={'class':'xartable'}),
|
||||
dict(name='table', attrs={'class':'wpnavi'}),
|
||||
@ -40,24 +40,26 @@ class Sueddeutsche(BasicNewsRecipe):
|
||||
dict(name='table', attrs={'class':'artikelBox'}),
|
||||
dict(name='table', attrs={'class':'kommentare'}),
|
||||
dict(name='table', attrs={'class':'pageBoxBot'}),
|
||||
dict(name='table', attrs={'td':'height="3"'}),
|
||||
dict(name='table', attrs={'class':'contentpaneopen'}),
|
||||
dict(name='td', attrs={'nowrap':'nowrap'}),
|
||||
dict(name='td', attrs={'valign':'middle'}),
|
||||
dict(name='td', attrs={'align':'left'}),
|
||||
dict(name='td', attrs={'align':'center'}),
|
||||
dict(name='td', attrs={'height':'5'}),
|
||||
dict(name='td', attrs={'class':'ArticleWidgetsHeadline'}),
|
||||
dict(name='div', attrs={'class':'artikelBox navigatorBox'}),
|
||||
dict(name='div', attrs={'class':'similar-article-box'}),
|
||||
dict(name='div', attrs={'class':'videoBigHack'}),
|
||||
dict(name='td', attrs={'class':'artikelDruckenRight'}),
|
||||
dict(name='td', attrs={'class':'width="200"'}),
|
||||
dict(name='span', attrs={'class':'content_rating'}),
|
||||
dict(name='a', attrs={'href':'http://www.addthis.com/bookmark.php'}),
|
||||
dict(name='a', attrs={'href':'/news'}),
|
||||
dict(name='a', attrs={'href':'/'}),
|
||||
dict(name='a', attrs={'href':'/articles'}),
|
||||
dict(name='a', attrs={'href':'/cgi-bin/survey/survey.cgi'}),
|
||||
dict(name='a', attrs={'href':'/cgi-bin/board/UltraBoard.pl'}),
|
||||
dict(name='iframe'),
|
||||
dict(name='form'),
|
||||
dict(name='span', attrs={'class':'hidePrint'}),
|
||||
dict(id='ArticleWidgets'),
|
||||
dict(id='headerLBox'),
|
||||
dict(id='nointelliTXT'),
|
||||
dict(id='rechteSpalte'),
|
||||
@ -69,27 +71,18 @@ class Sueddeutsche(BasicNewsRecipe):
|
||||
dict(id='nnav-headerteaser'),
|
||||
dict(id='nnav-head'),
|
||||
dict(id='nnav-top'),
|
||||
dict(id='nnav-logodiv'),
|
||||
dict(id='nnav-logo'),
|
||||
dict(id='nnav-oly'),
|
||||
dict(id='readcomment')]
|
||||
|
||||
|
||||
|
||||
feeds = [ (u'Linuxdevices', u'http://www.linuxdevices.com/backend/headlines.rss') ]
|
||||
feeds = [ (u'Linuxdevices', u'http://www.linuxfordevices.com/rss.xml') ]
|
||||
|
||||
def preprocess_html(self, soup):
|
||||
for item in soup.findAll(re.compile('^a')):
|
||||
item.extract()
|
||||
match = re.compile(r"^Related")
|
||||
for item in soup.findAll('b', text=match):
|
||||
item.extract()
|
||||
for item in soup.findAll(re.compile('^li')):
|
||||
item.extract()
|
||||
for item in soup.findAll(re.compile('^ul')):
|
||||
item.extract()
|
||||
for item in soup.find(re.compile('^br')):
|
||||
item.extract()
|
||||
for item in soup.findAll('br', limit=10):
|
||||
item.extract()
|
||||
return soup
|
||||
@ -101,4 +94,3 @@ class Sueddeutsche(BasicNewsRecipe):
|
||||
return soup
|
||||
|
||||
|
||||
|
||||
|
40
src/calibre/web/feeds/recipes/recipe_livemint.py
Normal file
40
src/calibre/web/feeds/recipes/recipe_livemint.py
Normal file
@ -0,0 +1,40 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2009, Darko Miletic <darko.miletic at gmail.com>'
|
||||
'''
|
||||
www.livemint.com
|
||||
'''
|
||||
|
||||
from calibre.web.feeds.news import BasicNewsRecipe
|
||||
|
||||
class LiveMint(BasicNewsRecipe):
|
||||
title = u'Livemint'
|
||||
__author__ = 'Darko Miletic'
|
||||
description = 'The Wall Street Journal'
|
||||
publisher = 'The Wall Street Journal'
|
||||
category = 'news, games, adventure, technology'
|
||||
language = _('English')
|
||||
oldest_article = 15
|
||||
max_articles_per_feed = 100
|
||||
no_stylesheets = True
|
||||
encoding = 'utf-8'
|
||||
use_embedded_content = False
|
||||
extra_css = ' #dvArtheadline{font-size: x-large} #dvArtAbstract{font-size: large} '
|
||||
|
||||
keep_only_tags = [dict(name='div', attrs={'class':'innercontent'})]
|
||||
|
||||
remove_tags = [dict(name=['object','link','embed','form','iframe'])]
|
||||
|
||||
feeds = [(u'Articles', u'http://www.livemint.com/SectionRssfeed.aspx?Mid=1')]
|
||||
|
||||
def print_version(self, url):
|
||||
link = url
|
||||
msoup = self.index_to_soup(link)
|
||||
mlink = msoup.find(attrs={'id':'ctl00_bodyplaceholdercontent_cntlArtTool_printUrl'})
|
||||
if mlink:
|
||||
link = 'http://www.livemint.com/Articles/' + mlink['href'].rpartition('/Articles/')[2]
|
||||
return link
|
||||
|
||||
def preprocess_html(self, soup):
|
||||
return self.adeify_images(soup)
|
@ -1,87 +1,132 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||
'''
|
||||
outlookindia.com
|
||||
'''
|
||||
|
||||
from calibre.web.feeds.news import BasicNewsRecipe
|
||||
__copyright__ = '2009, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||
import re
|
||||
from calibre import strftime
|
||||
from calibre.ebooks.BeautifulSoup import BeautifulSoup, Tag
|
||||
from calibre.web.feeds.news import BasicNewsRecipe
|
||||
|
||||
class OutlookIndia(BasicNewsRecipe):
|
||||
|
||||
title = 'Outlook India'
|
||||
__author__ = 'Kovid Goyal'
|
||||
description = 'Weekly news magazine focused on India.'
|
||||
|
||||
title = 'Outlook India'
|
||||
__author__ = 'Kovid Goyal and Sujata Raman'
|
||||
description = 'Weekly news and current affairs in India'
|
||||
no_stylesheets = True
|
||||
encoding = 'utf-8'
|
||||
language = _('English')
|
||||
recursions = 1
|
||||
match_regexp = r'full.asp.*&pn=\d+'
|
||||
|
||||
remove_tags = [
|
||||
dict(name='img', src="images/space.gif"),
|
||||
dict(name=lambda tag: tag.name == 'tr' and tag.find('img', src="image/tl.gif") is not None ),
|
||||
dict(name=lambda tag: tag.name == 'table' and tag.find('font', attrs={'class':'fontemailfeed'}) is not None),
|
||||
]
|
||||
|
||||
preprocess_regexps = [
|
||||
(re.compile(r'<body.*?<!--Add Banner ends from here-->', re.DOTALL|re.IGNORECASE),
|
||||
lambda match: '<body>'),
|
||||
|
||||
(re.compile(r'>More Stories:.*', re.DOTALL),
|
||||
lambda match: '></body></html>'),
|
||||
|
||||
(re.compile(r'<!-- Google panel start -->.*', re.DOTALL),
|
||||
lambda match: '</body></html>'),
|
||||
]
|
||||
|
||||
def parse_index(self):
|
||||
soup = self.index_to_soup('http://www.outlookindia.com/archivecontents.asp')
|
||||
feeds = []
|
||||
title = None
|
||||
bogus = True
|
||||
for table in soup.findAll('table'):
|
||||
if title is None:
|
||||
td = table.find('td', background="images/content_band1.jpg")
|
||||
if td is not None:
|
||||
title = self.tag_to_string(td, False)
|
||||
title = title.replace(u'\xa0', u'').strip()
|
||||
if 'Cover Story' in title and bogus:
|
||||
bogus = False
|
||||
title = None
|
||||
else:
|
||||
articles = []
|
||||
for a in table.findAll('a', href=True):
|
||||
if a.find('img') is not None:
|
||||
continue
|
||||
atitle = self.tag_to_string(a, use_alt=False)
|
||||
desc = a.findNextSibling('font', attrs={'class':'fontintro'})
|
||||
if desc is not None:
|
||||
desc = self.tag_to_string(desc)
|
||||
if not desc:
|
||||
desc = ''
|
||||
articles.append({
|
||||
'title':atitle,
|
||||
'description': desc,
|
||||
'content': '',
|
||||
'url':'http://www.outlookindia.com/'+a['href'],
|
||||
'date': '',
|
||||
})
|
||||
feeds.append((title, articles))
|
||||
title = None
|
||||
|
||||
|
||||
return feeds
|
||||
extra_css = '''
|
||||
body{font-family:Arial,Helvetica,sans-serif; font-size:xx-small;}
|
||||
.fspheading{color:#AF0E25 ; font-family:"Times New Roman",Times,serif; font-weight:bold ; font-size:large; }
|
||||
.fspauthor{color:#AF0E25; font-family:Arial,Helvetica,sans-serif; font-size:xx-small;}
|
||||
.fspintro{color:#666666; font-family:Arial,Helvetica,sans-serif; font-size:xx-small;}
|
||||
.fspchannelhome{font-family:Arial,Helvetica,sans-serif; font-size:x-small;}
|
||||
.fspphotocredit{color:##999999; font-family:Arial,Helvetica,sans-serif; font-size:xx-small;}
|
||||
'''
|
||||
keep_only_tags = [
|
||||
dict(name='div', attrs={'id':["ctl00_cphpagemiddle_reparticle_ctl00_divfullstorytext","ctl00_cphpagemiddle_reparticle_ctl00_divartpic","ctl00_cphpagemiddle_reparticle_ctl00_divfspheading", "ctl00_cphpagemiddle_reparticle_ctl00_divartpiccaption", "ctl00_cphpagemiddle_reparticle_ctl00_divartpiccredit","ctl00_cphpagemiddle_reparticle_ctl00_divfspintro", "ctl00_cphpagemiddle_reparticle_ctl00_divartbyline", "ctl00_cphpagemiddle_divglitteratiregulars","ctl00_cphpagemiddle_divcartoon","feedbackslatestfirst","ctl00_cphpagemiddle_divregulars","ctl00_cphpagemiddle_divquotes"]}),
|
||||
]
|
||||
remove_tags = [dict(name=['script','object','hr']),]
|
||||
|
||||
def get_browser(self):
|
||||
br = BasicNewsRecipe.get_browser(self)
|
||||
# This site sends article titles in the cookie which occasionally
|
||||
# contain non ascii characters causing httplib to fail. Instead just
|
||||
# disable cookies as they're not needed for download. Proper solution
|
||||
# would be to implement a unicode aware cookie jar
|
||||
br.set_cookiejar(None)
|
||||
return br
|
||||
|
||||
def parse_index(self):
|
||||
|
||||
|
||||
soup = self.index_to_soup('http://www.outlookindia.com/issues.aspx')
|
||||
# find cover pic
|
||||
div = soup.find('div', attrs={'class':re.compile('cententcellpadding')})
|
||||
|
||||
if div is None: return None
|
||||
a = div.find('a')
|
||||
|
||||
if a is not None:
|
||||
href = 'http://www.outlookindia.com/' + a['href']
|
||||
|
||||
soup = self.index_to_soup(href)
|
||||
cover = soup.find('img', attrs={'id':"ctl00_cphpagemiddle_dlissues_ctl00_imgcoverpic"}, src=True)
|
||||
if cover is not None:
|
||||
|
||||
self.cover_url = cover['src']
|
||||
|
||||
# end find cover pic
|
||||
#find current issue
|
||||
div = soup.find('table', attrs={'id':re.compile('ctl00_cphpagemiddle_dlissues')})
|
||||
|
||||
if div is None: return None
|
||||
a = div.find('a')
|
||||
|
||||
if a is not None:
|
||||
href = 'http://www.outlookindia.com/' + a['href']
|
||||
|
||||
soup = self.index_to_soup(href)
|
||||
#find current issue
|
||||
|
||||
#find the articles in the current issue
|
||||
articles = []
|
||||
|
||||
for a in soup.findAll('a', attrs={'class':['contentpgsubheadinglink',"contentpgtext6",]}):
|
||||
|
||||
if a and a.has_key('href'):
|
||||
|
||||
url = 'http://www.outlookindia.com/' + a['href']
|
||||
else:
|
||||
url =''
|
||||
|
||||
title = self.tag_to_string(a)
|
||||
|
||||
desc = ''
|
||||
date = ''
|
||||
articles.append({
|
||||
'title':title,
|
||||
'date':date,
|
||||
'url':url,
|
||||
'description':desc,
|
||||
})
|
||||
for a in soup.findAll('a', attrs={'id':["ctl00_cphpageleft_hlglitterati","ctl00_cphpageleft_hlposcape",]}):
|
||||
|
||||
if a and a.has_key('href'):
|
||||
|
||||
url = 'http://www.outlookindia.com/' + a['href']
|
||||
else:
|
||||
url =''
|
||||
|
||||
title = self.tag_to_string(a)
|
||||
|
||||
desc = ''
|
||||
date = ''
|
||||
articles.append({
|
||||
'title':title,
|
||||
'date':date,
|
||||
'url':url,
|
||||
'description':desc,
|
||||
})
|
||||
|
||||
|
||||
return [('Current Issue', articles)]
|
||||
|
||||
def preprocess_html(self, soup):
|
||||
for item in soup.findAll(style=True):
|
||||
del item['style']
|
||||
return self.adeify_images(soup)
|
||||
|
||||
|
||||
|
||||
def postrocess_html(self, soup, first):
|
||||
|
||||
for item in soup.findAll(align = "left"):
|
||||
del item['align']
|
||||
|
||||
for tag in soup.findAll(name=['table', 'tr','td','tbody','ul','li','font','span']):
|
||||
tag.name = 'div'
|
||||
|
||||
return soup
|
||||
|
||||
def postprocess_html(self, soup, first_fetch):
|
||||
bad = []
|
||||
for table in soup.findAll('table'):
|
||||
if table.find(text=re.compile(r'\(\d+ of \d+\)')):
|
||||
bad.append(table)
|
||||
for b in bad:
|
||||
b.extract()
|
||||
soup = soup.findAll('html')[0]
|
||||
for t in soup.findAll(['table', 'tr', 'td']):
|
||||
t.name = 'div'
|
||||
return soup
|
||||
|
||||
|
@ -10,11 +10,11 @@ from calibre import strftime
|
||||
from calibre.web.feeds.news import BasicNewsRecipe
|
||||
|
||||
class Pagina12(BasicNewsRecipe):
|
||||
title = 'Pagina/12'
|
||||
title = 'Pagina - 12'
|
||||
__author__ = 'Darko Miletic'
|
||||
description = 'Noticias de Argentina y el resto del mundo'
|
||||
publisher = 'La Pagina S.A.'
|
||||
category = 'news, politics, Argentina'
|
||||
category = 'news, politics, Argentina'
|
||||
oldest_article = 2
|
||||
max_articles_per_feed = 100
|
||||
no_stylesheets = True
|
||||
@ -22,16 +22,16 @@ class Pagina12(BasicNewsRecipe):
|
||||
cover_url = strftime('http://www.pagina12.com.ar/fotos/%Y%m%d/diario/tapagn.jpg')
|
||||
remove_javascript = True
|
||||
use_embedded_content = False
|
||||
language = _('Spanish')
|
||||
|
||||
|
||||
language = _('Spanish')
|
||||
|
||||
|
||||
html2lrf_options = [
|
||||
'--comment', description
|
||||
, '--category', category
|
||||
, '--publisher', publisher
|
||||
]
|
||||
|
||||
html2epub_options = 'publisher="' + publisher + '"\ncomments="' + description + '"\ntags="' + category + '"'
|
||||
|
||||
html2epub_options = 'publisher="' + publisher + '"\ncomments="' + description + '"\ntags="' + category + '"'
|
||||
|
||||
|
||||
remove_tags = [
|
||||
@ -39,7 +39,7 @@ class Pagina12(BasicNewsRecipe):
|
||||
,dict(name='div', attrs={'id':'logo' })
|
||||
]
|
||||
|
||||
|
||||
|
||||
feeds = [(u'Pagina/12', u'http://www.pagina12.com.ar/diario/rss/principal.xml')]
|
||||
|
||||
def print_version(self, url):
|
||||
@ -47,7 +47,7 @@ class Pagina12(BasicNewsRecipe):
|
||||
|
||||
def preprocess_html(self, soup):
|
||||
mtag = '<meta http-equiv="Content-Language" content="es-AR"/>'
|
||||
soup.head.insert(0,mtag)
|
||||
soup.head.insert(0,mtag)
|
||||
for item in soup.findAll(style=True):
|
||||
del item['style']
|
||||
return soup
|
||||
|
@ -16,7 +16,7 @@ class PeriodicalNameHere(BasicNewsRecipe):
|
||||
title = 'Slate'
|
||||
description = 'A general-interest publication offering analysis and commentary about politics, news and culture.'
|
||||
__author__ = 'GRiker'
|
||||
max_articles_per_feed = 40
|
||||
max_articles_per_feed = 20
|
||||
oldest_article = 7.0
|
||||
recursions = 0
|
||||
delay = 0
|
||||
@ -106,11 +106,15 @@ class PeriodicalNameHere(BasicNewsRecipe):
|
||||
older_section_dates = soup.findAll(True, attrs={'class':'maindateline'})
|
||||
for older_section in older_section_dates :
|
||||
self.section_dates.append(self.tag_to_string(older_section,use_alt=False))
|
||||
|
||||
headline_stories = soup_top_stories.find('ul')
|
||||
|
||||
if soup_top_stories:
|
||||
headline_stories = soup_top_stories.find('ul')
|
||||
else:
|
||||
headline_stories = None
|
||||
section_lists = soup.findAll('ul')
|
||||
# Prepend the headlines to the first section
|
||||
section_lists[0].insert(0,headline_stories)
|
||||
if headline_stories:
|
||||
section_lists[0].insert(0,headline_stories)
|
||||
|
||||
sections = []
|
||||
for section in section_lists :
|
||||
@ -290,7 +294,8 @@ class PeriodicalNameHere(BasicNewsRecipe):
|
||||
excluded = re.compile('|'.join(self.excludedContentKeywords))
|
||||
found_excluded = excluded.search(str(soup))
|
||||
if found_excluded :
|
||||
return None
|
||||
print "no allowed content found, removing article"
|
||||
raise StringError
|
||||
|
||||
# Articles from www.thebigmoney.com use different tagging for byline, dateline and body
|
||||
head = soup.find('head')
|
||||
@ -423,4 +428,3 @@ class PeriodicalNameHere(BasicNewsRecipe):
|
||||
if article.description is None :
|
||||
article.description = extract_description(article.href)
|
||||
|
||||
|
64
src/calibre/web/feeds/recipes/recipe_thedgesingapore.py
Normal file
64
src/calibre/web/feeds/recipes/recipe_thedgesingapore.py
Normal file
@ -0,0 +1,64 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2009, Darko Miletic <darko.miletic at gmail.com>'
|
||||
'''
|
||||
www.livemint.com
|
||||
'''
|
||||
|
||||
from calibre.web.feeds.news import BasicNewsRecipe
|
||||
|
||||
class Edgesingapore(BasicNewsRecipe):
|
||||
title = 'The Edge Singapore'
|
||||
__author__ = 'Darko Miletic'
|
||||
description = 'Financial news from Singapore'
|
||||
publisher = 'The Edge Singapore'
|
||||
category = 'news, finances, singapore'
|
||||
language = _('English')
|
||||
lang = 'en'
|
||||
oldest_article = 15
|
||||
max_articles_per_feed = 100
|
||||
no_stylesheets = True
|
||||
encoding = 'utf-8'
|
||||
use_embedded_content = False
|
||||
extra_css = ' .contentheading{font-size: x-large} .small{font-size: small} .createdate{font-size: small; font-weight: bold} '
|
||||
|
||||
|
||||
conversion_options = {
|
||||
'comment' : description
|
||||
, 'tags' : category
|
||||
, 'publisher' : publisher
|
||||
, 'author' : publisher
|
||||
, 'language' : lang
|
||||
, 'pretty_print' : True
|
||||
, 'linearize_tables' : True
|
||||
}
|
||||
|
||||
|
||||
remove_tags = [
|
||||
dict(name=['object','link','embed','form','iframe'])
|
||||
,dict(name='div',attrs={'id':'toolbar-article'})
|
||||
,dict(name='div',attrs={'class':'backtotop'})
|
||||
,dict(name='img',attrs={'alt':'Print'})
|
||||
]
|
||||
|
||||
remove_tags_after = dict(name='div',attrs={'class':'backtotop'})
|
||||
|
||||
feeds = [(u'Articles', u'http://feeds.feedburner.com/edgesg')]
|
||||
|
||||
def print_version(self, url):
|
||||
return url + '?tmpl=component&print=1'
|
||||
|
||||
def preprocess_html(self, soup):
|
||||
attribs = [ 'style','font','valign'
|
||||
,'colspan','width','height'
|
||||
,'rowspan','summary','align'
|
||||
,'cellspacing','cellpadding'
|
||||
,'frames','rules','border'
|
||||
]
|
||||
for item in soup.body.findAll(name=['table','td','tr','th','caption','thead','tfoot','tbody','colgroup','col']):
|
||||
item.name = 'div'
|
||||
for attrib in attribs:
|
||||
if item.has_key(attrib):
|
||||
del item[attrib]
|
||||
return self.adeify_images(soup)
|
122
upload.py
122
upload.py
@ -4,7 +4,7 @@ __copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
import shutil, os, glob, re, cStringIO, sys, tempfile, time, textwrap, socket, \
|
||||
struct, subprocess
|
||||
struct, subprocess, platform
|
||||
from datetime import datetime
|
||||
from setuptools.command.build_py import build_py as _build_py, convert_path
|
||||
from distutils.core import Command
|
||||
@ -24,6 +24,7 @@ HTML2LRF = "src/calibre/ebooks/lrf/html/demo"
|
||||
TXT2LRF = "src/calibre/ebooks/lrf/txt/demo"
|
||||
MOBILEREAD = 'ftp://dev.mobileread.com/calibre/'
|
||||
|
||||
is64bit = platform.architecture()[0] == '64bit'
|
||||
|
||||
def get_ip_address(ifname):
|
||||
import fcntl
|
||||
@ -35,7 +36,7 @@ def get_ip_address(ifname):
|
||||
)[20:24])
|
||||
|
||||
try:
|
||||
HOST=get_ip_address('eth0')
|
||||
HOST=get_ip_address('br0')
|
||||
except:
|
||||
try:
|
||||
HOST=get_ip_address('wlan0')
|
||||
@ -481,12 +482,12 @@ class upload_demo(OptionlessCommand):
|
||||
def installer_name(ext):
|
||||
if ext in ('exe', 'dmg'):
|
||||
return 'dist/%s-%s.%s'%(__appname__, __version__, ext)
|
||||
return 'dist/%s-%s-i686.%s'%(__appname__, __version__, ext)
|
||||
ans = 'dist/%s-%s-i686.%s'%(__appname__, __version__, ext)
|
||||
if is64bit:
|
||||
ans = ans.replace('i686', 'x86_64')
|
||||
return ans
|
||||
|
||||
|
||||
class build_linux(OptionlessCommand):
|
||||
description = 'Build linux installer'
|
||||
def run(self):
|
||||
def _build_linux():
|
||||
installer = installer_name('tar.bz2')
|
||||
locals = {}
|
||||
exec open('installer/linux/freeze.py') in locals
|
||||
@ -495,9 +496,15 @@ class build_linux(OptionlessCommand):
|
||||
raise Exception('Failed to build installer '+installer)
|
||||
return os.path.basename(installer)
|
||||
|
||||
class build_linux64(OptionlessCommand):
|
||||
description = 'Build linux 64bit installer'
|
||||
|
||||
def run(self):
|
||||
return _build_linux()
|
||||
|
||||
class VMInstaller(OptionlessCommand):
|
||||
|
||||
user_options = [('dont-shutdown', 'd', 'Dont shutdown Vm after build')]
|
||||
user_options = [('dont-shutdown', 'd', 'Dont shutdown VM after build')]
|
||||
boolean_options = ['dont-shutdown']
|
||||
|
||||
def initialize_options(self):
|
||||
@ -521,10 +528,34 @@ class VMInstaller(OptionlessCommand):
|
||||
def get_build_script(self, subs):
|
||||
return self.BUILD_SCRIPT%subs
|
||||
|
||||
def start_vm(self, ssh_host, build_script, sleep=75):
|
||||
build_script = self.get_build_script(build_script)
|
||||
def vmware_started(self):
|
||||
return 'started' in subprocess.Popen('/etc/init.d/vmware status', shell=True, stdout=subprocess.PIPE).stdout.read()
|
||||
|
||||
def start_vmware(self):
|
||||
if not self.vmware_started():
|
||||
if os.path.exists('/dev/kvm'):
|
||||
check_call('sudo rmmod -w kvm-intel kvm', shell=True)
|
||||
subprocess.Popen('sudo /etc/init.d/vmware start', shell=True)
|
||||
|
||||
def stop_vmware(self):
|
||||
while True:
|
||||
try:
|
||||
check_call('sudo /etc/init.d/vmware stop', shell=True)
|
||||
break
|
||||
except:
|
||||
pass
|
||||
while 'vmblock' in open('/proc/modules').read():
|
||||
check_call('sudo rmmod -f vmblock')
|
||||
check_call('sudo modprobe kvm-intel', shell=True)
|
||||
|
||||
|
||||
def run_vm(self):
|
||||
vmware = ('vmware', '-q', '-x', '-n', self.VM)
|
||||
Popen(vmware)
|
||||
self.__p = Popen(vmware)
|
||||
|
||||
def start_vm(self, ssh_host, build_script, sleep=75):
|
||||
self.run_vm()
|
||||
build_script = self.get_build_script(build_script)
|
||||
t = tempfile.NamedTemporaryFile(suffix='.sh')
|
||||
t.write(build_script)
|
||||
t.flush()
|
||||
@ -537,26 +568,55 @@ class VMInstaller(OptionlessCommand):
|
||||
check_call(('scp', t.name, ssh_host+':build-calibre'))
|
||||
check_call('ssh -t %s bash build-calibre'%ssh_host, shell=True)
|
||||
|
||||
class build_windows(VMInstaller):
|
||||
class KVMInstaller(VMInstaller):
|
||||
|
||||
def run_vm(self):
|
||||
self.stop_vmware()
|
||||
self.__p = Popen(self.VM)
|
||||
|
||||
|
||||
|
||||
class build_linux32(KVMInstaller):
|
||||
|
||||
description = 'Build linux 32bit installer'
|
||||
VM = '/vmware/bin/linux_build'
|
||||
|
||||
def run_vm(self):
|
||||
self.__p = Popen('/vmware/bin/linux_build')
|
||||
|
||||
def run(self):
|
||||
if is64bit:
|
||||
installer = installer_name('tar.bz2').replace('x86_64', 'i686')
|
||||
self.start_vm('linux_build', ('python setup.py build_ext',
|
||||
'python', 'setup.py build_linux32'))
|
||||
check_call(('scp', 'linux_build:build/calibre/dist/*.tar.bz2', 'dist'))
|
||||
if not os.path.exists(installer):
|
||||
raise Exception('Failed to build installer '+installer)
|
||||
if not self.dont_shutdown:
|
||||
Popen(('ssh', 'linux_build', 'sudo', '/sbin/poweroff'))
|
||||
return os.path.basename(installer)
|
||||
else:
|
||||
return _build_linux()
|
||||
|
||||
|
||||
class build_windows(KVMInstaller):
|
||||
description = 'Build windows installer'
|
||||
VM = '/mnt/backup/calibre_windows_xp_home/calibre_windows_xp_home.vmx'
|
||||
if not os.path.exists(VM):
|
||||
VM = '/home/kovid/calibre_windows_xp_home/calibre_windows_xp_home.vmx'
|
||||
VM = '/vmware/bin/win_build'
|
||||
|
||||
def run(self):
|
||||
installer = installer_name('exe')
|
||||
self.start_vm('windows', ('python setup.py develop',
|
||||
self.start_vm('win_build', ('python setup.py develop',
|
||||
'python',
|
||||
r'installer\\windows\\freeze.py'))
|
||||
if os.path.exists('build/py2exe'):
|
||||
shutil.rmtree('build/py2exe')
|
||||
check_call(('scp', '-rp', 'windows:build/%s/build/py2exe'%__appname__,
|
||||
check_call(('scp', '-rp', 'win_build:build/%s/build/py2exe'%__appname__,
|
||||
'build'))
|
||||
if not os.path.exists('build/py2exe'):
|
||||
raise Exception('Failed to run py2exe')
|
||||
if not self.dont_shutdown:
|
||||
Popen(('ssh', 'windows', 'shutdown', '-s', '-t', '0'))
|
||||
self.run_windows_install_jammer(installer)
|
||||
if not self.dont_shutdown:
|
||||
Popen(('ssh', 'win_build', 'shutdown', '-s', '-t', '0'))
|
||||
return os.path.basename(installer)
|
||||
|
||||
@classmethod
|
||||
@ -573,9 +633,7 @@ class build_windows(VMInstaller):
|
||||
|
||||
class build_osx(VMInstaller):
|
||||
description = 'Build OS X app bundle'
|
||||
VM = '/mnt/backup/calibre_os_x/Mac OSX.vmx'
|
||||
if not os.path.exists(VM):
|
||||
VM = '/home/kovid/calibre_os_x/Mac OSX.vmx'
|
||||
VM = '/vmware/calibre_os_x/Mac OSX.vmx'
|
||||
|
||||
def get_build_script(self, subs):
|
||||
return (self.BUILD_SCRIPT%subs).replace('rm ', 'sudo rm ')
|
||||
@ -583,17 +641,18 @@ class build_osx(VMInstaller):
|
||||
def run(self):
|
||||
installer = installer_name('dmg')
|
||||
python = '/Library/Frameworks/Python.framework/Versions/Current/bin/python'
|
||||
self.start_vm('osx', ('sudo %s setup.py develop'%python, python,
|
||||
self.start_vmware()
|
||||
self.start_vm('osx_build', ('sudo %s setup.py develop'%python, python,
|
||||
'installer/osx/freeze.py'))
|
||||
check_call(('scp', 'osx:build/calibre/dist/*.dmg', 'dist'))
|
||||
check_call(('scp', 'osx_build:build/calibre/dist/*.dmg', 'dist'))
|
||||
if not os.path.exists(installer):
|
||||
raise Exception('Failed to build installer '+installer)
|
||||
if not self.dont_shutdown:
|
||||
Popen(('ssh', 'osx', 'sudo', '/sbin/shutdown', '-h', 'now'))
|
||||
Popen(('ssh', 'osx_build', 'sudo', '/sbin/shutdown', '-h', 'now'))
|
||||
time.sleep(20)
|
||||
self.stop_vmware()
|
||||
return os.path.basename(installer)
|
||||
|
||||
|
||||
|
||||
class upload_installers(OptionlessCommand):
|
||||
description = 'Upload any installers present in dist/'
|
||||
def curl_list_dir(self, url=MOBILEREAD, listonly=1):
|
||||
@ -661,8 +720,9 @@ class upload_installers(OptionlessCommand):
|
||||
|
||||
def run(self):
|
||||
print 'Uploading installers...'
|
||||
for i in ('dmg', 'exe', 'tar.bz2'):
|
||||
self.upload_installer(installer_name(i))
|
||||
installers = list(map(installer_name, ('dmg', 'exe', 'tar.bz2')))
|
||||
installers.append(installers[-1].replace('x86_64', 'i686'))
|
||||
map(self.upload_installer, installers)
|
||||
|
||||
check_call('''ssh divok echo %s \\> %s/latest_version'''\
|
||||
%(__version__, DOWNLOADS), shell=True)
|
||||
@ -715,6 +775,10 @@ class stage3(OptionlessCommand):
|
||||
OptionlessCommand.run(self)
|
||||
self.misc()
|
||||
|
||||
class build_linux(OptionlessCommand):
|
||||
description = 'Build linux installers'
|
||||
sub_commands = [ ('build_linux64', None), ('build_linux32', None) ]
|
||||
|
||||
class stage2(OptionlessCommand):
|
||||
description = 'Stage 2 of the build process'
|
||||
sub_commands = [
|
||||
|
Loading…
x
Reference in New Issue
Block a user