Sync to pluginize

This commit is contained in:
John Schember 2009-05-12 18:30:35 -04:00
commit 8577e979aa
24 changed files with 715 additions and 105 deletions

View File

@ -31,6 +31,7 @@ def freeze():
'/usr/lib/libsqlite3.so.0', '/usr/lib/libsqlite3.so.0',
'/usr/lib/libsqlite3.so.0', '/usr/lib/libsqlite3.so.0',
'/usr/lib/libmng.so.1', '/usr/lib/libmng.so.1',
'/usr/lib/libpodofo.so.0.6.99',
'/lib/libz.so.1', '/lib/libz.so.1',
'/lib/libbz2.so.1', '/lib/libbz2.so.1',
'/lib/libbz2.so.1', '/lib/libbz2.so.1',

View File

@ -229,6 +229,11 @@ _check_symlinks_prescript()
all_modules = main_modules['console'] + main_modules['gui'] all_modules = main_modules['console'] + main_modules['gui']
all_functions = main_functions['console'] + main_functions['gui'] all_functions = main_functions['console'] + main_functions['gui']
print print
print 'Adding PoDoFo'
pdf = glob.glob(os.path.expanduser('~/podofo/*.dylib'))[0]
shutil.copyfile(pdf, os.path.join(frameworks_dir, os.path.basename(pdf)))
loader_path = os.path.join(resource_dir, 'loaders') loader_path = os.path.join(resource_dir, 'loaders')
if not os.path.exists(loader_path): if not os.path.exists(loader_path):
os.mkdir(loader_path) os.mkdir(loader_path)

View File

@ -12,6 +12,7 @@ LIBUNRAR = 'C:\\Program Files\\UnrarDLL\\unrar.dll'
PDFTOHTML = 'C:\\cygwin\\home\\kovid\\poppler-0.10.6\\rel\\pdftohtml.exe' PDFTOHTML = 'C:\\cygwin\\home\\kovid\\poppler-0.10.6\\rel\\pdftohtml.exe'
IMAGEMAGICK_DIR = 'C:\\ImageMagick' IMAGEMAGICK_DIR = 'C:\\ImageMagick'
PDFTK = 'C:\\pdftk.exe' PDFTK = 'C:\\pdftk.exe'
PODOFO = 'C:\\podofo'
FONTCONFIG_DIR = 'C:\\fontconfig' FONTCONFIG_DIR = 'C:\\fontconfig'
VC90 = r'C:\VC90.CRT' VC90 = r'C:\VC90.CRT'
@ -101,8 +102,11 @@ class BuildEXE(py2exe.build_exe.py2exe):
shutil.copyfile(PDFTOHTML, os.path.join(PY2EXE_DIR, os.path.basename(PDFTOHTML))) shutil.copyfile(PDFTOHTML, os.path.join(PY2EXE_DIR, os.path.basename(PDFTOHTML)))
shutil.copyfile(PDFTOHTML+'.manifest', os.path.join(PY2EXE_DIR, shutil.copyfile(PDFTOHTML+'.manifest', os.path.join(PY2EXE_DIR,
os.path.basename(PDFTOHTML)+'.manifest')) os.path.basename(PDFTOHTML)+'.manifest'))
print '\tAdding pdftk' #print '\tAdding pdftk'
shutil.copyfile(PDFTK, os.path.join(PY2EXE_DIR, os.path.basename(PDFTK))) #shutil.copyfile(PDFTK, os.path.join(PY2EXE_DIR, os.path.basename(PDFTK)))
print 'Adding 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 ImageMagick' print '\tAdding ImageMagick'
for f in os.listdir(IMAGEMAGICK_DIR): for f in os.listdir(IMAGEMAGICK_DIR):

View File

@ -80,13 +80,16 @@ CONFIG += x86 ppc
os.chdir(cwd) os.chdir(cwd)
def build_sbf(self, sip, sbf, bdir): def build_sbf(self, sip, sbf, bdir):
print '\tBuilding spf...' print '\tBuilding sbf...'
sip_bin = self.sipcfg.sip_bin sip_bin = self.sipcfg.sip_bin
pyqt_sip_flags = []
if hasattr(self, 'pyqtcfg'):
pyqt_sip_flags += ['-I', self.pyqtcfg.pyqt_sip_dir]
pyqt_sip_flags += self.pyqtcfg.pyqt_sip_flags.split()
self.spawn([sip_bin, self.spawn([sip_bin,
"-c", bdir, "-c", bdir,
"-b", sbf, "-b", sbf,
'-I', self.pyqtcfg.pyqt_sip_dir, ] + pyqt_sip_flags +
] + self.pyqtcfg.pyqt_sip_flags.split()+
[sip]) [sip])
def build_pyqt(self, bdir, sbf, ext, qtobjs, headers): def build_pyqt(self, bdir, sbf, ext, qtobjs, headers):
@ -94,9 +97,14 @@ CONFIG += x86 ppc
build_file=sbf, dir=bdir, build_file=sbf, dir=bdir,
makefile='Makefile.pyqt', makefile='Makefile.pyqt',
universal=OSX_SDK, qt=1) universal=OSX_SDK, qt=1)
makefile.extra_libs = ext.libraries
makefile.extra_lib_dirs = ext.library_dirs
makefile.extra_cxxflags = ext.extra_compile_args
if 'win32' in sys.platform: if 'win32' in sys.platform:
makefile.extra_lib_dirs += WINDOWS_PYTHON makefile.extra_lib_dirs += WINDOWS_PYTHON
makefile.extra_include_dirs = list(set(map(os.path.dirname, headers))) makefile.extra_include_dirs = list(set(map(os.path.dirname, headers)))
makefile.extra_include_dirs += ext.include_dirs
makefile.extra_lflags += qtobjs makefile.extra_lflags += qtobjs
makefile.generate() makefile.generate()
cwd = os.getcwd() cwd = os.getcwd()
@ -110,7 +118,7 @@ CONFIG += x86 ppc
def build_extension(self, ext): def build_extension(self, ext):
self.inplace = True # Causes extensions to be built in the source tree self.inplace = True # Causes extensions to be built in the source tree
fullname = self.get_ext_fullname(ext.name) fullname = self.get_ext_fullname(ext.name)
if self.inplace: if self.inplace:
# ignore build-lib -- put the compiled extension into # ignore build-lib -- put the compiled extension into
@ -127,14 +135,14 @@ CONFIG += x86 ppc
else: else:
ext_filename = os.path.join(self.build_lib, ext_filename = os.path.join(self.build_lib,
self.get_ext_filename(fullname)) self.get_ext_filename(fullname))
bdir = os.path.abspath(os.path.join(self.build_temp, fullname)) bdir = os.path.abspath(os.path.join(self.build_temp, fullname))
if not os.path.exists(bdir): if not os.path.exists(bdir):
os.makedirs(bdir) os.makedirs(bdir)
if not isinstance(ext, PyQtExtension): if not isinstance(ext, PyQtExtension):
if not iswindows: if not iswindows:
return _build_ext.build_extension(self, ext) return _build_ext.build_extension(self, ext)
c_sources = [f for f in ext.sources if os.path.splitext(f)[1].lower() in ('.c', '.cpp', '.cxx')] c_sources = [f for f in ext.sources if os.path.splitext(f)[1].lower() in ('.c', '.cpp', '.cxx')]
compile_args = '/c /nologo /Ox /MD /W3 /GX /DNDEBUG'.split() compile_args = '/c /nologo /Ox /MD /W3 /GX /DNDEBUG'.split()
compile_args += ext.extra_compile_args compile_args += ext.extra_compile_args
@ -147,7 +155,7 @@ CONFIG += x86 ppc
objects.append(o) objects.append(o)
compiler = cc + ['/Tc'+f, '/Fo'+o] compiler = cc + ['/Tc'+f, '/Fo'+o]
self.spawn(compiler) self.spawn(compiler)
out = os.path.join(bdir, base+'.pyd') out = os.path.join(bdir, base+'.pyd')
linker = [msvc.linker] + '/DLL /nologo /INCREMENTAL:NO'.split() linker = [msvc.linker] + '/DLL /nologo /INCREMENTAL:NO'.split()
linker += ['/LIBPATH:'+x for x in self.library_dirs] linker += ['/LIBPATH:'+x for x in self.library_dirs]
linker += [x+'.lib' for x in ext.libraries] linker += [x+'.lib' for x in ext.libraries]
@ -156,9 +164,9 @@ CONFIG += x86 ppc
for src in (out, out+'.manifest'): for src in (out, out+'.manifest'):
shutil.copyfile(src, os.path.join('src', 'calibre', 'plugins', os.path.basename(src))) shutil.copyfile(src, os.path.join('src', 'calibre', 'plugins', os.path.basename(src)))
return return
if not os.path.exists(bdir): if not os.path.exists(bdir):
os.makedirs(bdir) os.makedirs(bdir)
ext.sources2 = map(os.path.abspath, ext.sources) ext.sources2 = map(os.path.abspath, ext.sources)
@ -200,6 +208,14 @@ CONFIG += x86 ppc
shutil.copyfile(mod, ext_filename) shutil.copyfile(mod, ext_filename)
shutil.copymode(mod, ext_filename) shutil.copymode(mod, ext_filename)
if self.force or newer_group([mod], ext_filename, 'newer'):
if os.path.exists(ext_filename):
os.unlink(ext_filename)
shutil.copyfile(mod, ext_filename)
shutil.copymode(mod, ext_filename)
def get_sip_output_list(self, sbf, bdir): def get_sip_output_list(self, sbf, bdir):
""" """
Parse the sbf file specified to extract the name of the generated source Parse the sbf file specified to extract the name of the generated source

