From 16a11369a552b49878b0f84b1da61721fd791a22 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 10 May 2009 11:45:52 -0700 Subject: [PATCH 1/5] Fix #2420 (Download Only Covers) --- src/calibre/gui2/main.py | 13 ++++++++++--- src/calibre/gui2/metadata.py | 8 +++++--- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/src/calibre/gui2/main.py b/src/calibre/gui2/main.py index 6d7fd63378..459585a589 100644 --- a/src/calibre/gui2/main.py +++ b/src/calibre/gui2/main.py @@ -175,6 +175,7 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI): md.addSeparator() md.addAction(_('Download metadata and covers')) md.addAction(_('Download only metadata')) + md.addAction(_('Download only covers')) self.metadata_menu = md self.add_menu = QMenu() self.add_menu.addAction(_('Add books from a single directory')) @@ -205,6 +206,10 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI): partial(self.download_metadata, covers=True)) QObject.connect(md.actions()[5], SIGNAL('triggered(bool)'), 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() @@ -836,7 +841,7 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI): ############################### 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() previous = self.library_view.currentIndex() if not rows or len(rows) == 0: @@ -847,10 +852,12 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI): db = self.library_view.model().db ids = [db.id(row.row()) for row in rows] 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() + x = _('covers') if covers and not set_metadata else _('metadata') 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.connect(self._book_metadata_download_check, SIGNAL('timeout()'), self.book_metadata_download_check) diff --git a/src/calibre/gui2/metadata.py b/src/calibre/gui2/metadata.py index 607fd6a41b..247880d7b0 100644 --- a/src/calibre/gui2/metadata.py +++ b/src/calibre/gui2/metadata.py @@ -41,11 +41,12 @@ class Worker(Thread): class DownloadMetadata(Thread): - def __init__(self, db, ids, get_covers): + def __init__(self, db, ids, get_covers, set_metadata=True): Thread.__init__(self) self.setDaemon(True) self.metadata = {} self.covers = {} + self.set_metadata = set_metadata self.db = db self.updated = set([]) self.get_covers = get_covers @@ -96,8 +97,9 @@ class DownloadMetadata(Thread): self.commit_covers() self.commit_covers(True) - for id in self.fetched_metadata: - self.db.set_metadata(id, self.metadata[id]) + if self.set_metadata: + for id in self.fetched_metadata: + self.db.set_metadata(id, self.metadata[id]) self.updated = set(self.fetched_metadata) From 3e9e6a63d784baa4f46203cd27dcf0ecdcfa0aa7 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 10 May 2009 12:56:44 -0700 Subject: [PATCH 2/5] PDF Metadata: Switch to using PoDoFp to read/write PDF metadata. On linux, calibre will fall back to pdftk and pypdf. Linux distributors: calibre will only try to build the podofo extension if it detects the podofo header files in the directory pointed to by PODOFO_INC_DIR, defaults to /usr/include/podofo --- installer/linux/freeze.py | 1 + installer/osx/freeze.py | 5 ++ installer/windows/freeze.py | 8 +- pyqtdistutils.py | 38 +++++--- setup.py | 38 +++++--- src/calibre/constants.py | 2 +- src/calibre/ebooks/epub/from_any.py | 27 +++--- src/calibre/ebooks/metadata/pdf.py | 40 ++++++--- src/calibre/library/database2.py | 3 +- src/calibre/manual/faq.rst | 2 +- src/calibre/utils/podofo/__init__.py | 98 ++++++++++++++++++++ src/calibre/utils/podofo/podofo.sip | 128 +++++++++++++++++++++++++++ 12 files changed, 339 insertions(+), 51 deletions(-) create mode 100644 src/calibre/utils/podofo/__init__.py create mode 100644 src/calibre/utils/podofo/podofo.sip diff --git a/installer/linux/freeze.py b/installer/linux/freeze.py index 8524172a72..31f645dff1 100644 --- a/installer/linux/freeze.py +++ b/installer/linux/freeze.py @@ -31,6 +31,7 @@ def freeze(): '/usr/lib/libsqlite3.so.0', '/usr/lib/libsqlite3.so.0', '/usr/lib/libmng.so.1', + '/usr/lib/libpodofo.so.0.6.99', '/lib/libz.so.1', '/lib/libbz2.so.1', '/lib/libbz2.so.1', diff --git a/installer/osx/freeze.py b/installer/osx/freeze.py index 1861596f61..75621da017 100644 --- a/installer/osx/freeze.py +++ b/installer/osx/freeze.py @@ -229,6 +229,11 @@ _check_symlinks_prescript() all_modules = main_modules['console'] + main_modules['gui'] all_functions = main_functions['console'] + main_functions['gui'] 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') if not os.path.exists(loader_path): os.mkdir(loader_path) diff --git a/installer/windows/freeze.py b/installer/windows/freeze.py index a4988c6703..f545f7e534 100644 --- a/installer/windows/freeze.py +++ b/installer/windows/freeze.py @@ -12,6 +12,7 @@ LIBUNRAR = 'C:\\Program Files\\UnrarDLL\\unrar.dll' PDFTOHTML = 'C:\\cygwin\\home\\kovid\\poppler-0.10.6\\rel\\pdftohtml.exe' IMAGEMAGICK_DIR = 'C:\\ImageMagick' PDFTK = 'C:\\pdftk.exe' +PODOFO = 'C:\\podofo' FONTCONFIG_DIR = 'C:\\fontconfig' 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+'.manifest', os.path.join(PY2EXE_DIR, os.path.basename(PDFTOHTML)+'.manifest')) - print '\tAdding pdftk' - shutil.copyfile(PDFTK, os.path.join(PY2EXE_DIR, os.path.basename(PDFTK))) + #print '\tAdding 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' for f in os.listdir(IMAGEMAGICK_DIR): diff --git a/pyqtdistutils.py b/pyqtdistutils.py index 0e53aaabfe..b91b011bc1 100644 --- a/pyqtdistutils.py +++ b/pyqtdistutils.py @@ -80,13 +80,16 @@ CONFIG += x86 ppc os.chdir(cwd) def build_sbf(self, sip, sbf, bdir): - print '\tBuilding spf...' + print '\tBuilding sbf...' 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, "-c", bdir, "-b", sbf, - '-I', self.pyqtcfg.pyqt_sip_dir, - ] + self.pyqtcfg.pyqt_sip_flags.split()+ + ] + pyqt_sip_flags + [sip]) def build_pyqt(self, bdir, sbf, ext, qtobjs, headers): @@ -94,9 +97,14 @@ CONFIG += x86 ppc build_file=sbf, dir=bdir, makefile='Makefile.pyqt', 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: makefile.extra_lib_dirs += WINDOWS_PYTHON makefile.extra_include_dirs = list(set(map(os.path.dirname, headers))) + makefile.extra_include_dirs += ext.include_dirs makefile.extra_lflags += qtobjs makefile.generate() cwd = os.getcwd() @@ -110,7 +118,7 @@ CONFIG += x86 ppc def build_extension(self, ext): self.inplace = True # Causes extensions to be built in the source tree - + fullname = self.get_ext_fullname(ext.name) if self.inplace: # ignore build-lib -- put the compiled extension into @@ -127,14 +135,14 @@ CONFIG += x86 ppc else: ext_filename = os.path.join(self.build_lib, 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): os.makedirs(bdir) - + if not isinstance(ext, PyQtExtension): if not iswindows: 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')] compile_args = '/c /nologo /Ox /MD /W3 /GX /DNDEBUG'.split() compile_args += ext.extra_compile_args @@ -147,7 +155,7 @@ CONFIG += x86 ppc objects.append(o) compiler = cc + ['/Tc'+f, '/Fo'+o] 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 += ['/LIBPATH:'+x for x in self.library_dirs] linker += [x+'.lib' for x in ext.libraries] @@ -156,9 +164,9 @@ CONFIG += x86 ppc for src in (out, out+'.manifest'): shutil.copyfile(src, os.path.join('src', 'calibre', 'plugins', os.path.basename(src))) return - - - + + + if not os.path.exists(bdir): os.makedirs(bdir) ext.sources2 = map(os.path.abspath, ext.sources) @@ -200,6 +208,14 @@ CONFIG += x86 ppc shutil.copyfile(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): """ Parse the sbf file specified to extract the name of the generated source diff --git a/setup.py b/setup.py index b0ff04a983..d7d0d6cb1c 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ from __future__ import with_statement __license__ = 'GPL v3' __copyright__ = '2008, Kovid Goyal ' -import sys, re, os, shutil, cStringIO, tempfile, subprocess, time +import sys, re, os, subprocess sys.path.append('src') iswindows = re.search('win(32|64)', sys.platform) isosx = 'darwin' in sys.platform @@ -54,10 +54,28 @@ if __name__ == '__main__': build_osx, upload_installers, upload_user_manual, \ upload_to_pypi, stage3, stage2, stage1, upload, \ upload_rss - + entry_points['console_scripts'].append( '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', sources=['src/calibre/utils/lzx/lzxmodule.c', 'src/calibre/utils/lzx/compressor.c', @@ -65,12 +83,12 @@ if __name__ == '__main__': 'src/calibre/utils/lzx/lzc.c', 'src/calibre/utils/lzx/lzxc.c'], include_dirs=['src/calibre/utils/lzx']), - + Extension('calibre.plugins.msdes', sources=['src/calibre/utils/msdes/msdesmodule.c', 'src/calibre/utils/msdes/des.c'], include_dirs=['src/calibre/utils/msdes']), - + PyQtExtension('calibre.plugins.pictureflow', ['src/calibre/gui2/pictureflow/pictureflow.cpp', 'src/calibre/gui2/pictureflow/pictureflow.h'], @@ -81,7 +99,7 @@ if __name__ == '__main__': ext_modules.append(Extension('calibre.plugins.winutil', sources=['src/calibre/utils/windows/winutil.c'], libraries=['shell32', 'setupapi'], - include_dirs=os.environ.get('INCLUDE', + include_dirs=os.environ.get('INCLUDE', 'C:/WinDDK/6001.18001/inc/api/;' 'C:/WinDDK/6001.18001/inc/crt/').split(';'), extra_compile_args=['/X'] @@ -91,7 +109,7 @@ if __name__ == '__main__': sources=['src/calibre/devices/usbobserver/usbobserver.c'], extra_link_args=['-framework', 'IOKit']) ) - + if not iswindows: plugins = ['plugins/%s.so'%(x.name.rpartition('.')[-1]) for x in ext_modules] else: @@ -99,7 +117,7 @@ if __name__ == '__main__': ['plugins/%s.pyd.manifest'%(x.name.rpartition('.')[-1]) \ for x in ext_modules if 'pictureflow' not in x.name] - + setup( name = APPNAME, packages = find_packages('src'), @@ -152,9 +170,9 @@ if __name__ == '__main__': 'Topic :: System :: Hardware :: Hardware Drivers' ], cmdclass = { - 'build_ext' : build_ext, + 'build_ext' : build_ext, 'build' : build, - 'build_py' : build_py, + 'build_py' : build_py, 'pot' : pot, 'manual' : manual, 'resources' : resources, diff --git a/src/calibre/constants.py b/src/calibre/constants.py index cf4c6a28f5..d5958712f1 100644 --- a/src/calibre/constants.py +++ b/src/calibre/constants.py @@ -53,7 +53,7 @@ if plugins is None: plugin_path = getattr(pkg_resources, 'resource_filename')('calibre', 'plugins') sys.path.insert(0, plugin_path) - for plugin in ['pictureflow', 'lzx', 'msdes'] + \ + for plugin in ['pictureflow', 'lzx', 'msdes', 'podofo'] + \ (['winutil'] if iswindows else []) + \ (['usbobserver'] if isosx else []): try: diff --git a/src/calibre/ebooks/epub/from_any.py b/src/calibre/ebooks/epub/from_any.py index 9a8e251108..a3e266991f 100644 --- a/src/calibre/ebooks/epub/from_any.py +++ b/src/calibre/ebooks/epub/from_any.py @@ -80,7 +80,7 @@ def fb22opf(path, tdir, opts): from calibre.ebooks.lrf.fb2.convert_from import to_html print 'Converting FB2 to HTML...' return to_html(path, tdir) - + def rtf2opf(path, tdir, opts): from calibre.ebooks.lrf.rtf.convert_from import generate_html generate_html(path, tdir) @@ -89,6 +89,7 @@ def rtf2opf(path, tdir, opts): def txt2opf(path, tdir, opts): from calibre.ebooks.lrf.txt.convert_from import generate_html generate_html(path, opts.encoding, tdir) + opts.encoding = 'utf-8' return os.path.join(tdir, 'metadata.opf') def pdf2opf(path, tdir, opts): @@ -110,11 +111,11 @@ def epub2opf(path, tdir, opts): if opf and os.path.exists(encfile): if not process_encryption(encfile, opf): raise DRMError(os.path.basename(path)) - + if opf is None: raise ValueError('%s is not a valid EPUB file'%path) return opf - + def odt2epub(path, tdir, opts): from calibre.ebooks.odt.to_oeb import Extract opts.encoding = 'utf-8' @@ -132,13 +133,13 @@ MAP = { 'epub' : epub2opf, 'odt' : odt2epub, } -SOURCE_FORMATS = ['lit', 'mobi', 'prc', 'azw', 'fb2', 'odt', 'rtf', +SOURCE_FORMATS = ['lit', 'mobi', 'prc', 'azw', 'fb2', 'odt', 'rtf', 'txt', 'pdf', 'rar', 'zip', 'oebzip', 'htm', 'html', 'epub'] def unarchive(path, tdir): extract(path, tdir) files = list(walk(tdir)) - + for ext in ['opf'] + list(MAP.keys()): for f in files: if f.lower().endswith('.'+ext): @@ -147,32 +148,32 @@ def unarchive(path, tdir): return f, ext return find_html_index(files) -def any2epub(opts, path, notification=None, create_epub=True, +def any2epub(opts, path, notification=None, create_epub=True, oeb_cover=False, extract_to=None): path = run_plugins_on_preprocess(path) ext = os.path.splitext(path)[1] if not ext: raise ValueError('Unknown file type: '+path) ext = ext.lower()[1:] - + if opts.output is None: opts.output = os.path.splitext(os.path.basename(path))[0]+'.epub' - + with nested(TemporaryDirectory('_any2epub1'), TemporaryDirectory('_any2epub2')) as (tdir1, tdir2): if ext in ['rar', 'zip', 'oebzip']: path, ext = unarchive(path, tdir1) print 'Found %s file in archive'%(ext.upper()) - + if ext in MAP.keys(): path = MAP[ext](path, tdir2, opts) ext = 'opf' - - + + if re.match(r'((x){0,1}htm(l){0,1})|opf', ext) is None: raise ValueError('Conversion from %s is not supported'%ext.upper()) - + print 'Creating EPUB file...' - html2epub(path, opts, notification=notification, + html2epub(path, opts, notification=notification, create_epub=create_epub, oeb_cover=oeb_cover, extract_to=extract_to) diff --git a/src/calibre/ebooks/metadata/pdf.py b/src/calibre/ebooks/metadata/pdf.py index b6bf425d9f..20ba98ff54 100644 --- a/src/calibre/ebooks/metadata/pdf.py +++ b/src/calibre/ebooks/metadata/pdf.py @@ -6,13 +6,35 @@ __copyright__ = '2008, Kovid Goyal ' import sys, os, cStringIO from threading import Thread -from calibre import FileWrapper from calibre.ebooks.metadata import MetaInformation, authors_to_string, get_parser -from pyPdf import PdfFileReader, PdfFileWriter 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): + try: + return podofo_get_metadata(stream) + except: + return get_metadata_pypdf(stream) + +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 + from calibre import FileWrapper mi = MetaInformation(_('Unknown'), [_('Unknown')]) stream.seek(0) try: @@ -48,18 +70,12 @@ class MetadataWriter(Thread): except RuntimeError: pass -def set_metadata(stream, mi): - stream.seek(0) - try: - pdftk_set_metadata(stream, mi) - except: - pass - else: - return - +def set_metadata_pypdf(stream, mi): # 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 # could cause some issues. + + from pyPdf import PdfFileReader, PdfFileWriter raw = cStringIO.StringIO(stream.read()) orig_pdf = PdfFileReader(raw) @@ -73,7 +89,7 @@ def set_metadata(stream, mi): out_pdf.addPage(page) 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 writer.join() if out_pdf.killed: diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py index bc94d4faa3..4247e0cad3 100644 --- a/src/calibre/library/database2.py +++ b/src/calibre/library/database2.py @@ -402,7 +402,8 @@ class LibraryDatabase2(LibraryDatabase): def get_property(idx, index_is_id=False, loc=-1): 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', 'publisher', 'rating', 'series', 'series_index', 'tags', diff --git a/src/calibre/manual/faq.rst b/src/calibre/manual/faq.rst index 0c02ef2925..717effd455 100644 --- a/src/calibre/manual/faq.rst +++ b/src/calibre/manual/faq.rst @@ -220,7 +220,7 @@ Post any output you see in a help message on the `Forum +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 +using namespace PoDoFo; +%End + public: + PdfObject(); + +}; + +class PdfInfo { +%TypeHeaderCode +#define USING_SHARED_PODOFO +#include +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 +using namespace PoDoFo; +%End + public: + PdfOutputDevice(char *, long); + unsigned long GetLength(); + unsigned long Tell(); + void Flush(); +}; + + +class PdfMemDocument { +%TypeHeaderCode +#define USING_SHARED_PODOFO +#include +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 +%End +%RaiseCode + const char *detail = sipExceptionRef.what(); + + SIP_BLOCK_THREADS + PyErr_SetString(sipException_PoDoFo_PdfError, detail); + SIP_UNBLOCK_THREADS +%End +}; + From a4243033e0ce0afa07cbb19b51ed8bb0d40211a4 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 10 May 2009 17:54:18 -0700 Subject: [PATCH 3/5] IGN:... --- src/calibre/trac/donations/server.py | 143 ++++++++++++++++++--------- 1 file changed, 95 insertions(+), 48 deletions(-) diff --git a/src/calibre/trac/donations/server.py b/src/calibre/trac/donations/server.py index 2afc3adeca..3bf822b893 100644 --- a/src/calibre/trac/donations/server.py +++ b/src/calibre/trac/donations/server.py @@ -6,7 +6,7 @@ __docformat__ = 'restructuredtext en' ''' 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 math import sqrt os.environ['HOME'] = '/tmp' @@ -15,7 +15,6 @@ matplotlib.use('Agg') import matplotlib.pyplot as plt import matplotlib.dates as mdates - import cherrypy from lxml import etree @@ -33,7 +32,13 @@ def range_for_month(year, month): def range_for_year(year): 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): if re.match('(?i)(US|USA|America)', country): @@ -67,14 +72,14 @@ def rationalize_country(country): return country class Record(object): - + def __init__(self, email, country, amount, date, name): self.email = email self.country = country self.amount = amount self.date = date self.name = name - + def __str__(self): return ''%\ (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): return self.name + ': %.2f%%'%self.percent - + def __cmp__(self, other): return cmp(self.total, other.total) @@ -118,7 +123,7 @@ class Stats: if r.date not in self.days.keys(): self.days[r.date] = [] self.days[r.date].append(r) - + self.min, self.max = start, end self.period = (self.max - self.min) + timedelta(days=1) daily_totals = [] @@ -139,7 +144,24 @@ class Stats: self.countries[r.country].append(r) for country in self.countries.values(): 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): buf = cStringIO.StringIO() print >>buf, '\tTotal: %.2f'%self.total @@ -149,7 +171,7 @@ class Stats: for c in self.countries.values(): print >>buf, '\t\t', c return buf.getvalue() - + def to_html(self, num_of_countries=sys.maxint): countries = sorted(self.countries.values(), cmp=cmp, reverse=True)[:num_of_countries] crows = ['%s%.2f %%'%(c.name, c.percent) for c in countries] @@ -168,32 +190,48 @@ class Stats:
%(ctable)s - ''')%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), - dd=self.daily_deviation, ad=self.average_deviation, - dpd=len(self.totals)/float(self.period.days), + dd=self.daily_deviation, ad=self.average_deviation, + dpd=len(self.totals)/float(self.period.days), min=self.min.isoformat(), max=self.max.isoformat()) - + def expose(func): - + def do(self, *args, **kwargs): dict.update(cherrypy.response.headers, {'Server':'Donations_server/1.0'}) return func(self, *args, **kwargs) - + return cherrypy.expose(do) class Server(object): - + TRENDS = '/tmp/donations_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'): self.apache = apache self.document_root = root self.data_file = data_file 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): stats = self.get_slice(date.today()-timedelta(days=days-1), date.today()) 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 Average contribution: $%(ac).2f \u00b1 %(ad).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, ad=stats.average_deviation, dpd=len(stats.totals)/float(stats.period.days), ) text = ax.annotate(text, (0.5, 0.65), textcoords='axes fraction') fig.savefig(self.MONTH_TRENDS) - + def calculate_trend(self): def months(start, end): pos = range_for_month(start.year, start.month)[0] @@ -253,7 +291,7 @@ Donors per day: %(dpd).2f fig.savefig(self.TRENDS) #plt.show() - + def read_records(self): self.tree = etree.parse(self.data_file) self.last_read_time = time.time() @@ -269,21 +307,22 @@ Donors per day: %(dpd).2f self.earliest, self.latest = min_date, max_date self.calculate_trend() self.calculate_month_trend() - + self.calculate_daily_averages() + 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], start_date, end_date) return stats - + def month(self, year, month): return self.get_slice(*range_for_month(year, month)) - + def year(self, year): return self.get_slice(*range_for_year(year)) - + def range_to_date(self, raw): return date(*map(int, raw.split('-'))) - + def build_page(self, period_type, data): if os.stat(self.data_file).st_mtime >= self.last_read_time: self.read_records() @@ -294,7 +333,7 @@ Donors per day: %(dpd).2f yy = data if period_type == 'year' else year rl = data[0] if period_type == 'range' else '' rr = data[1] if period_type == 'range' else '' - + def build_month_list(current): months = [] for i in range(1, 13): @@ -302,7 +341,7 @@ Donors per day: %(dpd).2f sel = 'selected="selected"' if i == current else '' months.append(''%(i, sel, month)) return months - + def build_year_list(current): all_years = sorted(range(self.earliest.year, self.latest.year+1, 1)) if current not in all_years: @@ -312,11 +351,11 @@ Donors per day: %(dpd).2f sel = 'selected="selected"' if year == current else '' years.append(''%(year, sel, year)) return years - + mmlist = ''%('\n'.join(build_month_list(mm))) mylist = ''%('\n'.join(build_year_list(my))) yylist = ''%('\n'.join(build_year_list(yy))) - + if period_type == 'month': range_stats = range_for_month(my, mm) elif period_type == 'year': @@ -332,9 +371,9 @@ Donors per day: %(dpd).2f range_stats = '
Invalid input:\n%s
'%err else: range_stats = self.get_slice(*range_stats).to_html(num_of_countries=10) - + today = self.get_slice(date.today(), date.today()) - + return textwrap.dedent('''\ @@ -365,7 +404,7 @@ Donors per day: %(dpd).2f if ((dayobj.getMonth()+1!=monthfield)||(dayobj.getDate()!=dayfield)||(dayobj.getFullYear()!=yearfield)) return false; return true; } - + function check_period_form(form) { if (form.period_type[2].checked) { if (!test_date(form.range_left.value)) { @@ -381,11 +420,11 @@ Donors per day: %(dpd).2f } return true; } - + function is_empty(val) { return val.trim().length == 0 } - + function check_add_form(form) { var test_amount = /[\.0-9]+/; if (is_empty(form.email.value)) { @@ -409,8 +448,8 @@ Donors per day: %(dpd).2f return false; } return true; - } - + } + function rationalize_periods() { var form = document.forms[0]; var disabled = !form.period_type[0].checked; @@ -438,7 +477,7 @@ Donors per day: %(dpd).2f

Donations to date

%(todate)s - +

Donations in period

@@ -463,10 +502,13 @@ Donors per day: %(dpd).2f
-

Income trends for the last year

Income trends -

Income trends for the last 31 days

+

Income trends for the last year

Month income trend +

Income trends for the last 31 days

+ Daily average
+                    income trend +

Income trends since records started

@@ -479,26 +521,31 @@ Donors per day: %(dpd).2f rl=rl, rr=rr, range_stats=range_stats, root=self.document_root, today=today.total ) - + @expose def index(self): month = date.today().month year = date.today().year cherrypy.response.headers['Content-Type'] = 'application/xhtml+xml' return self.build_page('month', (year, month)) - + @expose def trend_png(self): cherrypy.response.headers['Content-Type'] = 'image/png' return open(self.TRENDS, 'rb').read() - + @expose def month_trend_png(self): cherrypy.response.headers['Content-Type'] = 'image/png' return open(self.MONTH_TRENDS, 'rb').read() - + @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=''): if period_type == '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) cherrypy.response.headers['Content-Type'] = 'application/xhtml+xml' return self.build_page(period_type, data) - + def config(): config = { 'global': { @@ -527,9 +574,9 @@ def apache_start(): 'environment' : 'production', '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()) - + def main(args=sys.argv): server = Server() From aeae9e613ad405fadb3d45a7268379a9f33a6f3c Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 11 May 2009 02:03:03 -0700 Subject: [PATCH 4/5] IGN:Windows support for the BeBook --- src/calibre/devices/bebook/driver.py | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/src/calibre/devices/bebook/driver.py b/src/calibre/devices/bebook/driver.py index 1b81fae27c..a0130cd7f8 100644 --- a/src/calibre/devices/bebook/driver.py +++ b/src/calibre/devices/bebook/driver.py @@ -14,9 +14,9 @@ class BEBOOK(USBMS): PRODUCT_ID = [0x8803, 0x6803] BCD = [0x312] - VENDOR_NAME = 'BEBOOK' - WINDOWS_MAIN_MEM = 'BEBOOK_INTERNAL_MEMORY' - WINDOWS_CARD_MEM = 'BEBOOK_STORAGE_CARD' + VENDOR_NAME = 'LINUX' + WINDOWS_MAIN_MEM = 'FILE-STOR_GADGET' + WINDOWS_CARD_MEM = 'FILE-STOR_GADGET' OSX_MAIN_MEM = 'BeBook Internal Memory' OSX_CARD_MEM = 'BeBook Storage Card' @@ -28,15 +28,23 @@ class BEBOOK(USBMS): FDI_LUNS = {'lun0':1, 'lun1':0, 'lun2':2} + def windows_sort_drives(self, drives): + main = drives.get('main', None) + card = drives.get('card', None) + if card and main and card < main: + drives['main'] = card + drives['card'] = main + + return drives + + + class BEBOOKMINI(BEBOOK): VENDOR_ID = [0x0492] PRODUCT_ID = [0x8813] BCD = [0x319] - WINDOWS_MAIN_MEM = 'BEBOOKMINI_INTERNAL_MEMORY' - WINDOWS_CARD_MEM = 'BEBOOKMINI_STORAGE_CARD' - OSX_MAIN_MEM = 'BeBook Mini Internal Memory' OSX_CARD_MEM = 'BeBook Mini Storage Card' From 040fe5a5530bca847a91cb4772e0d20ca95fe3ed Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 11 May 2009 02:03:41 -0700 Subject: [PATCH 5/5] Beginnings of new IPC framework --- src/calibre/devices/eb600/driver.py | 2 +- src/calibre/devices/jetbook/driver.py | 10 +- src/calibre/gui2/convert/gui_conversion.py | 11 +- src/calibre/gui2/tools.py | 12 +- src/calibre/ptempfile.py | 1 + src/calibre/utils/ipc/__init__.py | 10 ++ src/calibre/utils/ipc/launch.py | 150 +++++++++++++++++++++ src/calibre/utils/ipc/server.py | 10 ++ src/calibre/utils/ipc/worker.py | 78 +++++++++++ 9 files changed, 270 insertions(+), 14 deletions(-) create mode 100644 src/calibre/utils/ipc/__init__.py create mode 100644 src/calibre/utils/ipc/launch.py create mode 100644 src/calibre/utils/ipc/server.py create mode 100644 src/calibre/utils/ipc/worker.py diff --git a/src/calibre/devices/eb600/driver.py b/src/calibre/devices/eb600/driver.py index c390ce582f..b42c77f172 100644 --- a/src/calibre/devices/eb600/driver.py +++ b/src/calibre/devices/eb600/driver.py @@ -43,7 +43,7 @@ class EB600(USBMS): def windows_sort_drives(self, drives): main = drives.get('main', None) - card = drives.get('card', None) + card = drives.get('carda', None) if card and main and card < main: drives['main'] = card drives['carda'] = main diff --git a/src/calibre/devices/jetbook/driver.py b/src/calibre/devices/jetbook/driver.py index 05da539dd8..199566357b 100644 --- a/src/calibre/devices/jetbook/driver.py +++ b/src/calibre/devices/jetbook/driver.py @@ -76,7 +76,7 @@ class JETBOOK(USBMS): if newpath == path: newpath = os.path.join(newpath, author, title) - + if not os.path.exists(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(1.0, _('Transferring books to device...')) - + return zip(paths, cycle([on_card])) @classmethod @@ -109,7 +109,7 @@ class JETBOOK(USBMS): return txt.decode(sys.getfilesystemencoding(), 'replace') return txt - + from calibre.devices.usbms.driver import metadata_from_formats mi = metadata_from_formats([path]) @@ -126,10 +126,10 @@ class JETBOOK(USBMS): def windows_sort_drives(self, drives): main = drives.get('main', None) - card = drives.get('card', None) + card = drives.get('carda', None) if card and main and card < main: drives['main'] = card - drives['card'] = main + drives['carda'] = main return drives diff --git a/src/calibre/gui2/convert/gui_conversion.py b/src/calibre/gui2/convert/gui_conversion.py index 8f25acb7be..f8da2aa824 100644 --- a/src/calibre/gui2/convert/gui_conversion.py +++ b/src/calibre/gui2/convert/gui_conversion.py @@ -4,12 +4,15 @@ __license__ = 'GPL 3' __copyright__ = '2009, John Schember ' __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.customize.conversion import OptionRecommendation -def gui_convert(input, output, recommendations, notification): - plumber = Plumber(input, output, Log(), notification) +def gui_convert(input, output, recommendations, notification=DummyReporter()): + recommendations = list(recommendations) + recommendations.append(('verbose', 2, OptionRecommendation.HIGH)) + plumber = Plumber(input, output, Log(), report_progress=notification) plumber.merge_ui_recommendations(recommendations) - + plumber.run() diff --git a/src/calibre/gui2/tools.py b/src/calibre/gui2/tools.py index 4f3f837679..711c10943b 100644 --- a/src/calibre/gui2/tools.py +++ b/src/calibre/gui2/tools.py @@ -16,6 +16,7 @@ from calibre.gui2 import warning_dialog from calibre.gui2.convert.single import NoSupportedInputFormats from calibre.gui2.convert.single import Config as SingleConfig from calibre.gui2.convert.bulk import BulkConfig +from calibre.customize.conversion import OptionRecommendation from calibre.utils.config import prefs 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() pt = PersistentTemporaryFile(suffix='_recipe_out.%s'%fmt.lower()) pt.close() - args = ['ebook-convert', script, pt.name, '-vv'] + recs = [] + args = [script, pt.name, recs] if recipe.needs_subscription: x = config.get('recipe_account_info_%s'%recipe.id, False) if not x: raise ValueError(_('You must set a username and password for %s')%recipe.title) - args.extend(['--username', x[0], '--password', x[1]]) - - return 'ebook-convert', [args], _('Fetch news from ')+recipe.title, fmt.upper(), [pt] + recs.append(('username', x[0], OptionRecommendation.HIGH)) + recs.append(('password', x[1], OptionRecommendation.HIGH)) + + + return 'gui_convert', args, _('Fetch news from ')+recipe.title, fmt.upper(), [pt] diff --git a/src/calibre/ptempfile.py b/src/calibre/ptempfile.py index 831c6ccf6c..fe69949f99 100644 --- a/src/calibre/ptempfile.py +++ b/src/calibre/ptempfile.py @@ -31,6 +31,7 @@ class PersistentTemporaryFile(object): dir=dir) self._file = os.fdopen(fd, mode) self._name = name + self._fd = fd atexit.register(cleanup, name) def __getattr__(self, name): diff --git a/src/calibre/utils/ipc/__init__.py b/src/calibre/utils/ipc/__init__.py new file mode 100644 index 0000000000..3d1a86922e --- /dev/null +++ b/src/calibre/utils/ipc/__init__.py @@ -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 ' +__docformat__ = 'restructuredtext en' + + + diff --git a/src/calibre/utils/ipc/launch.py b/src/calibre/utils/ipc/launch.py new file mode 100644 index 0000000000..6c0ba46885 --- /dev/null +++ b/src/calibre/utils/ipc/launch.py @@ -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 ' +__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 + + + diff --git a/src/calibre/utils/ipc/server.py b/src/calibre/utils/ipc/server.py new file mode 100644 index 0000000000..3d1a86922e --- /dev/null +++ b/src/calibre/utils/ipc/server.py @@ -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 ' +__docformat__ = 'restructuredtext en' + + + diff --git a/src/calibre/utils/ipc/worker.py b/src/calibre/utils/ipc/worker.py new file mode 100644 index 0000000000..75b42c9a25 --- /dev/null +++ b/src/calibre/utils/ipc/worker.py @@ -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 ' +__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())