mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Sync to pluginize
This commit is contained in:
commit
8577e979aa
@ -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',
|
||||||
|
@ -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)
|
||||||
|
@ -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):
|
||||||
|
@ -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
|
||||||
|
20
setup.py
20
setup.py
@ -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',
|
||||||
|
@ -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:
|
||||||
|
@ -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'
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
@ -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()
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
@ -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)
|
||||||
|
|
||||||
|
|
||||||
|
@ -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]
|
||||||
|
|
||||||
|
|
||||||
|
@ -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',
|
||||||
|
@ -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?
|
||||||
|
@ -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):
|
||||||
|
@ -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()
|
||||||
|
10
src/calibre/utils/ipc/__init__.py
Normal file
10
src/calibre/utils/ipc/__init__.py
Normal 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'
|
||||||
|
|
||||||
|
|
||||||
|
|
150
src/calibre/utils/ipc/launch.py
Normal file
150
src/calibre/utils/ipc/launch.py
Normal 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
|
||||||
|
|
||||||
|
|
||||||
|
|
10
src/calibre/utils/ipc/server.py
Normal file
10
src/calibre/utils/ipc/server.py
Normal 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'
|
||||||
|
|
||||||
|
|
||||||
|
|
78
src/calibre/utils/ipc/worker.py
Normal file
78
src/calibre/utils/ipc/worker.py
Normal 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())
|
98
src/calibre/utils/podofo/__init__.py
Normal file
98
src/calibre/utils/podofo/__init__.py
Normal 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'
|
||||||
|
|
||||||
|
|
128
src/calibre/utils/podofo/podofo.sip
Normal file
128
src/calibre/utils/podofo/podofo.sip
Normal 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
|
||||||
|
};
|
||||||
|
|
Loading…
x
Reference in New Issue
Block a user