View File

@ -57,7 +57,25 @@ if __name__ == '__main__':
entry_points['console_scripts'].append( entry_points['console_scripts'].append(
'calibre_postinstall = calibre.linux:post_install') 'calibre_postinstall = calibre.linux:post_install')
ext_modules = [ optional = []
podofo_inc = '/usr/include/podofo' if islinux else \
'C:\\podofo\\include\\podofo' if iswindows else \
'/Users/kovid/podofo/include/podofo'
podofo_lib = '/usr/lib' if islinux else r'C:\podofo' if iswindows else \
'/Users/kovid/podofo/lib'
if os.path.exists(os.path.join(podofo_inc, 'PdfString.h')):
eca = ['/EHsc'] if iswindows else []
optional.append(PyQtExtension('calibre.plugins.podofo', [],
['src/calibre/utils/podofo/podofo.sip'],
libraries=['podofo'], extra_compile_args=eca,
library_dirs=[os.environ.get('PODOFO_LIB_DIR', podofo_lib)],
include_dirs=\
[os.environ.get('PODOFO_INC_DIR', podofo_inc)]))
ext_modules = optional + [
Extension('calibre.plugins.lzx', Extension('calibre.plugins.lzx',
sources=['src/calibre/utils/lzx/lzxmodule.c', sources=['src/calibre/utils/lzx/lzxmodule.c',
'src/calibre/utils/lzx/compressor.c', 'src/calibre/utils/lzx/compressor.c',

View File

@ -53,7 +53,7 @@ if plugins is None:
plugin_path = getattr(pkg_resources, 'resource_filename')('calibre', 'plugins') plugin_path = getattr(pkg_resources, 'resource_filename')('calibre', 'plugins')
sys.path.insert(0, plugin_path) sys.path.insert(0, plugin_path)
for plugin in ['pictureflow', 'lzx', 'msdes', 'cPalmdoc'] + \ for plugin in ['pictureflow', 'lzx', 'msdes', 'podofo', 'cPalmdoc'] + \
(['winutil'] if iswindows else []) + \ (['winutil'] if iswindows else []) + \
(['usbobserver'] if isosx else []): (['usbobserver'] if isosx else []):
try: try:

View File

@ -20,9 +20,9 @@ class BEBOOK(USBMS):
PRODUCT_ID = [0x8803, 0x6803] PRODUCT_ID = [0x8803, 0x6803]
BCD = [0x312] BCD = [0x312]
VENDOR_NAME = 'BEBOOK' VENDOR_NAME = 'LINUX'
WINDOWS_MAIN_MEM = 'BEBOOK_INTERNAL_MEMORY' WINDOWS_MAIN_MEM = 'FILE-STOR_GADGET'
WINDOWS_CARD_A_MEM = 'BEBOOK_STORAGE_CARD' WINDOWS_CARD_MEM = 'FILE-STOR_GADGET'
OSX_MAIN_MEM = 'BeBook Internal Memory' OSX_MAIN_MEM = 'BeBook Internal Memory'
OSX_CARD_A_MEM = 'BeBook Storage Card' OSX_CARD_A_MEM = 'BeBook Storage Card'
@ -34,17 +34,25 @@ class BEBOOK(USBMS):
FDI_LUNS = {'lun0':1, 'lun1':0, 'lun2':2} FDI_LUNS = {'lun0':1, 'lun1':0, 'lun2':2}
def windows_sort_drives(self, drives):
main = drives.get('main', None)
card = drives.get('carda', None)
if card and main and card < main:
drives['main'] = card
drives['carda'] = main
return drives
class BEBOOK_MINI(BEBOOK): class BEBOOK_MINI(BEBOOK):
name = 'BeBook Mini driver' name = 'BeBook Mini driver'
description = _('Communicate with the BeBook Mini eBook reader.') description = _('Communicate with the BeBook Mini eBook reader.')
VENDOR_ID = [0x0492] VENDOR_ID = [0x0492]
PRODUCT_ID = [0x8813] PRODUCT_ID = [0x8813]
BCD = [0x319] BCD = [0x319]
WINDOWS_MAIN_MEM = 'BEBOOKMINI_INTERNAL_MEMORY'
WINDOWS_CARD_MEM = 'BEBOOKMINI_STORAGE_CARD'
OSX_MAIN_MEM = 'BeBook Mini Internal Memory' OSX_MAIN_MEM = 'BeBook Mini Internal Memory'
OSX_CARD_MEM = 'BeBook Mini Storage Card' OSX_CARD_MEM = 'BeBook Mini Storage Card'

View File

@ -43,7 +43,7 @@ class EB600(USBMS):
def windows_sort_drives(self, drives): def windows_sort_drives(self, drives):
main = drives.get('main', None) main = drives.get('main', None)
card = drives.get('card', None) card = drives.get('carda', None)
if card and main and card < main: if card and main and card < main:
drives['main'] = card drives['main'] = card
drives['carda'] = main drives['carda'] = main

View File

@ -76,7 +76,7 @@ class JETBOOK(USBMS):
if newpath == path: if newpath == path:
newpath = os.path.join(newpath, author, title) newpath = os.path.join(newpath, author, title)
if not os.path.exists(newpath): if not os.path.exists(newpath):
os.makedirs(newpath) os.makedirs(newpath)
@ -97,7 +97,7 @@ class JETBOOK(USBMS):
self.report_progress((i+1) / float(len(files)), _('Transferring books to device...')) self.report_progress((i+1) / float(len(files)), _('Transferring books to device...'))
self.report_progress(1.0, _('Transferring books to device...')) self.report_progress(1.0, _('Transferring books to device...'))
return zip(paths, cycle([on_card])) return zip(paths, cycle([on_card]))
@classmethod @classmethod
@ -109,7 +109,7 @@ class JETBOOK(USBMS):
return txt.decode(sys.getfilesystemencoding(), 'replace') return txt.decode(sys.getfilesystemencoding(), 'replace')
return txt return txt
from calibre.devices.usbms.driver import metadata_from_formats from calibre.devices.usbms.driver import metadata_from_formats
mi = metadata_from_formats([path]) mi = metadata_from_formats([path])
@ -126,10 +126,10 @@ class JETBOOK(USBMS):
def windows_sort_drives(self, drives): def windows_sort_drives(self, drives):
main = drives.get('main', None) main = drives.get('main', None)
card = drives.get('card', None) card = drives.get('carda', None)
if card and main and card < main: if card and main and card < main:
drives['main'] = card drives['main'] = card
drives['card'] = main drives['carda'] = main
return drives return drives

View File

@ -7,9 +7,7 @@ import sys, os, cStringIO
from threading import Thread from threading import Thread
from calibre import StreamReadWrapper from calibre import StreamReadWrapper
from calibre.ebooks.metadata import MetaInformation, authors_to_string
from calibre.ptempfile import TemporaryDirectory from calibre.ptempfile import TemporaryDirectory
from pyPdf import PdfFileReader, PdfFileWriter
try: try:
from calibre.utils.PythonMagickWand import \ from calibre.utils.PythonMagickWand import \
NewMagickWand, MagickReadImage, MagickSetImageFormat, \ NewMagickWand, MagickReadImage, MagickSetImageFormat, \
@ -17,11 +15,17 @@ try:
_imagemagick_loaded = True _imagemagick_loaded = True
except: except:
_imagemagick_loaded = False _imagemagick_loaded = False
from calibre.ebooks.metadata import MetaInformation, authors_to_string
from calibre.utils.pdftk import set_metadata as pdftk_set_metadata from calibre.utils.pdftk import set_metadata as pdftk_set_metadata
from calibre.utils.podofo import get_metadata as podofo_get_metadata, \
set_metadata as podofo_set_metadata
def get_metadata(stream, extract_cover=True): def get_metadata(stream, extract_cover=True):
""" Return metadata as a L{MetaInfo} object """ try:
mi = MetaInformation(_('Unknown'), [_('Unknown')]) mi = podofo_get_metadata(stream)
except:
mi = get_metadata_pypdf(stream)
stream.seek(0) stream.seek(0)
if extract_cover and _imagemagick_loaded: if extract_cover and _imagemagick_loaded:
@ -32,7 +36,26 @@ def get_metadata(stream, extract_cover=True):
except: except:
import traceback import traceback
traceback.print_exc() traceback.print_exc()
return mi
def set_metadata(stream, mi):
stream.seek(0)
try:
return podofo_set_metadata(stream, mi)
except:
pass
try:
return pdftk_set_metadata(stream, mi)
except:
pass
set_metadata_pypdf(stream, mi)
def get_metadata_pypdf(stream):
""" Return metadata as a L{MetaInfo} object """
from pyPdf import PdfFileReader
mi = MetaInformation(_('Unknown'), [_('Unknown')])
try: try:
with StreamReadWrapper(stream) as stream: with StreamReadWrapper(stream) as stream:
info = PdfFileReader(stream).getDocumentInfo() info = PdfFileReader(stream).getDocumentInfo()
@ -66,18 +89,12 @@ class MetadataWriter(Thread):
except RuntimeError: except RuntimeError:
pass pass
def set_metadata(stream, mi): def set_metadata_pypdf(stream, mi):
stream.seek(0)
try:
pdftk_set_metadata(stream, mi)
except:
pass
else:
return
# Use a StringIO object for the pdf because we will want to over # Use a StringIO object for the pdf because we will want to over
# write it later and if we are working on the stream directly it # write it later and if we are working on the stream directly it
# could cause some issues. # could cause some issues.
from pyPdf import PdfFileReader, PdfFileWriter
raw = cStringIO.StringIO(stream.read()) raw = cStringIO.StringIO(stream.read())
orig_pdf = PdfFileReader(raw) orig_pdf = PdfFileReader(raw)
title = mi.title if mi.title else orig_pdf.documentInfo.title title = mi.title if mi.title else orig_pdf.documentInfo.title
@ -88,7 +105,7 @@ def set_metadata(stream, mi):
for page in orig_pdf.pages: for page in orig_pdf.pages:
out_pdf.addPage(page) out_pdf.addPage(page)
writer.start() writer.start()
writer.join(15) # Wait 15 secs for writing to complete writer.join(10) # Wait 10 secs for writing to complete
out_pdf.killed = True out_pdf.killed = True
writer.join() writer.join()
if out_pdf.killed: if out_pdf.killed:
@ -102,6 +119,8 @@ def set_metadata(stream, mi):
stream.seek(0) stream.seek(0)
def get_cover(stream): def get_cover(stream):
from pyPdf import PdfFileReader, PdfFileWriter
try: try:
with StreamReadWrapper(stream) as stream: with StreamReadWrapper(stream) as stream:
pdf = PdfFileReader(stream) pdf = PdfFileReader(stream)

View File

@ -4,12 +4,15 @@ __license__ = 'GPL 3'
__copyright__ = '2009, John Schember <john@nachtimwald.com>' __copyright__ = '2009, John Schember <john@nachtimwald.com>'
__docformat__ = 'restructuredtext en' __docformat__ = 'restructuredtext en'
from calibre.ebooks.conversion.plumber import Plumber from calibre.ebooks.conversion.plumber import Plumber, DummyReporter
from calibre.utils.logging import Log from calibre.utils.logging import Log
from calibre.customize.conversion import OptionRecommendation
def gui_convert(input, output, recommendations, notification): def gui_convert(input, output, recommendations, notification=DummyReporter()):
plumber = Plumber(input, output, Log(), notification) recommendations = list(recommendations)
recommendations.append(('verbose', 2, OptionRecommendation.HIGH))
plumber = Plumber(input, output, Log(), report_progress=notification)
plumber.merge_ui_recommendations(recommendations) plumber.merge_ui_recommendations(recommendations)
plumber.run() plumber.run()

View File

@ -179,6 +179,7 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
md.addSeparator() md.addSeparator()
md.addAction(_('Download metadata and covers')) md.addAction(_('Download metadata and covers'))
md.addAction(_('Download only metadata')) md.addAction(_('Download only metadata'))
md.addAction(_('Download only covers'))
self.metadata_menu = md self.metadata_menu = md
self.add_menu = QMenu() self.add_menu = QMenu()
self.add_menu.addAction(_('Add books from a single directory')) self.add_menu.addAction(_('Add books from a single directory'))
@ -209,6 +210,10 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
partial(self.download_metadata, covers=True)) partial(self.download_metadata, covers=True))
QObject.connect(md.actions()[5], SIGNAL('triggered(bool)'), QObject.connect(md.actions()[5], SIGNAL('triggered(bool)'),
partial(self.download_metadata, covers=False)) partial(self.download_metadata, covers=False))
QObject.connect(md.actions()[6], SIGNAL('triggered(bool)'),
partial(self.download_metadata, covers=True,
set_metadata=False))
self.save_menu = QMenu() self.save_menu = QMenu()
@ -834,7 +839,7 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
############################### Edit metadata ############################## ############################### Edit metadata ##############################
def download_metadata(self, checked, covers=True): def download_metadata(self, checked, covers=True, set_metadata=True):
rows = self.library_view.selectionModel().selectedRows() rows = self.library_view.selectionModel().selectedRows()
previous = self.library_view.currentIndex() previous = self.library_view.currentIndex()
if not rows or len(rows) == 0: if not rows or len(rows) == 0:
@ -845,10 +850,12 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
db = self.library_view.model().db db = self.library_view.model().db
ids = [db.id(row.row()) for row in rows] ids = [db.id(row.row()) for row in rows]
from calibre.gui2.metadata import DownloadMetadata from calibre.gui2.metadata import DownloadMetadata
self._download_book_metadata = DownloadMetadata(db, ids, get_covers=covers) self._download_book_metadata = DownloadMetadata(db, ids,
get_covers=covers, set_metadata=set_metadata)
self._download_book_metadata.start() self._download_book_metadata.start()
x = _('covers') if covers and not set_metadata else _('metadata')
self.progress_indicator.start( self.progress_indicator.start(
_('Downloading metadata for %d book(s)')%len(ids)) _('Downloading %s for %d book(s)')%(x, len(ids)))
self._book_metadata_download_check = QTimer(self) self._book_metadata_download_check = QTimer(self)
self.connect(self._book_metadata_download_check, self.connect(self._book_metadata_download_check,
SIGNAL('timeout()'), self.book_metadata_download_check) SIGNAL('timeout()'), self.book_metadata_download_check)

View File

@ -41,11 +41,12 @@ class Worker(Thread):
class DownloadMetadata(Thread): class DownloadMetadata(Thread):
def __init__(self, db, ids, get_covers): def __init__(self, db, ids, get_covers, set_metadata=True):
Thread.__init__(self) Thread.__init__(self)
self.setDaemon(True) self.setDaemon(True)
self.metadata = {} self.metadata = {}
self.covers = {} self.covers = {}
self.set_metadata = set_metadata
self.db = db self.db = db
self.updated = set([]) self.updated = set([])
self.get_covers = get_covers self.get_covers = get_covers
@ -96,8 +97,9 @@ class DownloadMetadata(Thread):
self.commit_covers() self.commit_covers()
self.commit_covers(True) self.commit_covers(True)
for id in self.fetched_metadata: if self.set_metadata:
self.db.set_metadata(id, self.metadata[id]) for id in self.fetched_metadata:
self.db.set_metadata(id, self.metadata[id])
self.updated = set(self.fetched_metadata) self.updated = set(self.fetched_metadata)

View File

@ -16,6 +16,7 @@ from calibre.gui2 import warning_dialog
from calibre.gui2.convert.single import NoSupportedInputFormats from calibre.gui2.convert.single import NoSupportedInputFormats
from calibre.gui2.convert.single import Config as SingleConfig from calibre.gui2.convert.single import Config as SingleConfig
from calibre.gui2.convert.bulk import BulkConfig from calibre.gui2.convert.bulk import BulkConfig
from calibre.customize.conversion import OptionRecommendation
from calibre.utils.config import prefs from calibre.utils.config import prefs
def convert_single_ebook(parent, db, book_ids, auto_conversion=False, out_format=None): def convert_single_ebook(parent, db, book_ids, auto_conversion=False, out_format=None):
@ -131,13 +132,16 @@ def fetch_scheduled_recipe(recipe, script):
fmt = prefs['output_format'].lower() fmt = prefs['output_format'].lower()
pt = PersistentTemporaryFile(suffix='_recipe_out.%s'%fmt.lower()) pt = PersistentTemporaryFile(suffix='_recipe_out.%s'%fmt.lower())
pt.close() pt.close()
args = ['ebook-convert', script, pt.name, '-vv'] recs = []
args = [script, pt.name, recs]
if recipe.needs_subscription: if recipe.needs_subscription:
x = config.get('recipe_account_info_%s'%recipe.id, False) x = config.get('recipe_account_info_%s'%recipe.id, False)
if not x: if not x:
raise ValueError(_('You must set a username and password for %s')%recipe.title) raise ValueError(_('You must set a username and password for %s')%recipe.title)
args.extend(['--username', x[0], '--password', x[1]]) recs.append(('username', x[0], OptionRecommendation.HIGH))
recs.append(('password', x[1], OptionRecommendation.HIGH))
return 'ebook-convert', [args], _('Fetch news from ')+recipe.title, fmt.upper(), [pt]
return 'gui_convert', args, _('Fetch news from ')+recipe.title, fmt.upper(), [pt]

View File

@ -402,7 +402,8 @@ class LibraryDatabase2(LibraryDatabase):
def get_property(idx, index_is_id=False, loc=-1): def get_property(idx, index_is_id=False, loc=-1):
row = self.data._data[idx] if index_is_id else self.data[idx] row = self.data._data[idx] if index_is_id else self.data[idx]
return row[loc] if row is not None:
return row[loc]
for prop in ('author_sort', 'authors', 'comment', 'comments', 'isbn', for prop in ('author_sort', 'authors', 'comment', 'comments', 'isbn',
'publisher', 'rating', 'series', 'series_index', 'tags', 'publisher', 'rating', 'series', 'series_index', 'tags',

View File

@ -220,7 +220,7 @@ Post any output you see in a help message on the `Forum <http://www.mobileread.c
My antivirus programs claims |app| is a virus/trojan? My antivirus programs claims |app| is a virus/trojan?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Your antivirus program is wrong. |app| is a completely open source product. You can actually browse the source code yourself (or hire someone to do it for you) to verify that it is not a virus. Please report the false identification to whatever company you buy your antivirus software from. Your antivirus program is wrong. |app| is a completely open source product. You can actually browse the source code yourself (or hire someone to do it for you) to verify that it is not a virus. Please report the false identification to whatever company you buy your antivirus software from. If the antivirus program is preventing you from downloading/installing |app|, disable it temporarily, install |app| and then re-enable it.
I want some feature added to |app|. What can I do? I want some feature added to |app|. What can I do?

View File

@ -31,6 +31,7 @@ class PersistentTemporaryFile(object):
dir=dir) dir=dir)
self._file = os.fdopen(fd, mode) self._file = os.fdopen(fd, mode)
self._name = name self._name = name
self._fd = fd
atexit.register(cleanup, name) atexit.register(cleanup, name)
def __getattr__(self, name): def __getattr__(self, name):

View File

@ -6,7 +6,7 @@ __docformat__ = 'restructuredtext en'
''' '''
Keep track of donations to calibre. Keep track of donations to calibre.
''' '''
import sys, cStringIO, textwrap, traceback, re, os, time import sys, cStringIO, textwrap, traceback, re, os, time, calendar
from datetime import date, timedelta from datetime import date, timedelta
from math import sqrt from math import sqrt
os.environ['HOME'] = '/tmp' os.environ['HOME'] = '/tmp'
@ -15,7 +15,6 @@ matplotlib.use('Agg')
import matplotlib.pyplot as plt import matplotlib.pyplot as plt
import matplotlib.dates as mdates import matplotlib.dates as mdates
import cherrypy import cherrypy
from lxml import etree from lxml import etree
@ -33,7 +32,13 @@ def range_for_month(year, month):
def range_for_year(year): def range_for_year(year):
return date(year=year, month=1, day=1), date(year=year, month=12, day=31) return date(year=year, month=1, day=1), date(year=year, month=12, day=31)
def days_in_month(year, month):
c = calendar.Calendar()
ans = 0
for x in c.itermonthdays(year, month):
if x != 0: ans += 1
return ans
def rationalize_country(country): def rationalize_country(country):
if re.match('(?i)(US|USA|America)', country): if re.match('(?i)(US|USA|America)', country):
@ -67,14 +72,14 @@ def rationalize_country(country):
return country return country
class Record(object): class Record(object):
def __init__(self, email, country, amount, date, name): def __init__(self, email, country, amount, date, name):
self.email = email self.email = email
self.country = country self.country = country
self.amount = amount self.amount = amount
self.date = date self.date = date
self.name = name self.name = name
def __str__(self): def __str__(self):
return '<donation email="%s" country="%s" amount="%.2f" date="%s" %s />'%\ return '<donation email="%s" country="%s" amount="%.2f" date="%s" %s />'%\
(self.email, self.country, self.amount, self.date.isoformat(), 'name="%s"'%self.name if self.name else '') (self.email, self.country, self.amount, self.date.isoformat(), 'name="%s"'%self.name if self.name else '')
@ -93,7 +98,7 @@ class Country(list):
def __str__(self): def __str__(self):
return self.name + ': %.2f%%'%self.percent return self.name + ': %.2f%%'%self.percent
def __cmp__(self, other): def __cmp__(self, other):
return cmp(self.total, other.total) return cmp(self.total, other.total)
@ -118,7 +123,7 @@ class Stats:
if r.date not in self.days.keys(): if r.date not in self.days.keys():
self.days[r.date] = [] self.days[r.date] = []
self.days[r.date].append(r) self.days[r.date].append(r)
self.min, self.max = start, end self.min, self.max = start, end
self.period = (self.max - self.min) + timedelta(days=1) self.period = (self.max - self.min) + timedelta(days=1)
daily_totals = [] daily_totals = []
@ -139,7 +144,24 @@ class Stats:
self.countries[r.country].append(r) self.countries[r.country].append(r)
for country in self.countries.values(): for country in self.countries.values():
country.percent = (100 * country.total/self.total) if self.total else 0. country.percent = (100 * country.total/self.total) if self.total else 0.
def get_daily_averages(self):
month_buckets, month_order = {}, []
x = self.min
for t in self.daily_totals:
month = (x.year, x.month)
if month not in month_buckets:
month_buckets[month] = 0.
month_order.append(month)
month_buckets[month] += t
x += timedelta(days=1)
c = calendar.Calendar()
month_days = [days_in_month(*x) for x in month_order]
month_averages = [month_buckets[x]/float(y) for x, y in zip(month_order, month_days)]
return month_order, month_averages
def __str__(self): def __str__(self):
buf = cStringIO.StringIO() buf = cStringIO.StringIO()
print >>buf, '\tTotal: %.2f'%self.total print >>buf, '\tTotal: %.2f'%self.total
@ -149,7 +171,7 @@ class Stats:
for c in self.countries.values(): for c in self.countries.values():
print >>buf, '\t\t', c print >>buf, '\t\t', c
return buf.getvalue() return buf.getvalue()
def to_html(self, num_of_countries=sys.maxint): def to_html(self, num_of_countries=sys.maxint):
countries = sorted(self.countries.values(), cmp=cmp, reverse=True)[:num_of_countries] countries = sorted(self.countries.values(), cmp=cmp, reverse=True)[:num_of_countries]
crows = ['<tr><td>%s</td><td class="country_percent">%.2f %%</td></tr>'%(c.name, c.percent) for c in countries] crows = ['<tr><td>%s</td><td class="country_percent">%.2f %%</td></tr>'%(c.name, c.percent) for c in countries]
@ -168,32 +190,48 @@ class Stats:
<br /> <br />
%(ctable)s %(ctable)s
</div> </div>
''')%dict(total=self.total, da=self.daily_average, ac=self.average, ''')%dict(total=self.total, da=self.daily_average, ac=self.average,
ctable=ctable, period=self.period.days, num=len(self.totals), ctable=ctable, period=self.period.days, num=len(self.totals),
dd=self.daily_deviation, ad=self.average_deviation, dd=self.daily_deviation, ad=self.average_deviation,
dpd=len(self.totals)/float(self.period.days), dpd=len(self.totals)/float(self.period.days),
min=self.min.isoformat(), max=self.max.isoformat()) min=self.min.isoformat(), max=self.max.isoformat())
def expose(func): def expose(func):
def do(self, *args, **kwargs): def do(self, *args, **kwargs):
dict.update(cherrypy.response.headers, {'Server':'Donations_server/1.0'}) dict.update(cherrypy.response.headers, {'Server':'Donations_server/1.0'})
return func(self, *args, **kwargs) return func(self, *args, **kwargs)
return cherrypy.expose(do) return cherrypy.expose(do)
class Server(object): class Server(object):
TRENDS = '/tmp/donations_trend.png' TRENDS = '/tmp/donations_trend.png'
MONTH_TRENDS = '/tmp/donations_month_trend.png' MONTH_TRENDS = '/tmp/donations_month_trend.png'
AVERAGES = '/tmp/donations_averages.png'
def __init__(self, apache=False, root='/', data_file='/tmp/donations.xml'): def __init__(self, apache=False, root='/', data_file='/tmp/donations.xml'):
self.apache = apache self.apache = apache
self.document_root = root self.document_root = root
self.data_file = data_file self.data_file = data_file
self.read_records() self.read_records()
def calculate_daily_averages(self):
stats = self.get_slice(self.earliest, self.latest)
fig = plt.figure(2, (10, 4), 96)#, facecolor, edgecolor, frameon, FigureClass)
fig.clear()
ax = fig.add_subplot(111)
month_order, month_averages = stats.get_daily_averages()
x = [date(y, m, 1) for y, m in month_order[:-1]]
ax.plot(x, month_averages[:-1])
ax.set_xlabel('Month')
ax.set_ylabel('Daily average ($)')
ax.xaxis.set_major_locator(mdates.MonthLocator(interval=2))
ax.xaxis.set_major_formatter(mdates.DateFormatter('%m/%y'))
fig.savefig(self.AVERAGES)
def calculate_month_trend(self, days=31): def calculate_month_trend(self, days=31):
stats = self.get_slice(date.today()-timedelta(days=days-1), date.today()) stats = self.get_slice(date.today()-timedelta(days=days-1), date.today())
fig = plt.figure(2, (10, 4), 96)#, facecolor, edgecolor, frameon, FigureClass) fig = plt.figure(2, (10, 4), 96)#, facecolor, edgecolor, frameon, FigureClass)
@ -214,14 +252,14 @@ Total: $%(total).2f
Daily average: $%(da).2f \u00b1 %(dd).2f Daily average: $%(da).2f \u00b1 %(dd).2f
Average contribution: $%(ac).2f \u00b1 %(ad).2f Average contribution: $%(ac).2f \u00b1 %(ad).2f
Donors per day: %(dpd).2f Donors per day: %(dpd).2f
'''%dict(total=stats.total, da=stats.daily_average, '''%dict(total=stats.total, da=stats.daily_average,
dd=stats.daily_deviation, ac=stats.average, dd=stats.daily_deviation, ac=stats.average,
ad=stats.average_deviation, ad=stats.average_deviation,
dpd=len(stats.totals)/float(stats.period.days), dpd=len(stats.totals)/float(stats.period.days),
) )
text = ax.annotate(text, (0.5, 0.65), textcoords='axes fraction') text = ax.annotate(text, (0.5, 0.65), textcoords='axes fraction')
fig.savefig(self.MONTH_TRENDS) fig.savefig(self.MONTH_TRENDS)
def calculate_trend(self): def calculate_trend(self):
def months(start, end): def months(start, end):
pos = range_for_month(start.year, start.month)[0] pos = range_for_month(start.year, start.month)[0]
@ -253,7 +291,7 @@ Donors per day: %(dpd).2f
fig.savefig(self.TRENDS) fig.savefig(self.TRENDS)
#plt.show() #plt.show()
def read_records(self): def read_records(self):
self.tree = etree.parse(self.data_file) self.tree = etree.parse(self.data_file)
self.last_read_time = time.time() self.last_read_time = time.time()
@ -269,21 +307,22 @@ Donors per day: %(dpd).2f
self.earliest, self.latest = min_date, max_date self.earliest, self.latest = min_date, max_date
self.calculate_trend() self.calculate_trend()
self.calculate_month_trend() self.calculate_month_trend()
self.calculate_daily_averages()
def get_slice(self, start_date, end_date): def get_slice(self, start_date, end_date):
stats = Stats([r for r in self.records if r.date >= start_date and r.date <= end_date], stats = Stats([r for r in self.records if r.date >= start_date and r.date <= end_date],
start_date, end_date) start_date, end_date)
return stats return stats
def month(self, year, month): def month(self, year, month):
return self.get_slice(*range_for_month(year, month)) return self.get_slice(*range_for_month(year, month))
def year(self, year): def year(self, year):
return self.get_slice(*range_for_year(year)) return self.get_slice(*range_for_year(year))
def range_to_date(self, raw): def range_to_date(self, raw):
return date(*map(int, raw.split('-'))) return date(*map(int, raw.split('-')))
def build_page(self, period_type, data): def build_page(self, period_type, data):
if os.stat(self.data_file).st_mtime >= self.last_read_time: if os.stat(self.data_file).st_mtime >= self.last_read_time:
self.read_records() self.read_records()
@ -294,7 +333,7 @@ Donors per day: %(dpd).2f
yy = data if period_type == 'year' else year yy = data if period_type == 'year' else year
rl = data[0] if period_type == 'range' else '' rl = data[0] if period_type == 'range' else ''
rr = data[1] if period_type == 'range' else '' rr = data[1] if period_type == 'range' else ''
def build_month_list(current): def build_month_list(current):
months = [] months = []
for i in range(1, 13): for i in range(1, 13):
@ -302,7 +341,7 @@ Donors per day: %(dpd).2f
sel = 'selected="selected"' if i == current else '' sel = 'selected="selected"' if i == current else ''
months.append('<option value="%d" %s>%s</option>'%(i, sel, month)) months.append('<option value="%d" %s>%s</option>'%(i, sel, month))
return months return months
def build_year_list(current): def build_year_list(current):
all_years = sorted(range(self.earliest.year, self.latest.year+1, 1)) all_years = sorted(range(self.earliest.year, self.latest.year+1, 1))
if current not in all_years: if current not in all_years:
@ -312,11 +351,11 @@ Donors per day: %(dpd).2f
sel = 'selected="selected"' if year == current else '' sel = 'selected="selected"' if year == current else ''
years.append('<option value="%d" %s>%d</option>'%(year, sel, year)) years.append('<option value="%d" %s>%d</option>'%(year, sel, year))
return years return years
mmlist = '<select name="month_month">\n%s</select>'%('\n'.join(build_month_list(mm))) mmlist = '<select name="month_month">\n%s</select>'%('\n'.join(build_month_list(mm)))
mylist = '<select name="month_year">\n%s</select>'%('\n'.join(build_year_list(my))) mylist = '<select name="month_year">\n%s</select>'%('\n'.join(build_year_list(my)))
yylist = '<select name="year_year">\n%s</select>'%('\n'.join(build_year_list(yy))) yylist = '<select name="year_year">\n%s</select>'%('\n'.join(build_year_list(yy)))
if period_type == 'month': if period_type == 'month':
range_stats = range_for_month(my, mm) range_stats = range_for_month(my, mm)
elif period_type == 'year': elif period_type == 'year':
@ -332,9 +371,9 @@ Donors per day: %(dpd).2f
range_stats = '<pre>Invalid input:\n%s</pre>'%err range_stats = '<pre>Invalid input:\n%s</pre>'%err
else: else:
range_stats = self.get_slice(*range_stats).to_html(num_of_countries=10) range_stats = self.get_slice(*range_stats).to_html(num_of_countries=10)
today = self.get_slice(date.today(), date.today()) today = self.get_slice(date.today(), date.today())
return textwrap.dedent('''\ return textwrap.dedent('''\
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
@ -365,7 +404,7 @@ Donors per day: %(dpd).2f
if ((dayobj.getMonth()+1!=monthfield)||(dayobj.getDate()!=dayfield)||(dayobj.getFullYear()!=yearfield)) return false; if ((dayobj.getMonth()+1!=monthfield)||(dayobj.getDate()!=dayfield)||(dayobj.getFullYear()!=yearfield)) return false;
return true; return true;
} }
function check_period_form(form) { function check_period_form(form) {
if (form.period_type[2].checked) { if (form.period_type[2].checked) {
if (!test_date(form.range_left.value)) { if (!test_date(form.range_left.value)) {
@ -381,11 +420,11 @@ Donors per day: %(dpd).2f
} }
return true; return true;
} }
function is_empty(val) { function is_empty(val) {
return val.trim().length == 0 return val.trim().length == 0
} }
function check_add_form(form) { function check_add_form(form) {
var test_amount = /[\.0-9]+/; var test_amount = /[\.0-9]+/;
if (is_empty(form.email.value)) { if (is_empty(form.email.value)) {
@ -409,8 +448,8 @@ Donors per day: %(dpd).2f
return false; return false;
} }
return true; return true;
} }
function rationalize_periods() { function rationalize_periods() {
var form = document.forms[0]; var form = document.forms[0];
var disabled = !form.period_type[0].checked; var disabled = !form.period_type[0].checked;
@ -438,7 +477,7 @@ Donors per day: %(dpd).2f
<h3>Donations to date</h3> <h3>Donations to date</h3>
%(todate)s %(todate)s
</td> </td>
<td id="right"> <td id="right">
<h3>Donations in period</h3> <h3>Donations in period</h3>
<fieldset> <fieldset>
@ -463,10 +502,13 @@ Donors per day: %(dpd).2f
</table> </table>
<hr /> <hr />
<div style="text-align:center"> <div style="text-align:center">
<h3>Income trends for the last year</h3>
<img src="%(root)strend.png" alt="Income trends" /> <img src="%(root)strend.png" alt="Income trends" />
<h3>Income trends for the last 31 days</h3> <h3>Income trends for the last year</h3>
<img src="%(root)smonth_trend.png" alt="Month income trend" /> <img src="%(root)smonth_trend.png" alt="Month income trend" />
<h3>Income trends for the last 31 days</h3>
<img src="%(root)saverage_trend.png" alt="Daily average
income trend" />
<h3>Income trends since records started</h3>
</div> </div>
</body> </body>
</html> </html>
@ -479,26 +521,31 @@ Donors per day: %(dpd).2f
rl=rl, rr=rr, range_stats=range_stats, root=self.document_root, rl=rl, rr=rr, range_stats=range_stats, root=self.document_root,
today=today.total today=today.total
) )
@expose @expose
def index(self): def index(self):
month = date.today().month month = date.today().month
year = date.today().year year = date.today().year
cherrypy.response.headers['Content-Type'] = 'application/xhtml+xml' cherrypy.response.headers['Content-Type'] = 'application/xhtml+xml'
return self.build_page('month', (year, month)) return self.build_page('month', (year, month))
@expose @expose
def trend_png(self): def trend_png(self):
cherrypy.response.headers['Content-Type'] = 'image/png' cherrypy.response.headers['Content-Type'] = 'image/png'
return open(self.TRENDS, 'rb').read() return open(self.TRENDS, 'rb').read()
@expose @expose
def month_trend_png(self): def month_trend_png(self):
cherrypy.response.headers['Content-Type'] = 'image/png' cherrypy.response.headers['Content-Type'] = 'image/png'
return open(self.MONTH_TRENDS, 'rb').read() return open(self.MONTH_TRENDS, 'rb').read()
@expose @expose
def show(self, period_type='month', month_month='', month_year='', def average_trend_png(self):
cherrypy.response.headers['Content-Type'] = 'image/png'
return open(self.AVERAGES, 'rb').read()
@expose
def show(self, period_type='month', month_month='', month_year='',
year_year='', range_left='', range_right=''): year_year='', range_left='', range_right=''):
if period_type == 'month': if period_type == 'month':
mm = int(month_month) if month_month else date.today().month mm = int(month_month) if month_month else date.today().month
@ -510,7 +557,7 @@ Donors per day: %(dpd).2f
data = (range_left, range_right) data = (range_left, range_right)
cherrypy.response.headers['Content-Type'] = 'application/xhtml+xml' cherrypy.response.headers['Content-Type'] = 'application/xhtml+xml'
return self.build_page(period_type, data) return self.build_page(period_type, data)
def config(): def config():
config = { config = {
'global': { 'global': {
@ -527,9 +574,9 @@ def apache_start():
'environment' : 'production', 'environment' : 'production',
'show_tracebacks' : False, 'show_tracebacks' : False,
}) })
cherrypy.tree.mount(Server(apache=True, root='/donations/', data_file='/var/www/calibre.kovidgoyal.net/donations.xml'), cherrypy.tree.mount(Server(apache=True, root='/donations/', data_file='/var/www/calibre.kovidgoyal.net/donations.xml'),
'/donations', config=config()) '/donations', config=config())
def main(args=sys.argv): def main(args=sys.argv):
server = Server() server = Server()

View File

@ -0,0 +1,10 @@
#!/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'

View File

@ -0,0 +1,150 @@
#!/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 subprocess, os, sys, time
from calibre.constants import iswindows, isosx, isfrozen
from calibre.utils.config import prefs
from calibre.ptempfile import PersistentTemporaryFile
if iswindows:
import win32process
class Worker(object):
'''
Platform independent object for launching child processes. All processes
have the environment variable :envvar:`CALIBRE_WORKER` set.
Useful attributes: ``is_alive``, ``returncode``
usefule methods: ``kill``
To launch child simply call the Worker object. By default, the child's
output is redirected to an on disk file, the path to which is returned by
the call.
'''
@property
def osx_interpreter(self):
exe = os.path.basename(sys.executable)
return exe if 'python' in exe else 'python'
@property
def osx_contents_dir(self):
fd = os.path.realpath(getattr(sys, 'frameworks_dir'))
return os.path.dirname(fd)
@property
def executable(self):
if iswindows:
return os.path.join(os.path.dirname(sys.executable),
'calibre-parallel.exe' if isfrozen else \
'Scripts\\calibre-parallel.exe')
if isosx:
if not isfrozen: return 'calibre-parallel'
contents = os.path.join(self.osx_contents_dir,
'console.app', 'Contents')
return os.path.join(contents, 'MacOS', self.osx_interpreter)
return os.path.join(getattr(sys, 'frozen_path'), 'calibre-parallel') \
if isfrozen else 'calibre-parallel'
@property
def gui_executable(self):
if isfrozen and isosx:
return os.path.join(self.osx_contents_dir,
'MacOS', self.osx_interpreter)
return self.executable
@property
def env(self):
env = dict(os.environ)
env['CALIBRE_WORKER'] = '1'
env.update(self._env)
return env
@property
def is_alive(self):
return hasattr(self, 'child') and self.child.poll() is not None
@property
def returncode(self):
if not hasattr(self, 'child'): return None
self.child.poll()
return self.child.returncode
def kill(self):
try:
if self.is_alive:
if iswindows:
return self.child.kill()
try:
self.child.terminate()
st = time.time()
while self.is_alive and time.time()-st < 2:
time.sleep(0.2)
finally:
if self.is_alive:
self.child.kill()
except:
pass
def __init__(self, env, gui=False):
self._env = {}
self.gui = gui
if isosx and isfrozen:
contents = os.path.join(self.osx_contents_dir, 'console.app', 'Contents')
resources = os.path.join(contents, 'Resources')
fd = os.path.join(contents, 'Frameworks')
self._env['PYTHONHOME'] = resources
self._env['MAGICK_HOME'] = os.path.join(fd, 'ImageMagick')
self._env['DYLD_LIBRARY_PATH'] = os.path.join(fd, 'ImageMagick', 'lib')
if isfrozen and not (iswindows or isosx):
self._env['LD_LIBRARY_PATH'] = getattr(sys, 'frozen_path') + ':'\
+ os.environ.get('LD_LIBRARY_PATH', '')
self._env.update(env)
def __call__(self, redirect_output=True, cwd=None, priority=None):
'''
If redirect_output is True, output from the child is redirected
to a file on disk and this method returns the path to that file.
'''
exe = self.gui_executable if self.gui else self.executable
env = self.env
env['ORIGWD'] = cwd or os.path.abspath(os.getcwd())
_cwd = cwd
if isfrozen and not iswindows and not isosx:
_cwd = getattr(sys, 'frozen_path', None)
if priority is None:
priority = prefs['worker_process_priority']
cmd = [exe]
if isosx:
cmd += ['-c', 'from calibre.utils.worker import main; main()']
args = {
'env' : env,
'cwd' : _cwd,
}
if iswindows:
priority = {
'high' : win32process.HIGH_PRIORITY_CLASS,
'normal' : win32process.NORMAL_PRIORITY_CLASS,
'low' : win32process.IDLE_PRIORITY_CLASS}[priority]
args['creationflags'] = win32process.CREATE_NO_WINDOW|priority
ret = None
if redirect_output:
self._file = PersistentTemporaryFile('_worker_redirect.log')
args['stdout'] = self._file._fd
args['stderr'] = subprocess.STDOUT
ret = self._file.name
self.child = subprocess.Popen(cmd, **args)
return ret

View File

@ -0,0 +1,10 @@
#!/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'

View File

@ -0,0 +1,78 @@
#!/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 os, cPickle
from multiprocessing.connection import Client
from threading import Thread
from queue import Queue
from contextlib import closing
PARALLEL_FUNCS = {
'lrfviewer' :
('calibre.gui2.lrf_renderer.main', 'main', None),
'ebook-viewer' :
('calibre.gui2.viewer.main', 'main', None),
'render_pages' :
('calibre.ebooks.comic.input', 'render_pages', 'notification'),
'gui_convert' :
('calibre.gui2.convert.gui_conversion', 'gui_convert', 'notification'),
}
class Progress(Thread):
def __init__(self, conn):
self.daemon = True
Thread.__init__(self)
self.conn = conn
self.queue = Queue()
def __call__(self, percent, msg=''):
self.queue.put((percent, msg))
def run(self):
while True:
x = self.queue.get()
if x is None:
break
try:
self.conn.send(x)
except:
break
def get_func(name):
module, func, notification = PARALLEL_FUNCS[name]
module = __import__(module, fromlist=[1])
func = getattr(module, func)
return func, notification
def main():
address = cPickle.loads(os.environ['CALIBRE_WORKER_ADDRESS'])
key = os.environ['CALIBRE_WORKER_KEY']
with closing(Client(address, authkey=key)) as conn:
name, args, kwargs = conn.recv()
func, notification = get_func(name)
notifier = Progress(conn)
if notification:
kwargs[notification] = notifier
notifier.start()
func(*args, **kwargs)
notifier.queue.put(None)
return 0
if __name__ == '__main__':
raise SystemExit(main())

View File

@ -0,0 +1,98 @@
#!/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 os
from calibre.constants import plugins, preferred_encoding
from calibre.ebooks.metadata import MetaInformation, string_to_authors, \
authors_to_string
podofo, podofo_err = plugins['podofo']
class Unavailable(Exception): pass
def get_metadata(stream):
if not podofo:
raise Unavailable(podofo_err)
raw = stream.read()
stream.seek(0)
p = podofo.PdfMemDocument()
p.Load(raw, len(raw))
info = p.GetInfo()
title = info.GetTitle().decode('utf-8').strip()
if not title:
title = getattr(stream, 'name', _('Unknown'))
title = os.path.splitext(os.path.basename(title))[0]
author = info.GetAuthor().decode('utf-8').strip()
authors = string_to_authors(author) if author else [_('Unknown')]
mi = MetaInformation(title, authors)
creator = info.GetCreator().decode('utf-8').strip()
if creator:
mi.book_producer = creator
return mi
def prep(val):
if not val:
return u''
if not isinstance(val, unicode):
val = val.decode(preferred_encoding, 'replace')
return val.strip()
def set_metadata(stream, mi):
if not podofo:
raise Unavailable(podofo_err)
raw = stream.read()
p = podofo.PdfMemDocument()
p.Load(raw, len(raw))
info = p.GetInfo()
title = prep(mi.title)
touched = False
if title:
info.SetTitle(title)
touched = True
author = prep(authors_to_string(mi.authors))
if author:
print repr(author)
info.SetAuthor(author)
touched = True
bkp = prep(mi.book_producer)
if bkp:
info.SetCreator(bkp)
touched = True
if touched:
p.SetInfo(info)
from calibre.ptempfile import TemporaryFile
with TemporaryFile('_pdf_set_metadata.pdf') as f:
p.Write(f)
raw = open(f, 'rb').read()
stream.seek(0)
stream.truncate()
stream.write(raw)
stream.flush()
stream.seek(0)
if __name__ == '__main__':
f = '/tmp/t.pdf'
import StringIO
stream = StringIO.StringIO(open(f).read())
mi = get_metadata(open(f))
print
print 'Original metadata:'
print mi
mi.title = 'Test title'
mi.authors = ['Test author', 'author2']
mi.book_producer = 'calibre'
set_metadata(stream, mi)
open('/tmp/x.pdf', 'wb').write(stream.getvalue())
print
print 'New pdf written to /tmp/x.pdf'

View File

@ -0,0 +1,128 @@
%Module podofo 0
%MappedType PdfString
{
%TypeHeaderCode
#define USING_SHARED_PODOFO
#include <PdfString.h>
using namespace PoDoFo;
%End
%ConvertFromTypeCode
if (sipCpp -> IsValid()) {
std::string raw = sipCpp->GetStringUtf8();
return PyString_FromStringAndSize(raw.c_str(), raw.length());
} else return PyString_FromString("");
%End
%ConvertToTypeCode
if (sipIsErr == NULL) {
if (sipIsErr == NULL)
return (PyUnicode_Check(sipPy) || PyString_Check(sipPy));
}
if (sipPy == Py_None) {
*sipCppPtr = NULL;
return 0;
}
if (PyString_Check(sipPy)) {
*sipCppPtr = new PdfString((pdf_utf8 *)PyString_AS_STRING(sipPy));
return sipGetState(sipTransferObj);
}
if (PyUnicode_Check(sipPy)) {
Py_UNICODE* u = PyUnicode_AS_UNICODE(sipPy);
PyObject *u8 = PyUnicode_EncodeUTF8(u, PyUnicode_GET_SIZE(sipPy), "replace");
pdf_utf8 *s8 = (pdf_utf8 *)PyString_AS_STRING(u8);
*sipCppPtr = new PdfString(s8);
return sipGetState(sipTransferObj);
}
*sipCppPtr = (PdfString *)sipForceConvertTo_PdfString(sipPy,sipIsErr);
return 1;
%End
};
class PdfObject {
%TypeHeaderCode
#define USING_SHARED_PODOFO
#include <PdfObject.h>
using namespace PoDoFo;
%End
public:
PdfObject();
};
class PdfInfo {
%TypeHeaderCode
#define USING_SHARED_PODOFO
#include <PdfInfo.h>
using namespace PoDoFo;
%End
public:
PdfInfo(PdfObject *);
PdfString GetAuthor() const;
PdfString GetSubject() const;
PdfString GetTitle() const;
PdfString GetKeywords() const;
PdfString GetCreator() const;
PdfString GetProducer() const;
void SetAuthor(PdfString &);
void SetSubject(PdfString &);
void SetTitle(PdfString &);
void SetKeywords(PdfString &);
void SetCreator(PdfString &);
void SetProducer(PdfString &);
};
class PdfOutputDevice {
%TypeHeaderCode
#define USING_SHARED_PODOFO
#include <PdfOutputDevice.h>
using namespace PoDoFo;
%End
public:
PdfOutputDevice(char *, long);
unsigned long GetLength();
unsigned long Tell();
void Flush();
};
class PdfMemDocument {
%TypeHeaderCode
#define USING_SHARED_PODOFO
#include <PdfMemDocument.h>
using namespace PoDoFo;
%End
public:
PdfMemDocument();
void Load(const char *filename);
void Load(const char *buffer, long size);
void Write(const char *filename);
PdfInfo *GetInfo() const;
protected:
void SetInfo(PdfInfo * /TransferThis/);
private:
PdfMemDocument(PdfMemDocument &);
};
%Exception PoDoFo::PdfError /PyName=PdfError/
{
%TypeHeaderCode
#define USING_SHARED_PODOFO
#include <PdfError.h>
%End
%RaiseCode
const char *detail = sipExceptionRef.what();
SIP_BLOCK_THREADS
PyErr_SetString(sipException_PoDoFo_PdfError, detail);
SIP_UNBLOCK_THREADS
%End
};