mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-08 02:34:06 -04:00
IGN:Sync to trunk
This commit is contained in:
commit
a2c24c81a0
@ -74,7 +74,7 @@ f.write(hook_script)
|
|||||||
sys.path.insert(0, CALIBRESRC)
|
sys.path.insert(0, CALIBRESRC)
|
||||||
from calibre.linux import entry_points
|
from calibre.linux import entry_points
|
||||||
|
|
||||||
executables, scripts = ['calibre_postinstall', 'parallel'], \
|
executables, scripts = ['calibre_postinstall', 'calibre-parallel'], \
|
||||||
[os.path.join(CALIBRESRC, 'calibre', 'linux.py'), os.path.join(CALIBRESRC, 'calibre', 'parallel.py')]
|
[os.path.join(CALIBRESRC, 'calibre', 'linux.py'), os.path.join(CALIBRESRC, 'calibre', 'parallel.py')]
|
||||||
|
|
||||||
for entry in entry_points['console_scripts'] + entry_points['gui_scripts']:
|
for entry in entry_points['console_scripts'] + entry_points['gui_scripts']:
|
||||||
|
@ -51,6 +51,7 @@ def _check_symlinks_prescript():
|
|||||||
import os
|
import os
|
||||||
scripts = %(sp)s
|
scripts = %(sp)s
|
||||||
links = %(sp)s
|
links = %(sp)s
|
||||||
|
fonts_conf = %(sp)s
|
||||||
os.setuid(0)
|
os.setuid(0)
|
||||||
for s, l in zip(scripts, links):
|
for s, l in zip(scripts, links):
|
||||||
if os.path.lexists(l):
|
if os.path.lexists(l):
|
||||||
@ -59,6 +60,11 @@ for s, l in zip(scripts, links):
|
|||||||
omask = os.umask(022)
|
omask = os.umask(022)
|
||||||
os.symlink(s, l)
|
os.symlink(s, l)
|
||||||
os.umask(omask)
|
os.umask(omask)
|
||||||
|
if not os.path.exists('/etc/fonts/fonts.conf'):
|
||||||
|
print 'Creating default fonts.conf'
|
||||||
|
if not os.path.exists('/etc/fonts'):
|
||||||
|
os.makedirs('/etc/fonts')
|
||||||
|
os.link(fonts_conf, '/etc/fonts/fonts.conf')
|
||||||
"""
|
"""
|
||||||
|
|
||||||
dest_path = %(dest_path)s
|
dest_path = %(dest_path)s
|
||||||
@ -66,6 +72,7 @@ for s, l in zip(scripts, links):
|
|||||||
scripts = %(scripts)s
|
scripts = %(scripts)s
|
||||||
links = [os.path.join(dest_path, i) for i in scripts]
|
links = [os.path.join(dest_path, i) for i in scripts]
|
||||||
scripts = [os.path.join(resources_path, 'loaders', i) for i in scripts]
|
scripts = [os.path.join(resources_path, 'loaders', i) for i in scripts]
|
||||||
|
fonts_conf = os.path.join(resources_path, 'fonts.conf')
|
||||||
|
|
||||||
bad = False
|
bad = False
|
||||||
for s, l in zip(scripts, links):
|
for s, l in zip(scripts, links):
|
||||||
@ -73,10 +80,12 @@ for s, l in zip(scripts, links):
|
|||||||
continue
|
continue
|
||||||
bad = True
|
bad = True
|
||||||
break
|
break
|
||||||
|
if not bad:
|
||||||
|
bad = os.path.exists('/etc/fonts/fonts.conf')
|
||||||
if bad:
|
if bad:
|
||||||
auth = Authorization(destroyflags=(kAuthorizationFlagDestroyRights,))
|
auth = Authorization(destroyflags=(kAuthorizationFlagDestroyRights,))
|
||||||
fd, name = tempfile.mkstemp('.py')
|
fd, name = tempfile.mkstemp('.py')
|
||||||
os.write(fd, AUTHTOOL %(pp)s (sys.executable, repr(scripts), repr(links)))
|
os.write(fd, AUTHTOOL %(pp)s (sys.executable, repr(scripts), repr(links), repr(fonts_conf)))
|
||||||
os.close(fd)
|
os.close(fd)
|
||||||
os.chmod(name, 0700)
|
os.chmod(name, 0700)
|
||||||
try:
|
try:
|
||||||
@ -276,10 +285,12 @@ sys.frameworks_dir = os.path.join(os.path.dirname(os.environ['RESOURCEPATH']), '
|
|||||||
f.write('src/calibre/gui2/main.py', 'calibre/gui2/main.py')
|
f.write('src/calibre/gui2/main.py', 'calibre/gui2/main.py')
|
||||||
f.close()
|
f.close()
|
||||||
print
|
print
|
||||||
|
print 'Adding default fonts.conf'
|
||||||
|
open(os.path.join(self.dist_dir, APPNAME+'.app', 'Contents', 'Resources', 'fonts.conf'), 'wb').write(open('/etc/fonts/fonts.conf').read())
|
||||||
|
print
|
||||||
print 'Building disk image'
|
print 'Building disk image'
|
||||||
BuildAPP.makedmg(os.path.join(self.dist_dir, APPNAME+'.app'), APPNAME+'-'+VERSION)
|
BuildAPP.makedmg(os.path.join(self.dist_dir, APPNAME+'.app'), APPNAME+'-'+VERSION)
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'src'))
|
sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'src'))
|
||||||
sys.argv[1:2] = ['py2app']
|
sys.argv[1:2] = ['py2app']
|
||||||
@ -295,7 +306,7 @@ def main():
|
|||||||
'iconfile' : 'icons/library.icns',
|
'iconfile' : 'icons/library.icns',
|
||||||
'frameworks': ['libusb.dylib', 'libunrar.dylib'],
|
'frameworks': ['libusb.dylib', 'libunrar.dylib'],
|
||||||
'includes' : ['sip', 'pkg_resources', 'PyQt4.QtXml',
|
'includes' : ['sip', 'pkg_resources', 'PyQt4.QtXml',
|
||||||
'PyQt4.QtSvg',
|
'PyQt4.QtSvg', 'PyQt4.QtWebKit',
|
||||||
'mechanize', 'ClientForm', 'usbobserver',
|
'mechanize', 'ClientForm', 'usbobserver',
|
||||||
'genshi', 'calibre.web.feeds.recipes.*',
|
'genshi', 'calibre.web.feeds.recipes.*',
|
||||||
'keyword', 'codeop', 'pydoc'],
|
'keyword', 'codeop', 'pydoc'],
|
||||||
|
@ -88,6 +88,9 @@ def setup_cli_handlers(logger, level):
|
|||||||
handler = logging.StreamHandler(sys.stderr)
|
handler = logging.StreamHandler(sys.stderr)
|
||||||
handler.setLevel(logging.DEBUG)
|
handler.setLevel(logging.DEBUG)
|
||||||
handler.setFormatter(logging.Formatter('[%(levelname)s] %(filename)s:%(lineno)s: %(message)s'))
|
handler.setFormatter(logging.Formatter('[%(levelname)s] %(filename)s:%(lineno)s: %(message)s'))
|
||||||
|
for hdlr in logger.handlers:
|
||||||
|
if hdlr.__class__ == handler.__class__:
|
||||||
|
logger.removeHandler(hdlr)
|
||||||
logger.addHandler(handler)
|
logger.addHandler(handler)
|
||||||
|
|
||||||
class CustomHelpFormatter(IndentedHelpFormatter):
|
class CustomHelpFormatter(IndentedHelpFormatter):
|
||||||
|
@ -353,9 +353,16 @@ class PRS505(Device):
|
|||||||
def upload_books(self, files, names, on_card=False, end_session=True):
|
def upload_books(self, files, names, on_card=False, end_session=True):
|
||||||
path = os.path.join(self._card_prefix, self.CARD_PATH_PREFIX) if on_card \
|
path = os.path.join(self._card_prefix, self.CARD_PATH_PREFIX) if on_card \
|
||||||
else os.path.join(self._main_prefix, 'database', 'media', 'books')
|
else os.path.join(self._main_prefix, 'database', 'media', 'books')
|
||||||
infiles = [file if hasattr(file, 'read') else open(file, 'rb') for file in files]
|
|
||||||
for f in infiles: f.seek(0, 2)
|
def get_size(obj):
|
||||||
sizes = [f.tell() for f in infiles]
|
if hasattr(obj, 'seek'):
|
||||||
|
obj.seek(0, 2)
|
||||||
|
size = obj.tell()
|
||||||
|
obj.seek(0)
|
||||||
|
return size
|
||||||
|
return os.path.getsize(obj)
|
||||||
|
|
||||||
|
sizes = map(get_size, files)
|
||||||
size = sum(sizes)
|
size = sum(sizes)
|
||||||
space = self.free_space()
|
space = self.free_space()
|
||||||
mspace = space[0]
|
mspace = space[0]
|
||||||
@ -370,13 +377,18 @@ class PRS505(Device):
|
|||||||
paths, ctimes = [], []
|
paths, ctimes = [], []
|
||||||
|
|
||||||
names = iter(names)
|
names = iter(names)
|
||||||
for infile in infiles:
|
for infile in files:
|
||||||
|
close = False
|
||||||
|
if not hasattr(infile, 'read'):
|
||||||
|
infile, close = open(infile, 'rb'), True
|
||||||
infile.seek(0)
|
infile.seek(0)
|
||||||
name = names.next()
|
name = names.next()
|
||||||
paths.append(os.path.join(path, name))
|
paths.append(os.path.join(path, name))
|
||||||
if not os.path.exists(os.path.dirname(paths[-1])):
|
if not os.path.exists(os.path.dirname(paths[-1])):
|
||||||
os.makedirs(os.path.dirname(paths[-1]))
|
os.makedirs(os.path.dirname(paths[-1]))
|
||||||
self.put_file(infile, paths[-1], replace_file=True)
|
self.put_file(infile, paths[-1], replace_file=True)
|
||||||
|
if close:
|
||||||
|
infile.close()
|
||||||
ctimes.append(os.path.getctime(paths[-1]))
|
ctimes.append(os.path.getctime(paths[-1]))
|
||||||
return zip(paths, sizes, ctimes, cycle([on_card]))
|
return zip(paths, sizes, ctimes, cycle([on_card]))
|
||||||
|
|
||||||
|
@ -121,7 +121,6 @@ def option_parser(usage, gui_mode=False):
|
|||||||
laf.add_option('--ignore-colors', action='store_true', default=False, dest='ignore_colors',
|
laf.add_option('--ignore-colors', action='store_true', default=False, dest='ignore_colors',
|
||||||
help=_('Render all content as black on white instead of the colors specified by the HTML or CSS.'))
|
help=_('Render all content as black on white instead of the colors specified by the HTML or CSS.'))
|
||||||
|
|
||||||
|
|
||||||
page = parser.add_option_group('PAGE OPTIONS')
|
page = parser.add_option_group('PAGE OPTIONS')
|
||||||
profiles = profile_map.keys()
|
profiles = profile_map.keys()
|
||||||
page.add_option('-p', '--profile', default=PRS500_PROFILE, dest='profile', type='choice',
|
page.add_option('-p', '--profile', default=PRS500_PROFILE, dest='profile', type='choice',
|
||||||
@ -139,6 +138,11 @@ def option_parser(usage, gui_mode=False):
|
|||||||
help=_('''Top margin of page. Default is %default px.'''))
|
help=_('''Top margin of page. Default is %default px.'''))
|
||||||
page.add_option('--bottom-margin', default=0, dest='bottom_margin', type='int',
|
page.add_option('--bottom-margin', default=0, dest='bottom_margin', type='int',
|
||||||
help=_('''Bottom margin of page. Default is %default px.'''))
|
help=_('''Bottom margin of page. Default is %default px.'''))
|
||||||
|
page.add_option('--render-tables-as-images', default=False, action='store_true',
|
||||||
|
help=_('Render tables in the HTML as images (useful if the document has large or complex tables)'))
|
||||||
|
page.add_option('--text-size-multiplier-for-rendered-tables', type='float', default=1.0,
|
||||||
|
help=_('Multiply the size of text in rendered tables by this factor. Default is %default'))
|
||||||
|
|
||||||
link = parser.add_option_group('LINK PROCESSING OPTIONS')
|
link = parser.add_option_group('LINK PROCESSING OPTIONS')
|
||||||
link.add_option('--link-levels', action='store', type='int', default=sys.maxint, \
|
link.add_option('--link-levels', action='store', type='int', default=sys.maxint, \
|
||||||
dest='link_levels',
|
dest='link_levels',
|
||||||
@ -154,12 +158,13 @@ def option_parser(usage, gui_mode=False):
|
|||||||
chapter = parser.add_option_group('CHAPTER OPTIONS')
|
chapter = parser.add_option_group('CHAPTER OPTIONS')
|
||||||
chapter.add_option('--disable-chapter-detection', action='store_true',
|
chapter.add_option('--disable-chapter-detection', action='store_true',
|
||||||
default=False, dest='disable_chapter_detection',
|
default=False, dest='disable_chapter_detection',
|
||||||
help=_('''Prevent the automatic insertion of page breaks'''
|
help=_('''Prevent the automatic detection chapters.'''))
|
||||||
''' before detected chapters.'''))
|
|
||||||
chapter.add_option('--chapter-regex', dest='chapter_regex',
|
chapter.add_option('--chapter-regex', dest='chapter_regex',
|
||||||
default='chapter|book|appendix',
|
default='chapter|book|appendix',
|
||||||
help=_('''The regular expression used to detect chapter titles.'''
|
help=_('''The regular expression used to detect chapter titles.'''
|
||||||
''' It is searched for in heading tags (h1-h6). Defaults to %default'''))
|
''' It is searched for in heading tags (h1-h6). Defaults to %default'''))
|
||||||
|
chapter.add_option('--chapter-attr', default='$,,$',
|
||||||
|
help=_('Detect a chapter beginning at an element having the specified attribute. The format for this option is tagname regexp,attribute name,attribute value regexp. For example to match all heading tags that have the attribute class="chapter" you would use "h\d,class,chapter". Default is %default'''))
|
||||||
chapter.add_option('--page-break-before-tag', dest='page_break', default='h[12]',
|
chapter.add_option('--page-break-before-tag', dest='page_break', default='h[12]',
|
||||||
help=_('''If html2lrf does not find any page breaks in the '''
|
help=_('''If html2lrf does not find any page breaks in the '''
|
||||||
'''html file and cannot detect chapter headings, it will '''
|
'''html file and cannot detect chapter headings, it will '''
|
||||||
|
@ -158,7 +158,10 @@ def main(args=sys.argv, logger=None, gui_mode=False):
|
|||||||
print _('No file to convert specified.')
|
print _('No file to convert specified.')
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
return process_file(args[1], options, logger)
|
src = args[1]
|
||||||
|
if not isinstance(src, unicode):
|
||||||
|
src = src.decode(sys.getfilesystemencoding())
|
||||||
|
return process_file(src, options, logger)
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
sys.exit(main())
|
sys.exit(main())
|
||||||
|
@ -30,7 +30,7 @@ from calibre.ebooks.lrf import option_parser as lrf_option_parser
|
|||||||
from calibre.ebooks import ConversionError
|
from calibre.ebooks import ConversionError
|
||||||
from calibre.ebooks.lrf.html.table import Table
|
from calibre.ebooks.lrf.html.table import Table
|
||||||
from calibre import filename_to_utf8, setup_cli_handlers, __appname__, \
|
from calibre import filename_to_utf8, setup_cli_handlers, __appname__, \
|
||||||
fit_image, LoggingInterface
|
fit_image, LoggingInterface, preferred_encoding
|
||||||
from calibre.ptempfile import PersistentTemporaryFile
|
from calibre.ptempfile import PersistentTemporaryFile
|
||||||
from calibre.ebooks.metadata.opf import OPFReader
|
from calibre.ebooks.metadata.opf import OPFReader
|
||||||
from calibre.devices.interface import Device
|
from calibre.devices.interface import Device
|
||||||
@ -242,6 +242,7 @@ class HTMLConverter(object, LoggingInterface):
|
|||||||
|
|
||||||
self.override_css = {}
|
self.override_css = {}
|
||||||
self.override_pcss = {}
|
self.override_pcss = {}
|
||||||
|
self.table_render_job_server = None
|
||||||
|
|
||||||
if self._override_css is not None:
|
if self._override_css is not None:
|
||||||
if os.access(self._override_css, os.R_OK):
|
if os.access(self._override_css, os.R_OK):
|
||||||
@ -260,38 +261,43 @@ class HTMLConverter(object, LoggingInterface):
|
|||||||
|
|
||||||
|
|
||||||
paths = [os.path.abspath(path) for path in paths]
|
paths = [os.path.abspath(path) for path in paths]
|
||||||
|
paths = [path.decode(sys.getfilesystemencoding()) if not isinstance(path, unicode) else path for path in paths]
|
||||||
|
|
||||||
while len(paths) > 0 and self.link_level <= self.link_levels:
|
try:
|
||||||
for path in paths:
|
while len(paths) > 0 and self.link_level <= self.link_levels:
|
||||||
if path in self.processed_files:
|
for path in paths:
|
||||||
continue
|
if path in self.processed_files:
|
||||||
try:
|
continue
|
||||||
self.add_file(path)
|
try:
|
||||||
except KeyboardInterrupt:
|
self.add_file(path)
|
||||||
raise
|
except KeyboardInterrupt:
|
||||||
except:
|
|
||||||
if self.link_level == 0: # Die on errors in the first level
|
|
||||||
raise
|
raise
|
||||||
for link in self.links:
|
except:
|
||||||
if link['path'] == path:
|
if self.link_level == 0: # Die on errors in the first level
|
||||||
self.links.remove(link)
|
raise
|
||||||
break
|
for link in self.links:
|
||||||
self.log_warn('Could not process '+path)
|
if link['path'] == path:
|
||||||
if self.verbose:
|
self.links.remove(link)
|
||||||
self.log_exception(' ')
|
break
|
||||||
self.links = self.process_links()
|
self.log_warn('Could not process '+path)
|
||||||
self.link_level += 1
|
if self.verbose:
|
||||||
paths = [link['path'] for link in self.links]
|
self.log_exception(' ')
|
||||||
|
self.links = self.process_links()
|
||||||
|
self.link_level += 1
|
||||||
|
paths = [link['path'] for link in self.links]
|
||||||
|
|
||||||
if self.current_page is not None and self.current_page.has_text():
|
if self.current_page is not None and self.current_page.has_text():
|
||||||
self.book.append(self.current_page)
|
self.book.append(self.current_page)
|
||||||
|
|
||||||
for text, tb in self.extra_toc_entries:
|
for text, tb in self.extra_toc_entries:
|
||||||
self.book.addTocEntry(text, tb)
|
self.book.addTocEntry(text, tb)
|
||||||
|
|
||||||
if self.base_font_size > 0:
|
if self.base_font_size > 0:
|
||||||
self.log_info('\tRationalizing font sizes...')
|
self.log_info('\tRationalizing font sizes...')
|
||||||
self.book.rationalize_font_sizes(self.base_font_size)
|
self.book.rationalize_font_sizes(self.base_font_size)
|
||||||
|
finally:
|
||||||
|
if self.table_render_job_server is not None:
|
||||||
|
self.table_render_job_server.killall()
|
||||||
|
|
||||||
def is_baen(self, soup):
|
def is_baen(self, soup):
|
||||||
return bool(soup.find('meta', attrs={'name':'Publisher',
|
return bool(soup.find('meta', attrs={'name':'Publisher',
|
||||||
@ -380,12 +386,15 @@ class HTMLConverter(object, LoggingInterface):
|
|||||||
self.log_info(_('\tConverting to BBeB...'))
|
self.log_info(_('\tConverting to BBeB...'))
|
||||||
self.current_style = {}
|
self.current_style = {}
|
||||||
self.page_break_found = False
|
self.page_break_found = False
|
||||||
|
if not isinstance(path, unicode):
|
||||||
|
path = path.decode(sys.getfilesystemencoding())
|
||||||
self.target_prefix = path
|
self.target_prefix = path
|
||||||
self.previous_text = '\n'
|
self.previous_text = '\n'
|
||||||
self.tops[path] = self.parse_file(soup)
|
self.tops[path] = self.parse_file(soup)
|
||||||
self.processed_files.append(path)
|
self.processed_files.append(path)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def parse_css(self, style):
|
def parse_css(self, style):
|
||||||
"""
|
"""
|
||||||
Parse the contents of a <style> tag or .css file.
|
Parse the contents of a <style> tag or .css file.
|
||||||
@ -494,7 +503,9 @@ class HTMLConverter(object, LoggingInterface):
|
|||||||
top = self.current_block
|
top = self.current_block
|
||||||
self.current_block.must_append = True
|
self.current_block.must_append = True
|
||||||
|
|
||||||
|
self.soup = soup
|
||||||
self.process_children(soup, {}, {})
|
self.process_children(soup, {}, {})
|
||||||
|
self.soup = None
|
||||||
|
|
||||||
if self.current_para and self.current_block:
|
if self.current_para and self.current_block:
|
||||||
self.current_para.append_to(self.current_block)
|
self.current_para.append_to(self.current_block)
|
||||||
@ -625,6 +636,8 @@ class HTMLConverter(object, LoggingInterface):
|
|||||||
para, text, path, fragment = link['para'], link['text'], link['path'], link['fragment']
|
para, text, path, fragment = link['para'], link['text'], link['path'], link['fragment']
|
||||||
ascii_text = text
|
ascii_text = text
|
||||||
|
|
||||||
|
if not isinstance(path, unicode):
|
||||||
|
path = path.decode(sys.getfilesystemencoding())
|
||||||
if path in self.processed_files:
|
if path in self.processed_files:
|
||||||
if path+fragment in self.targets.keys():
|
if path+fragment in self.targets.keys():
|
||||||
tb = get_target_block(path+fragment, self.targets)
|
tb = get_target_block(path+fragment, self.targets)
|
||||||
@ -1424,6 +1437,18 @@ class HTMLConverter(object, LoggingInterface):
|
|||||||
return
|
return
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
|
if not self.disable_chapter_detection and \
|
||||||
|
(self.chapter_attr[0].match(tagname) and \
|
||||||
|
tag.has_key(self.chapter_attr[1]) and \
|
||||||
|
self.chapter_attr[2].match(tag[self.chapter_attr[1]])):
|
||||||
|
self.log_debug('Detected chapter %s', tagname)
|
||||||
|
self.end_page()
|
||||||
|
self.page_break_found = True
|
||||||
|
|
||||||
|
if self.options.add_chapters_to_toc:
|
||||||
|
self.extra_toc_entries.append((self.get_text(tag,
|
||||||
|
limit=1000), self.current_block))
|
||||||
|
|
||||||
end_page = self.process_page_breaks(tag, tagname, tag_css)
|
end_page = self.process_page_breaks(tag, tagname, tag_css)
|
||||||
try:
|
try:
|
||||||
if tagname in ["title", "script", "meta", 'del', 'frameset']:
|
if tagname in ["title", "script", "meta", 'del', 'frameset']:
|
||||||
@ -1680,18 +1705,48 @@ class HTMLConverter(object, LoggingInterface):
|
|||||||
self.previous_text = ' '
|
self.previous_text = ' '
|
||||||
self.process_children(tag, tag_css, tag_pseudo_css)
|
self.process_children(tag, tag_css, tag_pseudo_css)
|
||||||
elif tagname == 'table' and not self.ignore_tables and not self.in_table:
|
elif tagname == 'table' and not self.ignore_tables and not self.in_table:
|
||||||
tag_css = self.tag_css(tag)[0] # Table should not inherit CSS
|
if self.render_tables_as_images:
|
||||||
try:
|
if self.table_render_job_server is None:
|
||||||
self.process_table(tag, tag_css)
|
from calibre.parallel import Server
|
||||||
except Exception, err:
|
self.table_render_job_server = Server(number_of_workers=1)
|
||||||
self.log_warning(_('An error occurred while processing a table: %s. Ignoring table markup.'), str(err))
|
print 'Rendering table...'
|
||||||
self.log_debug('', exc_info=True)
|
from calibre.ebooks.lrf.html.table_as_image import render_table
|
||||||
self.log_debug(_('Bad table:\n%s'), str(tag)[:300])
|
pheight = int(self.current_page.pageStyle.attrs['textheight'])
|
||||||
self.in_table = False
|
pwidth = int(self.current_page.pageStyle.attrs['textwidth'])
|
||||||
self.process_children(tag, tag_css, tag_pseudo_css)
|
images = render_table(self.table_render_job_server,
|
||||||
finally:
|
self.soup, tag, tag_css,
|
||||||
if self.minimize_memory_usage:
|
os.path.dirname(self.target_prefix),
|
||||||
tag.extract()
|
pwidth, pheight, self.profile.dpi,
|
||||||
|
self.text_size_multiplier_for_rendered_tables)
|
||||||
|
for path, width, height in images:
|
||||||
|
stream = ImageStream(path, encoding='PNG')
|
||||||
|
im = Image(stream, x0=0, y0=0, x1=width, y1=height,\
|
||||||
|
xsize=width, ysize=height)
|
||||||
|
pb = self.current_block
|
||||||
|
self.end_current_para()
|
||||||
|
self.process_alignment(tag_css)
|
||||||
|
self.current_para.append(Plot(im, xsize=width*720./self.profile.dpi,
|
||||||
|
ysize=height*720./self.profile.dpi))
|
||||||
|
self.current_block.append(self.current_para)
|
||||||
|
self.current_page.append(self.current_block)
|
||||||
|
self.current_block = self.book.create_text_block(
|
||||||
|
textStyle=pb.textStyle,
|
||||||
|
blockStyle=pb.blockStyle)
|
||||||
|
self.current_para = Paragraph()
|
||||||
|
|
||||||
|
else:
|
||||||
|
tag_css = self.tag_css(tag)[0] # Table should not inherit CSS
|
||||||
|
try:
|
||||||
|
self.process_table(tag, tag_css)
|
||||||
|
except Exception, err:
|
||||||
|
self.log_warning(_('An error occurred while processing a table: %s. Ignoring table markup.'), str(err))
|
||||||
|
self.log_debug('', exc_info=True)
|
||||||
|
self.log_debug(_('Bad table:\n%s'), str(tag)[:300])
|
||||||
|
self.in_table = False
|
||||||
|
self.process_children(tag, tag_css, tag_pseudo_css)
|
||||||
|
finally:
|
||||||
|
if self.minimize_memory_usage:
|
||||||
|
tag.extract()
|
||||||
else:
|
else:
|
||||||
self.process_children(tag, tag_css, tag_pseudo_css)
|
self.process_children(tag, tag_css, tag_pseudo_css)
|
||||||
finally:
|
finally:
|
||||||
@ -1743,6 +1798,8 @@ def process_file(path, options, logger=None):
|
|||||||
level = logging.DEBUG if options.verbose else logging.INFO
|
level = logging.DEBUG if options.verbose else logging.INFO
|
||||||
logger = logging.getLogger('html2lrf')
|
logger = logging.getLogger('html2lrf')
|
||||||
setup_cli_handlers(logger, level)
|
setup_cli_handlers(logger, level)
|
||||||
|
if not isinstance(path, unicode):
|
||||||
|
path = path.decode(sys.getfilesystemencoding())
|
||||||
path = os.path.abspath(path)
|
path = os.path.abspath(path)
|
||||||
default_title = filename_to_utf8(os.path.splitext(os.path.basename(path))[0])
|
default_title = filename_to_utf8(os.path.splitext(os.path.basename(path))[0])
|
||||||
dirpath = os.path.dirname(path)
|
dirpath = os.path.dirname(path)
|
||||||
@ -1821,9 +1878,14 @@ def process_file(path, options, logger=None):
|
|||||||
re.compile('$')
|
re.compile('$')
|
||||||
fpb = re.compile(options.force_page_break, re.IGNORECASE) if options.force_page_break else \
|
fpb = re.compile(options.force_page_break, re.IGNORECASE) if options.force_page_break else \
|
||||||
re.compile('$')
|
re.compile('$')
|
||||||
|
cq = options.chapter_attr.split(',')
|
||||||
|
options.chapter_attr = [re.compile(cq[0], re.IGNORECASE), cq[1],
|
||||||
|
re.compile(cq[2], re.IGNORECASE)]
|
||||||
options.force_page_break = fpb
|
options.force_page_break = fpb
|
||||||
options.link_exclude = le
|
options.link_exclude = le
|
||||||
options.page_break = pb
|
options.page_break = pb
|
||||||
|
if not isinstance(options.chapter_regex, unicode):
|
||||||
|
options.chapter_regex = options.chapter_regex.decode(preferred_encoding)
|
||||||
options.chapter_regex = re.compile(options.chapter_regex, re.IGNORECASE)
|
options.chapter_regex = re.compile(options.chapter_regex, re.IGNORECASE)
|
||||||
fpba = options.force_page_break_attr.split(',')
|
fpba = options.force_page_break_attr.split(',')
|
||||||
if len(fpba) != 3:
|
if len(fpba) != 3:
|
||||||
@ -1940,7 +2002,8 @@ def main(args=sys.argv):
|
|||||||
except Exception, err:
|
except Exception, err:
|
||||||
print >> sys.stderr, err
|
print >> sys.stderr, err
|
||||||
return 1
|
return 1
|
||||||
|
if not isinstance(src, unicode):
|
||||||
|
src = src.decode(sys.getfilesystemencoding())
|
||||||
process_file(src, options)
|
process_file(src, options)
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
104
src/calibre/ebooks/lrf/html/table_as_image.py
Normal file
104
src/calibre/ebooks/lrf/html/table_as_image.py
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
__license__ = 'GPL v3'
|
||||||
|
__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
|
||||||
|
__docformat__ = 'restructuredtext en'
|
||||||
|
|
||||||
|
'''
|
||||||
|
Render HTML tables as images.
|
||||||
|
'''
|
||||||
|
import os, tempfile, atexit, shutil, time
|
||||||
|
from PyQt4.Qt import QWebPage, QUrl, QApplication, QSize, \
|
||||||
|
SIGNAL, QPainter, QImage, QObject, Qt
|
||||||
|
|
||||||
|
__app = None
|
||||||
|
|
||||||
|
class HTMLTableRenderer(QObject):
|
||||||
|
|
||||||
|
def __init__(self, html, base_dir, width, height, dpi, factor):
|
||||||
|
'''
|
||||||
|
`width, height`: page width and height in pixels
|
||||||
|
`base_dir`: The directory in which the HTML file that contains the table resides
|
||||||
|
'''
|
||||||
|
QObject.__init__(self)
|
||||||
|
|
||||||
|
self.app = None
|
||||||
|
self.width, self.height, self.dpi = width, height, dpi
|
||||||
|
self.base_dir = base_dir
|
||||||
|
self.page = QWebPage()
|
||||||
|
self.connect(self.page, SIGNAL('loadFinished(bool)'), self.render_html)
|
||||||
|
self.page.mainFrame().setTextSizeMultiplier(factor)
|
||||||
|
self.page.mainFrame().setHtml(html,
|
||||||
|
QUrl('file:'+os.path.abspath(self.base_dir)))
|
||||||
|
self.images = []
|
||||||
|
self.tdir = tempfile.mkdtemp(prefix='calibre_render_table')
|
||||||
|
|
||||||
|
def render_html(self, ok):
|
||||||
|
try:
|
||||||
|
if not ok:
|
||||||
|
return
|
||||||
|
cwidth, cheight = self.page.mainFrame().contentsSize().width(), self.page.mainFrame().contentsSize().height()
|
||||||
|
self.page.setViewportSize(QSize(cwidth, cheight))
|
||||||
|
factor = float(self.width)/cwidth if cwidth > self.width else 1
|
||||||
|
cutoff_height = int(self.height/factor)-3
|
||||||
|
image = QImage(self.page.viewportSize(), QImage.Format_ARGB32)
|
||||||
|
image.setDotsPerMeterX(self.dpi*(100/2.54))
|
||||||
|
image.setDotsPerMeterX(self.dpi*(100/2.54))
|
||||||
|
painter = QPainter(image)
|
||||||
|
self.page.mainFrame().render(painter)
|
||||||
|
painter.end()
|
||||||
|
cheight = image.height()
|
||||||
|
cwidth = image.width()
|
||||||
|
pos = 0
|
||||||
|
while pos < cheight:
|
||||||
|
img = image.copy(0, pos, cwidth, min(cheight-pos, cutoff_height))
|
||||||
|
pos += cutoff_height-20
|
||||||
|
if cwidth > self.width:
|
||||||
|
img = img.scaledToWidth(self.width, Qt.SmoothTransform)
|
||||||
|
f = os.path.join(self.tdir, '%d.png'%pos)
|
||||||
|
img.save(f)
|
||||||
|
self.images.append((f, img.width(), img.height()))
|
||||||
|
finally:
|
||||||
|
QApplication.quit()
|
||||||
|
|
||||||
|
def render_table(server, soup, table, css, base_dir, width, height, dpi, factor=1.0):
|
||||||
|
head = ''
|
||||||
|
for e in soup.findAll(['link', 'style']):
|
||||||
|
head += unicode(e)+'\n\n'
|
||||||
|
style = ''
|
||||||
|
for key, val in css.items():
|
||||||
|
style += key + ':%s;'%val
|
||||||
|
html = u'''\
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
%s
|
||||||
|
</head>
|
||||||
|
<body style="width: %dpx; background: white">
|
||||||
|
<style type="text/css">
|
||||||
|
table {%s}
|
||||||
|
</style>
|
||||||
|
%s
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
'''%(head, width-10, style, unicode(table))
|
||||||
|
server.run_job(1, 'render_table',
|
||||||
|
args=[html, base_dir, width, height, dpi, factor])
|
||||||
|
res = None
|
||||||
|
while res is None:
|
||||||
|
time.sleep(2)
|
||||||
|
res = server.result(1)
|
||||||
|
result, exception, traceback = res
|
||||||
|
if exception:
|
||||||
|
print 'Failed to render table'
|
||||||
|
print exception
|
||||||
|
print traceback
|
||||||
|
images, tdir = result
|
||||||
|
atexit.register(shutil.rmtree, tdir)
|
||||||
|
return images
|
||||||
|
|
||||||
|
def do_render(html, base_dir, width, height, dpi, factor):
|
||||||
|
app = QApplication.instance()
|
||||||
|
if app is None:
|
||||||
|
app = QApplication([])
|
||||||
|
tr = HTMLTableRenderer(html, base_dir, width, height, dpi, factor)
|
||||||
|
app.exec_()
|
||||||
|
return tr.images, tr.tdir
|
@ -38,7 +38,7 @@ class MetaInformation(object):
|
|||||||
setattr(ans, attr, getattr(mi, attr))
|
setattr(ans, attr, getattr(mi, attr))
|
||||||
|
|
||||||
|
|
||||||
def __init__(self, title, authors=['Unknown']):
|
def __init__(self, title, authors=[_('Unknown')]):
|
||||||
'''
|
'''
|
||||||
@param title: title or "Unknown" or a MetaInformation object
|
@param title: title or "Unknown" or a MetaInformation object
|
||||||
@param authors: List of strings or []
|
@param authors: List of strings or []
|
||||||
|
@ -511,6 +511,8 @@ class OPFCreator(MetaInformation):
|
|||||||
path = path[len(self.base_path)+1:]
|
path = path[len(self.base_path)+1:]
|
||||||
manifest.append((path, mt))
|
manifest.append((path, mt))
|
||||||
self.manifest = manifest
|
self.manifest = manifest
|
||||||
|
if not self.authors:
|
||||||
|
self.authors = [_('Unknown')]
|
||||||
|
|
||||||
def create_manifest(self, entries):
|
def create_manifest(self, entries):
|
||||||
'''
|
'''
|
||||||
|
@ -156,7 +156,7 @@ class MobiReader(object):
|
|||||||
|
|
||||||
processed_records = self.extract_text()
|
processed_records = self.extract_text()
|
||||||
self.add_anchors()
|
self.add_anchors()
|
||||||
self.processed_html = self.processed_html.decode(self.book_header.codec)
|
self.processed_html = self.processed_html.decode(self.book_header.codec, 'ignore')
|
||||||
self.extract_images(processed_records, output_dir)
|
self.extract_images(processed_records, output_dir)
|
||||||
self.replace_page_breaks()
|
self.replace_page_breaks()
|
||||||
self.cleanup()
|
self.cleanup()
|
||||||
@ -177,7 +177,7 @@ class MobiReader(object):
|
|||||||
opf.render(open(os.path.splitext(htmlfile)[0]+'.opf', 'wb'))
|
opf.render(open(os.path.splitext(htmlfile)[0]+'.opf', 'wb'))
|
||||||
|
|
||||||
def cleanup(self):
|
def cleanup(self):
|
||||||
self.processed_html = re.sub(r'<div height="0(em|%)"></div>', '', self.processed_html)
|
self.processed_html = re.sub(r'<div height="0(em|%){0,1}"></div>', '', self.processed_html)
|
||||||
|
|
||||||
def create_opf(self, htmlfile):
|
def create_opf(self, htmlfile):
|
||||||
mi = self.book_header.exth.mi
|
mi = self.book_header.exth.mi
|
||||||
|
@ -9,14 +9,15 @@
|
|||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>830</width>
|
<width>830</width>
|
||||||
<height>700</height>
|
<height>642</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<property name="windowTitle" >
|
<property name="windowTitle" >
|
||||||
<string>Fetch metadata</string>
|
<string>Fetch metadata</string>
|
||||||
</property>
|
</property>
|
||||||
<property name="windowIcon" >
|
<property name="windowIcon" >
|
||||||
<iconset resource="../images.qrc" >:/images/metadata.svg</iconset>
|
<iconset resource="../images.qrc" >
|
||||||
|
<normaloff>:/images/metadata.svg</normaloff>:/images/metadata.svg</iconset>
|
||||||
</property>
|
</property>
|
||||||
<layout class="QVBoxLayout" >
|
<layout class="QVBoxLayout" >
|
||||||
<item>
|
<item>
|
||||||
@ -107,7 +108,7 @@
|
|||||||
<item>
|
<item>
|
||||||
<widget class="QDialogButtonBox" name="buttonBox" >
|
<widget class="QDialogButtonBox" name="buttonBox" >
|
||||||
<property name="standardButtons" >
|
<property name="standardButtons" >
|
||||||
<set>QDialogButtonBox::Cancel|QDialogButtonBox::NoButton|QDialogButtonBox::Ok</set>
|
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
@ -35,7 +35,7 @@ class JobsDialog(QDialog, Ui_JobsDialog):
|
|||||||
self.jobs_view.setModel(model)
|
self.jobs_view.setModel(model)
|
||||||
self.model = model
|
self.model = model
|
||||||
self.setWindowModality(Qt.NonModal)
|
self.setWindowModality(Qt.NonModal)
|
||||||
self.setWindowTitle(__appname__ + ' - Active Jobs')
|
self.setWindowTitle(__appname__ + _(' - Jobs'))
|
||||||
QObject.connect(self.jobs_view.model(), SIGNAL('modelReset()'),
|
QObject.connect(self.jobs_view.model(), SIGNAL('modelReset()'),
|
||||||
self.jobs_view.resizeColumnsToContents)
|
self.jobs_view.resizeColumnsToContents)
|
||||||
QObject.connect(self.kill_button, SIGNAL('clicked()'),
|
QObject.connect(self.kill_button, SIGNAL('clicked()'),
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
__license__ = 'GPL v3'
|
__license__ = 'GPL v3'
|
||||||
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
|
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||||
import os, cPickle, codecs
|
import os, codecs
|
||||||
|
|
||||||
from PyQt4.QtCore import QObject, SIGNAL, Qt, QVariant, QByteArray
|
from PyQt4.QtCore import QObject, SIGNAL, Qt
|
||||||
from PyQt4.QtGui import QAbstractSpinBox, QLineEdit, QCheckBox, QDialog, \
|
from PyQt4.QtGui import QAbstractSpinBox, QLineEdit, QCheckBox, QDialog, \
|
||||||
QPixmap, QTextEdit, QListWidgetItem, QIcon
|
QPixmap, QTextEdit, QListWidgetItem, QIcon
|
||||||
|
|
||||||
@ -48,10 +48,7 @@ class LRFSingleDialog(QDialog, Ui_LRFSingleDialog):
|
|||||||
self.gui_mono_family.setModel(self.font_family_model)
|
self.gui_mono_family.setModel(self.font_family_model)
|
||||||
self.load_saved_global_defaults()
|
self.load_saved_global_defaults()
|
||||||
|
|
||||||
def __init__(self, window, db, row):
|
def populate_list(self):
|
||||||
QDialog.__init__(self, window)
|
|
||||||
Ui_LRFSingleDialog.__init__(self)
|
|
||||||
self.setupUi(self)
|
|
||||||
self.__w = []
|
self.__w = []
|
||||||
self.__w.append(QIcon(':/images/dialog_information.svg'))
|
self.__w.append(QIcon(':/images/dialog_information.svg'))
|
||||||
self.item1 = QListWidgetItem(self.__w[-1], _("Metadata"), self.categoryList)
|
self.item1 = QListWidgetItem(self.__w[-1], _("Metadata"), self.categoryList)
|
||||||
@ -61,11 +58,17 @@ class LRFSingleDialog(QDialog, Ui_LRFSingleDialog):
|
|||||||
self.item3 = QListWidgetItem(self.__w[-1], _('Page Setup'), self.categoryList)
|
self.item3 = QListWidgetItem(self.__w[-1], _('Page Setup'), self.categoryList)
|
||||||
self.__w.append(QIcon(':/images/chapters.svg'))
|
self.__w.append(QIcon(':/images/chapters.svg'))
|
||||||
self.item4 = QListWidgetItem(self.__w[-1], _('Chapter Detection'), self.categoryList)
|
self.item4 = QListWidgetItem(self.__w[-1], _('Chapter Detection'), self.categoryList)
|
||||||
|
|
||||||
|
def __init__(self, window, db, row):
|
||||||
|
QDialog.__init__(self, window)
|
||||||
|
Ui_LRFSingleDialog.__init__(self)
|
||||||
|
self.setupUi(self)
|
||||||
|
self.populate_list()
|
||||||
self.categoryList.setCurrentRow(0)
|
self.categoryList.setCurrentRow(0)
|
||||||
QObject.connect(self.categoryList, SIGNAL('itemEntered(QListWidgetItem *)'),
|
QObject.connect(self.categoryList, SIGNAL('itemEntered(QListWidgetItem *)'),
|
||||||
self.show_category_help)
|
self.show_category_help)
|
||||||
QObject.connect(self.cover_button, SIGNAL("clicked(bool)"), self.select_cover)
|
QObject.connect(self.cover_button, SIGNAL("clicked(bool)"), self.select_cover)
|
||||||
self.categoryList.leaveEvent = self.reset_help
|
#self.categoryList.leaveEvent = self.reset_help
|
||||||
self.reset_help()
|
self.reset_help()
|
||||||
self.selected_format = None
|
self.selected_format = None
|
||||||
self.initialize_common()
|
self.initialize_common()
|
||||||
@ -277,9 +280,9 @@ class LRFSingleDialog(QDialog, Ui_LRFSingleDialog):
|
|||||||
obj.setWhatsThis(help)
|
obj.setWhatsThis(help)
|
||||||
self.option_map[guiname] = opt
|
self.option_map[guiname] = opt
|
||||||
obj.__class__.enterEvent = show_item_help
|
obj.__class__.enterEvent = show_item_help
|
||||||
obj.leaveEvent = self.reset_help
|
#obj.leaveEvent = self.reset_help
|
||||||
self.preprocess.__class__.enterEvent = show_item_help
|
self.preprocess.__class__.enterEvent = show_item_help
|
||||||
self.preprocess.leaveEvent = self.reset_help
|
#self.preprocess.leaveEvent = self.reset_help
|
||||||
|
|
||||||
|
|
||||||
def show_category_help(self, item):
|
def show_category_help(self, item):
|
||||||
@ -293,7 +296,8 @@ class LRFSingleDialog(QDialog, Ui_LRFSingleDialog):
|
|||||||
self.set_help(help[text])
|
self.set_help(help[text])
|
||||||
|
|
||||||
def set_help(self, msg):
|
def set_help(self, msg):
|
||||||
self.help_view.setHtml('<html><body>%s</body></html>'%(msg,))
|
if msg and getattr(msg, 'strip', lambda:True)():
|
||||||
|
self.help_view.setHtml('<html><body>%s</body></html>'%(msg,))
|
||||||
|
|
||||||
def reset_help(self, *args):
|
def reset_help(self, *args):
|
||||||
self.set_help(_('<font color="gray">No help available</font>'))
|
self.set_help(_('<font color="gray">No help available</font>'))
|
||||||
@ -390,6 +394,7 @@ class LRFBulkDialog(LRFSingleDialog):
|
|||||||
QDialog.__init__(self, window)
|
QDialog.__init__(self, window)
|
||||||
Ui_LRFSingleDialog.__init__(self)
|
Ui_LRFSingleDialog.__init__(self)
|
||||||
self.setupUi(self)
|
self.setupUi(self)
|
||||||
|
self.populate_list()
|
||||||
|
|
||||||
self.categoryList.takeItem(0)
|
self.categoryList.takeItem(0)
|
||||||
self.stack.removeWidget(self.stack.widget(0))
|
self.stack.removeWidget(self.stack.widget(0))
|
||||||
@ -399,7 +404,14 @@ class LRFBulkDialog(LRFSingleDialog):
|
|||||||
self.setWindowTitle(_('Bulk convert ebooks to LRF'))
|
self.setWindowTitle(_('Bulk convert ebooks to LRF'))
|
||||||
|
|
||||||
def accept(self):
|
def accept(self):
|
||||||
self.cmdline = self.cmdline = [unicode(i) for i in self.build_commandline()]
|
self.cmdline = [unicode(i) for i in self.build_commandline()]
|
||||||
|
for meta in ('--title', '--author', '--publisher', '--comment'):
|
||||||
|
try:
|
||||||
|
index = self.cmdline.index(meta)
|
||||||
|
self.cmdline[index:index+2] = []
|
||||||
|
except ValueError:
|
||||||
|
continue
|
||||||
|
|
||||||
self.cover_file = None
|
self.cover_file = None
|
||||||
QDialog.accept(self)
|
QDialog.accept(self)
|
||||||
|
|
||||||
|
@ -115,7 +115,7 @@
|
|||||||
<item row="0" column="0" >
|
<item row="0" column="0" >
|
||||||
<widget class="QStackedWidget" name="stack" >
|
<widget class="QStackedWidget" name="stack" >
|
||||||
<property name="currentIndex" >
|
<property name="currentIndex" >
|
||||||
<number>0</number>
|
<number>3</number>
|
||||||
</property>
|
</property>
|
||||||
<widget class="QWidget" name="metadata_page" >
|
<widget class="QWidget" name="metadata_page" >
|
||||||
<property name="geometry" >
|
<property name="geometry" >
|
||||||
@ -818,6 +818,39 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
<item row="5" column="0" >
|
||||||
|
<widget class="QCheckBox" name="gui_render_tables_as_images" >
|
||||||
|
<property name="text" >
|
||||||
|
<string>&Convert tables to images (good for large/complex tables)</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="6" column="0" >
|
||||||
|
<widget class="QLabel" name="label_27" >
|
||||||
|
<property name="text" >
|
||||||
|
<string>&Multiplier for text size in rendered tables:</string>
|
||||||
|
</property>
|
||||||
|
<property name="buddy" >
|
||||||
|
<cstring>gui_text_size_multiplier_for_rendered_tables</cstring>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="6" column="1" >
|
||||||
|
<widget class="QDoubleSpinBox" name="gui_text_size_multiplier_for_rendered_tables" >
|
||||||
|
<property name="enabled" >
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
<property name="decimals" >
|
||||||
|
<number>2</number>
|
||||||
|
</property>
|
||||||
|
<property name="minimum" >
|
||||||
|
<double>0.100000000000000</double>
|
||||||
|
</property>
|
||||||
|
<property name="value" >
|
||||||
|
<double>1.000000000000000</double>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
<widget class="QWidget" name="chapterdetection_page" >
|
<widget class="QWidget" name="chapterdetection_page" >
|
||||||
@ -918,6 +951,19 @@
|
|||||||
<item row="2" column="1" >
|
<item row="2" column="1" >
|
||||||
<widget class="QLineEdit" name="gui_force_page_break_before_attr" />
|
<widget class="QLineEdit" name="gui_force_page_break_before_attr" />
|
||||||
</item>
|
</item>
|
||||||
|
<item row="3" column="0" >
|
||||||
|
<widget class="QLabel" name="label_28" >
|
||||||
|
<property name="text" >
|
||||||
|
<string>Detect chapter &at tag:</string>
|
||||||
|
</property>
|
||||||
|
<property name="buddy" >
|
||||||
|
<cstring>gui_chapter_attr</cstring>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="3" column="1" >
|
||||||
|
<widget class="QLineEdit" name="gui_chapter_attr" />
|
||||||
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
@ -1048,8 +1094,8 @@ p, li { white-space: pre-wrap; }
|
|||||||
<slot>setCurrentIndex(int)</slot>
|
<slot>setCurrentIndex(int)</slot>
|
||||||
<hints>
|
<hints>
|
||||||
<hint type="sourcelabel" >
|
<hint type="sourcelabel" >
|
||||||
<x>191</x>
|
<x>184</x>
|
||||||
<y>236</y>
|
<y>279</y>
|
||||||
</hint>
|
</hint>
|
||||||
<hint type="destinationlabel" >
|
<hint type="destinationlabel" >
|
||||||
<x>368</x>
|
<x>368</x>
|
||||||
@ -1064,8 +1110,8 @@ p, li { white-space: pre-wrap; }
|
|||||||
<slot>setDisabled(bool)</slot>
|
<slot>setDisabled(bool)</slot>
|
||||||
<hints>
|
<hints>
|
||||||
<hint type="sourcelabel" >
|
<hint type="sourcelabel" >
|
||||||
<x>428</x>
|
<x>650</x>
|
||||||
<y>89</y>
|
<y>122</y>
|
||||||
</hint>
|
</hint>
|
||||||
<hint type="destinationlabel" >
|
<hint type="destinationlabel" >
|
||||||
<x>788</x>
|
<x>788</x>
|
||||||
@ -1073,22 +1119,6 @@ p, li { white-space: pre-wrap; }
|
|||||||
</hint>
|
</hint>
|
||||||
</hints>
|
</hints>
|
||||||
</connection>
|
</connection>
|
||||||
<connection>
|
|
||||||
<sender>gui_header</sender>
|
|
||||||
<signal>toggled(bool)</signal>
|
|
||||||
<receiver>gui_headerformat</receiver>
|
|
||||||
<slot>setEnabled(bool)</slot>
|
|
||||||
<hints>
|
|
||||||
<hint type="sourcelabel" >
|
|
||||||
<x>348</x>
|
|
||||||
<y>340</y>
|
|
||||||
</hint>
|
|
||||||
<hint type="destinationlabel" >
|
|
||||||
<x>823</x>
|
|
||||||
<y>372</y>
|
|
||||||
</hint>
|
|
||||||
</hints>
|
|
||||||
</connection>
|
|
||||||
<connection>
|
<connection>
|
||||||
<sender>gui_disable_chapter_detection</sender>
|
<sender>gui_disable_chapter_detection</sender>
|
||||||
<signal>toggled(bool)</signal>
|
<signal>toggled(bool)</signal>
|
||||||
@ -1096,12 +1126,60 @@ p, li { white-space: pre-wrap; }
|
|||||||
<slot>setDisabled(bool)</slot>
|
<slot>setDisabled(bool)</slot>
|
||||||
<hints>
|
<hints>
|
||||||
<hint type="sourcelabel" >
|
<hint type="sourcelabel" >
|
||||||
<x>321</x>
|
<x>543</x>
|
||||||
<y>78</y>
|
<y>122</y>
|
||||||
</hint>
|
</hint>
|
||||||
<hint type="destinationlabel" >
|
<hint type="destinationlabel" >
|
||||||
<x>322</x>
|
<x>544</x>
|
||||||
<y>172</y>
|
<y>211</y>
|
||||||
|
</hint>
|
||||||
|
</hints>
|
||||||
|
</connection>
|
||||||
|
<connection>
|
||||||
|
<sender>gui_render_tables_as_images</sender>
|
||||||
|
<signal>toggled(bool)</signal>
|
||||||
|
<receiver>gui_text_size_multiplier_for_rendered_tables</receiver>
|
||||||
|
<slot>setEnabled(bool)</slot>
|
||||||
|
<hints>
|
||||||
|
<hint type="sourcelabel" >
|
||||||
|
<x>298</x>
|
||||||
|
<y>398</y>
|
||||||
|
</hint>
|
||||||
|
<hint type="destinationlabel" >
|
||||||
|
<x>660</x>
|
||||||
|
<y>435</y>
|
||||||
|
</hint>
|
||||||
|
</hints>
|
||||||
|
</connection>
|
||||||
|
<connection>
|
||||||
|
<sender>gui_header</sender>
|
||||||
|
<signal>toggled(bool)</signal>
|
||||||
|
<receiver>gui_headerformat</receiver>
|
||||||
|
<slot>setEnabled(bool)</slot>
|
||||||
|
<hints>
|
||||||
|
<hint type="sourcelabel" >
|
||||||
|
<x>330</x>
|
||||||
|
<y>367</y>
|
||||||
|
</hint>
|
||||||
|
<hint type="destinationlabel" >
|
||||||
|
<x>823</x>
|
||||||
|
<y>372</y>
|
||||||
|
</hint>
|
||||||
|
</hints>
|
||||||
|
</connection>
|
||||||
|
<connection>
|
||||||
|
<sender>gui_disable_chapter_detection</sender>
|
||||||
|
<signal>toggled(bool)</signal>
|
||||||
|
<receiver>gui_chapter_attr</receiver>
|
||||||
|
<slot>setDisabled(bool)</slot>
|
||||||
|
<hints>
|
||||||
|
<hint type="sourcelabel" >
|
||||||
|
<x>344</x>
|
||||||
|
<y>107</y>
|
||||||
|
</hint>
|
||||||
|
<hint type="destinationlabel" >
|
||||||
|
<x>489</x>
|
||||||
|
<y>465</y>
|
||||||
</hint>
|
</hint>
|
||||||
</hints>
|
</hints>
|
||||||
</connection>
|
</connection>
|
||||||
|
@ -84,6 +84,7 @@ class UserProfiles(QDialog, Ui_Dialog):
|
|||||||
self.populate_options(recipe)
|
self.populate_options(recipe)
|
||||||
self.stacks.setCurrentIndex(0)
|
self.stacks.setCurrentIndex(0)
|
||||||
self.toggle_mode_button.setText(_('Switch to Advanced mode'))
|
self.toggle_mode_button.setText(_('Switch to Advanced mode'))
|
||||||
|
self.source_code.setPlainText('')
|
||||||
else:
|
else:
|
||||||
self.source_code.setPlainText(src)
|
self.source_code.setPlainText(src)
|
||||||
self.highlighter = PythonHighlighter(self.source_code.document())
|
self.highlighter = PythonHighlighter(self.source_code.document())
|
||||||
|
@ -86,16 +86,33 @@ class DeviceJob(Job):
|
|||||||
|
|
||||||
class ConversionJob(Job):
|
class ConversionJob(Job):
|
||||||
''' Jobs that involve conversion of content.'''
|
''' Jobs that involve conversion of content.'''
|
||||||
def run(self):
|
def __init__(self, *args, **kwdargs):
|
||||||
last_traceback, exception = None, None
|
Job.__init__(self, *args, **kwdargs)
|
||||||
try:
|
self.log = ''
|
||||||
self.result, exception, last_traceback, self.log = \
|
|
||||||
self.server.run(self.id, self.func, self.args, self.kwargs)
|
|
||||||
except Exception, err:
|
|
||||||
last_traceback = traceback.format_exc()
|
|
||||||
exception = (exception.__class__.__name__, unicode(str(err), 'utf8', 'replace'))
|
|
||||||
|
|
||||||
self.last_traceback, self.exception = last_traceback, exception
|
def run(self):
|
||||||
|
result = None
|
||||||
|
self.server.run_job(self.id, self.func, progress=self.progress,
|
||||||
|
args=self.args, kwdargs=self.kwargs,
|
||||||
|
output=self.output)
|
||||||
|
res = None
|
||||||
|
while res is None:
|
||||||
|
time.sleep(2)
|
||||||
|
res = self.server.result(self.id)
|
||||||
|
if res is None:
|
||||||
|
exception, tb = 'UnknownError: This should not have happened', ''
|
||||||
|
else:
|
||||||
|
result, exception, tb = res
|
||||||
|
self.result, self.last_traceback, self.exception = result, tb, exception
|
||||||
|
|
||||||
|
def output(self, msg):
|
||||||
|
if self.log is None:
|
||||||
|
self.log = ''
|
||||||
|
self.log += msg
|
||||||
|
self.emit(SIGNAL('output_received()'))
|
||||||
|
|
||||||
|
def formatted_log(self):
|
||||||
|
return '<h2>Log:</h2><pre>%s</pre>'%self.log
|
||||||
|
|
||||||
def notify(self):
|
def notify(self):
|
||||||
self.emit(SIGNAL('jobdone(PyQt_PyObject, PyQt_PyObject, PyQt_PyObject, PyQt_PyObject, PyQt_PyObject, PyQt_PyObject)'),
|
self.emit(SIGNAL('jobdone(PyQt_PyObject, PyQt_PyObject, PyQt_PyObject, PyQt_PyObject, PyQt_PyObject, PyQt_PyObject)'),
|
||||||
@ -113,6 +130,9 @@ class ConversionJob(Job):
|
|||||||
ans += '<h2>Traceback:</h2><pre>%s</pre>'%self.last_traceback
|
ans += '<h2>Traceback:</h2><pre>%s</pre>'%self.last_traceback
|
||||||
return ans
|
return ans
|
||||||
|
|
||||||
|
def progress(self, percent, msg):
|
||||||
|
self.emit(SIGNAL('update_progress(int, PyQt_PyObject)'), self.id, percent)
|
||||||
|
|
||||||
class JobManager(QAbstractTableModel):
|
class JobManager(QAbstractTableModel):
|
||||||
|
|
||||||
PRIORITY = {'Idle' : QThread.IdlePriority,
|
PRIORITY = {'Idle' : QThread.IdlePriority,
|
||||||
@ -149,9 +169,9 @@ class JobManager(QAbstractTableModel):
|
|||||||
try:
|
try:
|
||||||
if isinstance(job, DeviceJob):
|
if isinstance(job, DeviceJob):
|
||||||
job.terminate()
|
job.terminate()
|
||||||
self.process_server.kill(job.id)
|
|
||||||
except:
|
except:
|
||||||
continue
|
continue
|
||||||
|
self.process_server.killall()
|
||||||
|
|
||||||
def timerEvent(self, event):
|
def timerEvent(self, event):
|
||||||
if event.timerId() == self.timer_id:
|
if event.timerId() == self.timer_id:
|
||||||
@ -241,7 +261,10 @@ class JobManager(QAbstractTableModel):
|
|||||||
id = self.next_id
|
id = self.next_id
|
||||||
job = job_class(id, description, slot, priority, *args, **kwargs)
|
job = job_class(id, description, slot, priority, *args, **kwargs)
|
||||||
job.server = self.process_server
|
job.server = self.process_server
|
||||||
QObject.connect(job, SIGNAL('status_update(int, int)'), self.status_update, Qt.QueuedConnection)
|
QObject.connect(job, SIGNAL('status_update(int, int)'), self.status_update,
|
||||||
|
Qt.QueuedConnection)
|
||||||
|
self.connect(job, SIGNAL('update_progress(int, PyQt_PyObject)'),
|
||||||
|
self.update_progress, Qt.QueuedConnection)
|
||||||
self.update_lock.lock()
|
self.update_lock.lock()
|
||||||
self.add_queue.append(job)
|
self.add_queue.append(job)
|
||||||
self.update_lock.unlock()
|
self.update_lock.unlock()
|
||||||
@ -370,11 +393,14 @@ class DetailView(QDialog, Ui_Dialog):
|
|||||||
self.setupUi(self)
|
self.setupUi(self)
|
||||||
self.setWindowTitle(job.description)
|
self.setWindowTitle(job.description)
|
||||||
self.job = job
|
self.job = job
|
||||||
txt = self.job.formatted_error() + self.job.formatted_log()
|
self.connect(self.job, SIGNAL('output_received()'), self.update)
|
||||||
|
self.update()
|
||||||
|
|
||||||
|
|
||||||
|
def update(self):
|
||||||
|
txt = self.job.formatted_error() + self.job.formatted_log()
|
||||||
if not txt:
|
if not txt:
|
||||||
txt = 'No details available'
|
txt = 'No details available'
|
||||||
|
|
||||||
self.log.setHtml(txt)
|
self.log.setHtml(txt)
|
||||||
|
vbar = self.log.verticalScrollBar()
|
||||||
|
vbar.setValue(vbar.maximum())
|
||||||
|
@ -303,7 +303,7 @@ class BooksModel(QAbstractTableModel):
|
|||||||
metadata.append(mi)
|
metadata.append(mi)
|
||||||
return metadata
|
return metadata
|
||||||
|
|
||||||
def get_preferred_formats(self, rows, formats):
|
def get_preferred_formats(self, rows, formats, paths=False):
|
||||||
ans = []
|
ans = []
|
||||||
for row in (row.row() for row in rows):
|
for row in (row.row() for row in rows):
|
||||||
format = None
|
format = None
|
||||||
@ -314,7 +314,8 @@ class BooksModel(QAbstractTableModel):
|
|||||||
if format:
|
if format:
|
||||||
pt = PersistentTemporaryFile(suffix='.'+format)
|
pt = PersistentTemporaryFile(suffix='.'+format)
|
||||||
pt.write(self.db.format(row, format))
|
pt.write(self.db.format(row, format))
|
||||||
pt.seek(0)
|
pt.flush()
|
||||||
|
pt.close() if paths else pt.seek(0)
|
||||||
ans.append(pt)
|
ans.append(pt)
|
||||||
else:
|
else:
|
||||||
ans.append(None)
|
ans.append(None)
|
||||||
|
@ -77,7 +77,6 @@ class Main(MainWindow, Ui_MainWindow):
|
|||||||
self.conversion_jobs = {}
|
self.conversion_jobs = {}
|
||||||
self.persistent_files = []
|
self.persistent_files = []
|
||||||
self.metadata_dialogs = []
|
self.metadata_dialogs = []
|
||||||
self.viewer_job_id = 1
|
|
||||||
self.default_thumbnail = None
|
self.default_thumbnail = None
|
||||||
self.device_error_dialog = ConversionErrorDialog(self, _('Error communicating with device'), ' ')
|
self.device_error_dialog = ConversionErrorDialog(self, _('Error communicating with device'), ' ')
|
||||||
self.device_error_dialog.setModal(Qt.NonModal)
|
self.device_error_dialog.setModal(Qt.NonModal)
|
||||||
@ -277,14 +276,6 @@ class Main(MainWindow, Ui_MainWindow):
|
|||||||
elif msg.startswith('refreshdb:'):
|
elif msg.startswith('refreshdb:'):
|
||||||
self.library_view.model().resort()
|
self.library_view.model().resort()
|
||||||
self.library_view.model().research()
|
self.library_view.model().research()
|
||||||
elif msg.startswith('progress:'):
|
|
||||||
try:
|
|
||||||
fields = msg.split(':')
|
|
||||||
job_id, percent = fields[1:3]
|
|
||||||
job_id, percent = int(job_id), float(percent)
|
|
||||||
self.job_manager.update_progress(job_id, percent)
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
else:
|
else:
|
||||||
print msg
|
print msg
|
||||||
|
|
||||||
@ -488,7 +479,7 @@ class Main(MainWindow, Ui_MainWindow):
|
|||||||
else:
|
else:
|
||||||
self.upload_books(paths, names, infos, on_card=on_card)
|
self.upload_books(paths, names, infos, on_card=on_card)
|
||||||
|
|
||||||
def upload_books(self, files, names, metadata, on_card=False):
|
def upload_books(self, files, names, metadata, on_card=False, memory=None):
|
||||||
'''
|
'''
|
||||||
Upload books to device.
|
Upload books to device.
|
||||||
@param files: List of either paths to files or file like objects
|
@param files: List of either paths to files or file like objects
|
||||||
@ -499,13 +490,13 @@ class Main(MainWindow, Ui_MainWindow):
|
|||||||
files, names, on_card=on_card,
|
files, names, on_card=on_card,
|
||||||
job_extra_description=titles
|
job_extra_description=titles
|
||||||
)
|
)
|
||||||
self.upload_memory[id] = (metadata, on_card)
|
self.upload_memory[id] = (metadata, on_card, memory)
|
||||||
|
|
||||||
def books_uploaded(self, id, description, result, exception, formatted_traceback):
|
def books_uploaded(self, id, description, result, exception, formatted_traceback):
|
||||||
'''
|
'''
|
||||||
Called once books have been uploaded.
|
Called once books have been uploaded.
|
||||||
'''
|
'''
|
||||||
metadata, on_card = self.upload_memory.pop(id)
|
metadata, on_card = self.upload_memory.pop(id)[:2]
|
||||||
if exception:
|
if exception:
|
||||||
if isinstance(exception, FreeSpaceError):
|
if isinstance(exception, FreeSpaceError):
|
||||||
where = 'in main memory.' if 'memory' in str(exception) else 'on the storage card.'
|
where = 'in main memory.' if 'memory' in str(exception) else 'on the storage card.'
|
||||||
@ -633,8 +624,9 @@ class Main(MainWindow, Ui_MainWindow):
|
|||||||
if cdata:
|
if cdata:
|
||||||
mi['cover'] = self.cover_to_thumbnail(cdata)
|
mi['cover'] = self.cover_to_thumbnail(cdata)
|
||||||
metadata = iter(metadata)
|
metadata = iter(metadata)
|
||||||
files = self.library_view.model().get_preferred_formats(rows,
|
_files = self.library_view.model().get_preferred_formats(rows,
|
||||||
self.device_manager.device_class.FORMATS)
|
self.device_manager.device_class.FORMATS, paths=True)
|
||||||
|
files = [f.name for f in _files]
|
||||||
bad, good, gf, names = [], [], [], []
|
bad, good, gf, names = [], [], [], []
|
||||||
for f in files:
|
for f in files:
|
||||||
mi = metadata.next()
|
mi = metadata.next()
|
||||||
@ -649,7 +641,9 @@ class Main(MainWindow, Ui_MainWindow):
|
|||||||
try:
|
try:
|
||||||
smi = MetaInformation(mi['title'], aus2)
|
smi = MetaInformation(mi['title'], aus2)
|
||||||
smi.comments = mi.get('comments', None)
|
smi.comments = mi.get('comments', None)
|
||||||
set_metadata(f, smi, f.name.rpartition('.')[2])
|
_f = open(f, 'r+b')
|
||||||
|
set_metadata(_f, smi, f.rpartition('.')[2])
|
||||||
|
_f.close()
|
||||||
except:
|
except:
|
||||||
print 'Error setting metadata in book:', mi['title']
|
print 'Error setting metadata in book:', mi['title']
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
@ -666,8 +660,8 @@ class Main(MainWindow, Ui_MainWindow):
|
|||||||
prefix = prefix.encode('ascii', 'ignore')
|
prefix = prefix.encode('ascii', 'ignore')
|
||||||
else:
|
else:
|
||||||
prefix = prefix.decode('ascii', 'ignore').encode('ascii', 'ignore')
|
prefix = prefix.decode('ascii', 'ignore').encode('ascii', 'ignore')
|
||||||
names.append('%s_%d%s'%(prefix, id, os.path.splitext(f.name)[1]))
|
names.append('%s_%d%s'%(prefix, id, os.path.splitext(f)[1]))
|
||||||
self.upload_books(gf, names, good, on_card)
|
self.upload_books(gf, names, good, on_card, memory=_files)
|
||||||
self.status_bar.showMessage(_('Sending books to device.'), 5000)
|
self.status_bar.showMessage(_('Sending books to device.'), 5000)
|
||||||
if bad:
|
if bad:
|
||||||
bad = '\n'.join('<li>%s</li>'%(i,) for i in bad)
|
bad = '\n'.join('<li>%s</li>'%(i,) for i in bad)
|
||||||
@ -759,6 +753,15 @@ class Main(MainWindow, Ui_MainWindow):
|
|||||||
|
|
||||||
for i, row in enumerate([r.row() for r in rows]):
|
for i, row in enumerate([r.row() for r in rows]):
|
||||||
cmdline = list(d.cmdline)
|
cmdline = list(d.cmdline)
|
||||||
|
mi = self.library_view.model().db.get_metadata(row)
|
||||||
|
if mi.title:
|
||||||
|
cmdline.extend(['--title', mi.title])
|
||||||
|
if mi.authors:
|
||||||
|
cmdline.extend(['--author', ','.join(mi.authors)])
|
||||||
|
if mi.publisher:
|
||||||
|
cmdline.extend(['--publisher', mi.publisher])
|
||||||
|
if mi.comments:
|
||||||
|
cmdline.extend(['--comment', mi.comments])
|
||||||
data = None
|
data = None
|
||||||
for fmt in LRF_PREFERRED_SOURCE_FORMATS:
|
for fmt in LRF_PREFERRED_SOURCE_FORMATS:
|
||||||
try:
|
try:
|
||||||
@ -784,7 +787,7 @@ class Main(MainWindow, Ui_MainWindow):
|
|||||||
cmdline.append(pt.name)
|
cmdline.append(pt.name)
|
||||||
id = self.job_manager.run_conversion_job(self.book_converted,
|
id = self.job_manager.run_conversion_job(self.book_converted,
|
||||||
'any2lrf', args=[cmdline],
|
'any2lrf', args=[cmdline],
|
||||||
job_description='Convert book %d of %d'%(i, len(rows)))
|
job_description='Convert book %d of %d'%(i+1, len(rows)))
|
||||||
|
|
||||||
|
|
||||||
self.conversion_jobs[id] = (d.cover_file, pt, of, d.output_format,
|
self.conversion_jobs[id] = (d.cover_file, pt, of, d.output_format,
|
||||||
@ -864,15 +867,16 @@ class Main(MainWindow, Ui_MainWindow):
|
|||||||
self._view_file(result)
|
self._view_file(result)
|
||||||
|
|
||||||
def _view_file(self, name):
|
def _view_file(self, name):
|
||||||
if name.upper().endswith('.LRF'):
|
self.setCursor(Qt.BusyCursor)
|
||||||
args = ['lrfviewer', name]
|
try:
|
||||||
self.job_manager.process_server.run('viewer%d'%self.viewer_job_id,
|
if name.upper().endswith('.LRF'):
|
||||||
'lrfviewer', kwdargs=dict(args=args),
|
args = ['lrfviewer', name]
|
||||||
monitor=False)
|
self.job_manager.process_server.run_free_job('lrfviewer', kwdargs=dict(args=args))
|
||||||
self.viewer_job_id += 1
|
else:
|
||||||
else:
|
QDesktopServices.openUrl(QUrl('file:'+name))#launch(name)
|
||||||
QDesktopServices.openUrl(QUrl('file:'+name))#launch(name)
|
time.sleep(5) # User feedback
|
||||||
time.sleep(2) # User feedback
|
finally:
|
||||||
|
self.unsetCursor()
|
||||||
|
|
||||||
def view_specific_format(self, triggered):
|
def view_specific_format(self, triggered):
|
||||||
rows = self.library_view.selectionModel().selectedRows()
|
rows = self.library_view.selectionModel().selectedRows()
|
||||||
@ -1076,7 +1080,7 @@ class Main(MainWindow, Ui_MainWindow):
|
|||||||
if getattr(exception, 'only_msg', False):
|
if getattr(exception, 'only_msg', False):
|
||||||
error_dialog(self, _('Conversion Error'), unicode(exception)).exec_()
|
error_dialog(self, _('Conversion Error'), unicode(exception)).exec_()
|
||||||
return
|
return
|
||||||
msg = u'<p><b>%s</b>: %s</p>'%exception
|
msg = u'<p><b>%s</b>: </p>'%exception
|
||||||
msg += u'<p>Failed to perform <b>job</b>: '+description
|
msg += u'<p>Failed to perform <b>job</b>: '+description
|
||||||
msg += u'<p>Detailed <b>traceback</b>:<pre>'
|
msg += u'<p>Detailed <b>traceback</b>:<pre>'
|
||||||
msg += formatted_traceback + '</pre>'
|
msg += formatted_traceback + '</pre>'
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
__license__ = 'GPL v3'
|
__license__ = 'GPL v3'
|
||||||
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
|
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||||
import textwrap, re
|
import re
|
||||||
|
|
||||||
from PyQt4.QtGui import QStatusBar, QMovie, QLabel, QFrame, QHBoxLayout, QPixmap, \
|
from PyQt4.QtGui import QStatusBar, QMovie, QLabel, QFrame, QHBoxLayout, QPixmap, \
|
||||||
QVBoxLayout, QSizePolicy, QToolButton, QIcon
|
QVBoxLayout, QSizePolicy, QToolButton, QIcon
|
||||||
|
@ -11,7 +11,10 @@ import sys, os
|
|||||||
from textwrap import TextWrapper
|
from textwrap import TextWrapper
|
||||||
|
|
||||||
from calibre import OptionParser, Settings, terminal_controller, preferred_encoding
|
from calibre import OptionParser, Settings, terminal_controller, preferred_encoding
|
||||||
from calibre.gui2 import SingleApplication
|
try:
|
||||||
|
from calibre.utils.single_qt_application import send_message
|
||||||
|
except:
|
||||||
|
send_message = None
|
||||||
from calibre.ebooks.metadata.meta import get_metadata
|
from calibre.ebooks.metadata.meta import get_metadata
|
||||||
from calibre.library.database2 import LibraryDatabase2
|
from calibre.library.database2 import LibraryDatabase2
|
||||||
from calibre.library.database import text_to_tokens
|
from calibre.library.database import text_to_tokens
|
||||||
@ -184,9 +187,8 @@ def do_add(db, paths, one_book_per_directory, recurse, add_duplicates):
|
|||||||
print '\t', title+':'
|
print '\t', title+':'
|
||||||
print '\t\t ', path
|
print '\t\t ', path
|
||||||
|
|
||||||
if SingleApplication is not None:
|
if send_message is not None:
|
||||||
sa = SingleApplication('calibre GUI')
|
send_message('refreshdb:', 'calibre GUI')
|
||||||
sa.send_message('refreshdb:')
|
|
||||||
finally:
|
finally:
|
||||||
sys.stdout = sys.__stdout__
|
sys.stdout = sys.__stdout__
|
||||||
|
|
||||||
@ -224,9 +226,9 @@ def do_remove(db, ids):
|
|||||||
for y in x:
|
for y in x:
|
||||||
db.delete_book(y)
|
db.delete_book(y)
|
||||||
|
|
||||||
if SingleApplication is not None:
|
if send_message is not None:
|
||||||
sa = SingleApplication('calibre GUI')
|
send_message('refreshdb:', 'calibre GUI')
|
||||||
sa.send_message('refreshdb:')
|
|
||||||
|
|
||||||
def command_remove(args, dbpath):
|
def command_remove(args, dbpath):
|
||||||
parser = get_parser(_(
|
parser = get_parser(_(
|
||||||
@ -339,9 +341,8 @@ def do_set_metadata(db, id, stream):
|
|||||||
mi = OPFReader(stream)
|
mi = OPFReader(stream)
|
||||||
db.set_metadata(id, mi)
|
db.set_metadata(id, mi)
|
||||||
do_show_metadata(db, id, False)
|
do_show_metadata(db, id, False)
|
||||||
if SingleApplication is not None:
|
if send_message is not None:
|
||||||
sa = SingleApplication('calibre GUI')
|
send_message('refreshdb:', 'calibre GUI')
|
||||||
sa.send_message('refreshdb:')
|
|
||||||
|
|
||||||
def command_set_metadata(args, dbpath):
|
def command_set_metadata(args, dbpath):
|
||||||
parser = get_parser(_(
|
parser = get_parser(_(
|
||||||
|
@ -1414,11 +1414,13 @@ ALTER TABLE books ADD COLUMN isbn TEXT DEFAULT "" COLLATE NOCASE;
|
|||||||
mi = OPFCreator(base, self.get_metadata(idx, index_is_id=index_is_id))
|
mi = OPFCreator(base, self.get_metadata(idx, index_is_id=index_is_id))
|
||||||
cover = self.cover(idx, index_is_id=index_is_id)
|
cover = self.cover(idx, index_is_id=index_is_id)
|
||||||
if cover is not None:
|
if cover is not None:
|
||||||
cname = name + '.jpg'
|
cname = sanitize_file_name(name) + '.jpg'
|
||||||
cpath = os.path.join(base, cname)
|
cpath = os.path.join(base, cname)
|
||||||
open(cpath, 'wb').write(cover)
|
open(cpath, 'wb').write(cover)
|
||||||
mi.cover = cname
|
mi.cover = cname
|
||||||
f = open(os.path.join(base, sanitize_file_name(name)+'.opf'), 'wb')
|
f = open(os.path.join(base, sanitize_file_name(name)+'.opf'), 'wb')
|
||||||
|
if not mi.authors:
|
||||||
|
mi.authors = [_('Unknown')]
|
||||||
mi.render(f)
|
mi.render(f)
|
||||||
f.close()
|
f.close()
|
||||||
|
|
||||||
|
@ -8,6 +8,8 @@ Download and install the linux binary.
|
|||||||
'''
|
'''
|
||||||
import sys, os, shutil, tarfile, subprocess, tempfile, urllib2, re, stat
|
import sys, os, shutil, tarfile, subprocess, tempfile, urllib2, re, stat
|
||||||
|
|
||||||
|
MOBILEREAD='https://dev.mobileread.com/dist/kovid/calibre/'
|
||||||
|
|
||||||
class TerminalController:
|
class TerminalController:
|
||||||
"""
|
"""
|
||||||
A class that can be used to portably generate formatted output to
|
A class that can be used to portably generate formatted output to
|
||||||
@ -239,7 +241,7 @@ def do_postinstall(destdir):
|
|||||||
|
|
||||||
def download_tarball():
|
def download_tarball():
|
||||||
pb = ProgressBar(TerminalController(sys.stdout), 'Downloading calibre...')
|
pb = ProgressBar(TerminalController(sys.stdout), 'Downloading calibre...')
|
||||||
src = urllib2.urlopen('http://calibre.kovidgoyal.net/downloads/latest-linux-binary.tar.bz2')
|
src = urllib2.urlopen(MOBILEREAD+'calibre-%version-i686.tar.bz2')
|
||||||
size = int(src.info()['content-length'])
|
size = int(src.info()['content-length'])
|
||||||
f = tempfile.NamedTemporaryFile()
|
f = tempfile.NamedTemporaryFile()
|
||||||
while f.tell() < size:
|
while f.tell() < size:
|
||||||
|
@ -1,21 +1,26 @@
|
|||||||
|
from __future__ import with_statement
|
||||||
__license__ = 'GPL v3'
|
__license__ = 'GPL v3'
|
||||||
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
|
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||||
'''
|
'''
|
||||||
Used to run jobs in parallel in separate processes.
|
Used to run jobs in parallel in separate processes.
|
||||||
'''
|
'''
|
||||||
import re, sys, tempfile, os, cPickle, traceback, atexit, binascii, time, subprocess
|
import sys, os, gc, cPickle, traceback, atexit, cStringIO, time, \
|
||||||
|
subprocess, socket, collections, binascii
|
||||||
|
from select import select
|
||||||
from functools import partial
|
from functools import partial
|
||||||
|
from threading import RLock, Thread, Event
|
||||||
|
|
||||||
from calibre.ebooks.lrf.any.convert_from import main as any2lrf
|
from calibre.ebooks.lrf.any.convert_from import main as any2lrf
|
||||||
from calibre.ebooks.lrf.web.convert_from import main as web2lrf
|
from calibre.ebooks.lrf.web.convert_from import main as web2lrf
|
||||||
from calibre.ebooks.lrf.feeds.convert_from import main as feeds2lrf
|
from calibre.ebooks.lrf.feeds.convert_from import main as feeds2lrf
|
||||||
from calibre.gui2.lrf_renderer.main import main as lrfviewer
|
from calibre.gui2.lrf_renderer.main import main as lrfviewer
|
||||||
from calibre import iswindows, __appname__, islinux
|
from calibre.ptempfile import PersistentTemporaryFile
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from calibre.utils.single_qt_application import SingleApplication
|
from calibre.ebooks.lrf.html.table_as_image import do_render as render_table
|
||||||
except:
|
except: # Dont fail is PyQt4.4 not present
|
||||||
SingleApplication = None
|
render_table = None
|
||||||
|
from calibre import iswindows, islinux, detect_ncpus
|
||||||
|
|
||||||
sa = None
|
sa = None
|
||||||
job_id = None
|
job_id = None
|
||||||
@ -25,12 +30,14 @@ def report_progress(percent, msg=''):
|
|||||||
msg = 'progress:%s:%f:%s'%(job_id, percent, msg)
|
msg = 'progress:%s:%f:%s'%(job_id, percent, msg)
|
||||||
sa.send_message(msg)
|
sa.send_message(msg)
|
||||||
|
|
||||||
|
_notify = 'fskjhwseiuyweoiu987435935-0342'
|
||||||
|
|
||||||
PARALLEL_FUNCS = {
|
PARALLEL_FUNCS = {
|
||||||
'any2lrf' : partial(any2lrf, gui_mode=True),
|
'any2lrf' : partial(any2lrf, gui_mode=True),
|
||||||
'web2lrf' : web2lrf,
|
'web2lrf' : web2lrf,
|
||||||
'lrfviewer' : lrfviewer,
|
'lrfviewer' : lrfviewer,
|
||||||
'feeds2lrf' : partial(feeds2lrf, notification=report_progress),
|
'feeds2lrf' : partial(feeds2lrf, notification=_notify),
|
||||||
|
'render_table': render_table,
|
||||||
}
|
}
|
||||||
|
|
||||||
python = sys.executable
|
python = sys.executable
|
||||||
@ -41,138 +48,463 @@ if iswindows:
|
|||||||
python = os.path.join(os.path.dirname(python), 'parallel.exe')
|
python = os.path.join(os.path.dirname(python), 'parallel.exe')
|
||||||
else:
|
else:
|
||||||
python = os.path.join(os.path.dirname(python), 'Scripts\\parallel.exe')
|
python = os.path.join(os.path.dirname(python), 'Scripts\\parallel.exe')
|
||||||
popen = partial(subprocess.Popen, creationflags=0x08) # CREATE_NO_WINDOW=0x08 so that no ugly console is popped up
|
open = partial(subprocess.Popen, creationflags=0x08) # CREATE_NO_WINDOW=0x08 so that no ugly console is popped up
|
||||||
|
|
||||||
if islinux and hasattr(sys, 'frozen_path'):
|
if islinux and hasattr(sys, 'frozen_path'):
|
||||||
python = os.path.join(getattr(sys, 'frozen_path'), 'parallel')
|
python = os.path.join(getattr(sys, 'frozen_path'), 'calibre-parallel')
|
||||||
popen = partial(subprocess.Popen, cwd=getattr(sys, 'frozen_path'))
|
popen = partial(subprocess.Popen, cwd=getattr(sys, 'frozen_path'))
|
||||||
|
|
||||||
def cleanup(tdir):
|
prefix = 'import sys; sys.in_worker = True; '
|
||||||
try:
|
if hasattr(sys, 'frameworks_dir'):
|
||||||
import shutil
|
fd = getattr(sys, 'frameworks_dir')
|
||||||
shutil.rmtree(tdir, True)
|
prefix += 'sys.frameworks_dir = "%s"; sys.frozen = "macosx_app"; '%fd
|
||||||
except:
|
if fd not in os.environ['PATH']:
|
||||||
pass
|
os.environ['PATH'] += ':'+fd
|
||||||
|
if 'parallel' in python:
|
||||||
|
executable = [python]
|
||||||
|
worker_command = '%s:%s'
|
||||||
|
free_spirit_command = '%s'
|
||||||
|
else:
|
||||||
|
executable = [python, '-c']
|
||||||
|
worker_command = prefix + 'from calibre.parallel import worker; worker(%s, %s)'
|
||||||
|
free_spirit_command = prefix + 'from calibre.parallel import free_spirit; free_spirit(%s)'
|
||||||
|
|
||||||
class Server(object):
|
def write(socket, msg, timeout=5):
|
||||||
|
if isinstance(msg, unicode):
|
||||||
|
msg = msg.encode('utf-8')
|
||||||
|
length = None
|
||||||
|
while len(msg) > 0:
|
||||||
|
if length is None:
|
||||||
|
length = len(msg)
|
||||||
|
chunk = ('%-12d'%length) + msg[:4096-12]
|
||||||
|
msg = msg[4096-12:]
|
||||||
|
else:
|
||||||
|
chunk, msg = msg[:4096], msg[4096:]
|
||||||
|
w = select([], [socket], [], timeout)[1]
|
||||||
|
if not w:
|
||||||
|
raise RuntimeError('Write to socket timed out')
|
||||||
|
if socket.sendall(chunk) is not None:
|
||||||
|
raise RuntimeError('Failed to write chunk to socket')
|
||||||
|
|
||||||
|
|
||||||
|
def read(socket, timeout=5):
|
||||||
|
buf = cStringIO.StringIO()
|
||||||
|
length = None
|
||||||
|
while select([socket],[],[],timeout)[0]:
|
||||||
|
msg = socket.recv(4096)
|
||||||
|
if not msg:
|
||||||
|
break
|
||||||
|
if length is None:
|
||||||
|
length, msg = int(msg[:12]), msg[12:]
|
||||||
|
buf.write(msg)
|
||||||
|
if buf.tell() >= length:
|
||||||
|
break
|
||||||
|
if not length:
|
||||||
|
return ''
|
||||||
|
msg = buf.getvalue()[:length]
|
||||||
|
if len(msg) < length:
|
||||||
|
raise RuntimeError('Corrupted packet received')
|
||||||
|
|
||||||
|
return msg
|
||||||
|
|
||||||
|
class RepeatingTimer(Thread):
|
||||||
|
|
||||||
|
def repeat(self):
|
||||||
|
while True:
|
||||||
|
self.event.wait(self.interval)
|
||||||
|
if self.event.isSet():
|
||||||
|
break
|
||||||
|
self.action()
|
||||||
|
|
||||||
|
def __init__(self, interval, func):
|
||||||
|
self.event = Event()
|
||||||
|
self.interval = interval
|
||||||
|
self.action = func
|
||||||
|
Thread.__init__(self, target=self.repeat)
|
||||||
|
self.setDaemon(True)
|
||||||
|
|
||||||
|
class ControlError(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class Overseer(object):
|
||||||
|
|
||||||
#: Interval in seconds at which child processes are polled for status information
|
|
||||||
INTERVAL = 0.1
|
|
||||||
KILL_RESULT = 'Server: job killed by user|||#@#$%&*)*(*$#$%#$@&'
|
KILL_RESULT = 'Server: job killed by user|||#@#$%&*)*(*$#$%#$@&'
|
||||||
|
INTERVAL = 0.1
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self, server, port, timeout=5):
|
||||||
self.tdir = tempfile.mkdtemp('', '%s_IPC_'%__appname__)
|
self.cmd = worker_command%(repr('127.0.0.1'), repr(port))
|
||||||
atexit.register(cleanup, self.tdir)
|
self.process = popen(executable + [self.cmd])
|
||||||
self.kill_jobs = []
|
self.socket = server.accept()[0]
|
||||||
|
|
||||||
def kill(self, job_id):
|
self.working = False
|
||||||
'''
|
self.timeout = timeout
|
||||||
Kill the job identified by job_id.
|
self.last_job_time = time.time()
|
||||||
'''
|
self.job_id = None
|
||||||
self.kill_jobs.append(str(job_id))
|
self._stop = False
|
||||||
|
if not select([self.socket], [], [], 120)[0]:
|
||||||
|
raise RuntimeError(_('Could not launch worker process.'))
|
||||||
|
ID = self.read().split(':')
|
||||||
|
if ID[0] != 'CALIBRE_WORKER':
|
||||||
|
raise RuntimeError('Impostor')
|
||||||
|
self.worker_pid = int(ID[1])
|
||||||
|
self.write('OK')
|
||||||
|
if self.read() != 'WAITING':
|
||||||
|
raise RuntimeError('Worker sulking')
|
||||||
|
|
||||||
def _terminate(self, process):
|
def terminate(self):
|
||||||
'''
|
'''
|
||||||
Kill process.
|
Kill process.
|
||||||
'''
|
'''
|
||||||
|
try:
|
||||||
|
if self.socket:
|
||||||
|
self.write('STOP:')
|
||||||
|
time.sleep(1)
|
||||||
|
self.socket.shutdown(socket.SHUT_RDWR)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
if iswindows:
|
if iswindows:
|
||||||
win32api = __import__('win32api')
|
win32api = __import__('win32api')
|
||||||
try:
|
try:
|
||||||
win32api.TerminateProcess(int(process.pid), -1)
|
handle = win32api.OpenProcess(1, False, self.worker_pid)
|
||||||
|
win32api.TerminateProcess(handle, -1)
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
import signal
|
import signal
|
||||||
os.kill(process.pid, signal.SIGKILL)
|
try:
|
||||||
time.sleep(0.05)
|
os.kill(self.worker_pid, signal.SIGKILL)
|
||||||
|
time.sleep(0.05)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def write(self, msg, timeout=None):
|
||||||
|
write(self.socket, msg, timeout=self.timeout if timeout is None else timeout)
|
||||||
|
|
||||||
|
def read(self, timeout=None):
|
||||||
|
return read(self.socket, timeout=self.timeout if timeout is None else timeout)
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
return hasattr(other, 'process') and hasattr(other, 'worker_pid') and self.worker_pid == other.worker_pid
|
||||||
|
|
||||||
|
def __bool__(self):
|
||||||
|
self.process.poll()
|
||||||
|
return self.process.returncode is None
|
||||||
|
|
||||||
|
def pid(self):
|
||||||
|
return self.worker_pid
|
||||||
|
|
||||||
|
def select(self, timeout=0):
|
||||||
|
return select([self.socket], [self.socket], [self.socket], timeout)
|
||||||
|
|
||||||
|
def initialize_job(self, job):
|
||||||
|
self.job_id = job.job_id
|
||||||
|
self.working = True
|
||||||
|
self.write('JOB:'+cPickle.dumps((job.func, job.args, job.kwdargs), -1))
|
||||||
|
msg = self.read()
|
||||||
|
if msg != 'OK':
|
||||||
|
raise ControlError('Failed to initialize job on worker %d:%s'%(self.worker_pid, msg))
|
||||||
|
self.output = job.output if callable(job.output) else sys.stdout.write
|
||||||
|
self.progress = job.progress if callable(job.progress) else None
|
||||||
|
self.job = job
|
||||||
|
|
||||||
|
def control(self):
|
||||||
|
try:
|
||||||
|
if select([self.socket],[],[],0)[0]:
|
||||||
|
msg = self.read()
|
||||||
|
word, msg = msg.partition(':')[0], msg.partition(':')[-1]
|
||||||
|
if word == 'RESULT':
|
||||||
|
self.write('OK')
|
||||||
|
return Result(cPickle.loads(msg), None, None)
|
||||||
|
elif word == 'OUTPUT':
|
||||||
|
self.write('OK')
|
||||||
|
try:
|
||||||
|
self.output(''.join(cPickle.loads(msg)))
|
||||||
|
except:
|
||||||
|
self.output('Bad output message: '+ repr(msg))
|
||||||
|
elif word == 'PROGRESS':
|
||||||
|
self.write('OK')
|
||||||
|
percent = None
|
||||||
|
try:
|
||||||
|
percent, msg = cPickle.loads(msg)[-1]
|
||||||
|
except:
|
||||||
|
print 'Bad progress update:', repr(msg)
|
||||||
|
if self.progress and percent is not None:
|
||||||
|
self.progress(percent, msg)
|
||||||
|
elif word == 'ERROR':
|
||||||
|
self.write('OK')
|
||||||
|
return Result(None, *cPickle.loads(msg))
|
||||||
|
else:
|
||||||
|
self.terminate()
|
||||||
|
return Result(None, ControlError('Worker sent invalid msg: %s', repr(msg)), '')
|
||||||
|
self.process.poll()
|
||||||
|
if self.process.returncode is not None:
|
||||||
|
return Result(None, ControlError('Worker process died unexpectedly with returncode: %d'%self.process.returncode), '')
|
||||||
|
finally:
|
||||||
|
self.working = False
|
||||||
|
self.last_job_time = time.time()
|
||||||
|
|
||||||
|
class Job(object):
|
||||||
|
|
||||||
|
def __init__(self, job_id, func, args, kwdargs, output, progress, done):
|
||||||
|
self.job_id = job_id
|
||||||
|
self.func = func
|
||||||
|
self.args = args
|
||||||
|
self.kwdargs = kwdargs
|
||||||
|
self.output = output
|
||||||
|
self.progress = progress
|
||||||
|
self.done = done
|
||||||
|
|
||||||
|
class Result(object):
|
||||||
|
|
||||||
|
def __init__(self, result, exception, traceback):
|
||||||
|
self.result = result
|
||||||
|
self.exception = exception
|
||||||
|
self.traceback = traceback
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
return 3
|
||||||
|
|
||||||
|
def __item__(self, i):
|
||||||
|
return (self.result, self.exception, self.traceback)[i]
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
return iter((self.result, self.exception, self.traceback))
|
||||||
|
|
||||||
|
class Server(Thread):
|
||||||
|
|
||||||
|
KILL_RESULT = Overseer.KILL_RESULT
|
||||||
|
START_PORT = 10013
|
||||||
|
|
||||||
|
def __init__(self, number_of_workers=detect_ncpus()):
|
||||||
|
Thread.__init__(self)
|
||||||
|
self.setDaemon(True)
|
||||||
|
self.server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
self.port = self.START_PORT
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
self.server_socket.bind(('localhost', self.port))
|
||||||
|
break
|
||||||
|
except:
|
||||||
|
self.port += 1
|
||||||
|
self.server_socket.listen(5)
|
||||||
|
self.number_of_workers = number_of_workers
|
||||||
|
self.pool, self.jobs, self.working, self.results = [], collections.deque(), [], {}
|
||||||
|
atexit.register(self.killall)
|
||||||
|
atexit.register(self.close)
|
||||||
|
self.job_lock = RLock()
|
||||||
|
self.overseer_lock = RLock()
|
||||||
|
self.working_lock = RLock()
|
||||||
|
self.result_lock = RLock()
|
||||||
|
self.pool_lock = RLock()
|
||||||
|
self.start()
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
try:
|
||||||
|
self.server_socket.shutdown(socket.SHUT_RDWR)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def add_job(self, job):
|
||||||
|
with self.job_lock:
|
||||||
|
self.jobs.append(job)
|
||||||
|
|
||||||
|
def store_result(self, result, id=None):
|
||||||
|
if id:
|
||||||
|
with self.job_lock:
|
||||||
|
self.results[id] = result
|
||||||
|
|
||||||
|
def result(self, id):
|
||||||
|
with self.result_lock:
|
||||||
|
return self.results.pop(id, None)
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
while True:
|
||||||
|
job = None
|
||||||
|
with self.job_lock:
|
||||||
|
if len(self.jobs) > 0 and len(self.working) < self.number_of_workers:
|
||||||
|
job = self.jobs.popleft()
|
||||||
|
with self.pool_lock:
|
||||||
|
o = self.pool.pop() if self.pool else Overseer(self.server_socket, self.port)
|
||||||
|
try:
|
||||||
|
o.initialize_job(job)
|
||||||
|
except Exception, err:
|
||||||
|
res = Result(None, unicode(err), traceback.format_exc())
|
||||||
|
job.done(res)
|
||||||
|
o.terminate()
|
||||||
|
o = None
|
||||||
|
if o:
|
||||||
|
with self.working_lock:
|
||||||
|
self.working.append(o)
|
||||||
|
|
||||||
|
with self.working_lock:
|
||||||
|
done = []
|
||||||
|
for o in self.working:
|
||||||
|
try:
|
||||||
|
res = o.control()
|
||||||
|
except Exception, err:
|
||||||
|
res = Result(None, unicode(err), traceback.format_exc())
|
||||||
|
o.terminate()
|
||||||
|
if isinstance(res, Result):
|
||||||
|
o.job.done(res)
|
||||||
|
done.append(o)
|
||||||
|
for o in done:
|
||||||
|
self.working.remove(o)
|
||||||
|
if o:
|
||||||
|
with self.pool_lock:
|
||||||
|
self.pool.append(o)
|
||||||
|
|
||||||
|
time.sleep(1)
|
||||||
|
|
||||||
|
|
||||||
|
def killall(self):
|
||||||
|
with self.pool_lock:
|
||||||
|
map(lambda x: x.terminate(), self.pool)
|
||||||
|
self.pool = []
|
||||||
|
|
||||||
|
|
||||||
|
def kill(self, job_id):
|
||||||
|
with self.working_lock:
|
||||||
|
pop = None
|
||||||
|
for o in self.working:
|
||||||
|
if o.job_id == job_id:
|
||||||
|
o.terminate()
|
||||||
|
o.job.done(Result(self.KILL_RESULT, None, ''))
|
||||||
|
pop = o
|
||||||
|
break
|
||||||
|
if pop is not None:
|
||||||
|
self.working.remove(pop)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def run(self, job_id, func, args=[], kwdargs={}, monitor=True):
|
def run_job(self, job_id, func, args=[], kwdargs={},
|
||||||
|
output=None, progress=None, done=None):
|
||||||
'''
|
'''
|
||||||
Run a job in a separate process.
|
Run a job in a separate process. Supports job control, output redirection
|
||||||
@param job_id: A unique (per server) identifier
|
and progress reporting.
|
||||||
@param func: One of C{PARALLEL_FUNCS.keys()}
|
|
||||||
@param args: A list of arguments to pass of C{func}
|
|
||||||
@param kwdargs: A dictionary of keyword arguments to pass to C{func}
|
|
||||||
@param monitor: If False launch the child process and return. Do not monitor/communicate with it.
|
|
||||||
@return: (result, exception, formatted_traceback, log) where log is the combined
|
|
||||||
stdout + stderr of the child process; or None if monitor is True. If a job is killed
|
|
||||||
by a call to L{kill()} then result will be L{KILL_RESULT}
|
|
||||||
'''
|
'''
|
||||||
job_id = str(job_id)
|
if done is None:
|
||||||
job_dir = os.path.join(self.tdir, job_id)
|
done = partial(self.store_result, id=job_id)
|
||||||
if os.path.exists(job_dir):
|
job = Job(job_id, func, args, kwdargs, output, progress, done)
|
||||||
raise ValueError('Cannot run job. The job_id %s has already been used.'%job_id)
|
with self.job_lock:
|
||||||
os.mkdir(job_dir)
|
self.jobs.append(job)
|
||||||
|
|
||||||
job_data = os.path.join(job_dir, 'job_data.pickle')
|
def run_free_job(self, func, args=[], kwdargs={}):
|
||||||
cPickle.dump((job_id, func, args, kwdargs), open(job_data, 'wb'), -1)
|
pt = PersistentTemporaryFile('.pickle', '_IPC_')
|
||||||
prefix = ''
|
pt.write(cPickle.dumps((func, args, kwdargs)))
|
||||||
if hasattr(sys, 'frameworks_dir'):
|
pt.close()
|
||||||
fd = getattr(sys, 'frameworks_dir')
|
cmd = free_spirit_command%repr(binascii.hexlify(pt.name))
|
||||||
prefix = 'import sys; sys.frameworks_dir = "%s"; sys.frozen = "macosx_app"; '%fd
|
popen(executable + [cmd])
|
||||||
if fd not in os.environ['PATH']:
|
|
||||||
os.environ['PATH'] += ':'+fd
|
|
||||||
cmd = prefix + 'from calibre.parallel import run_job; run_job(\'%s\')'%binascii.hexlify(job_data)
|
|
||||||
|
|
||||||
if not monitor:
|
##########################################################################################
|
||||||
popen([python, '-c', cmd], stdout=subprocess.PIPE, stdin=subprocess.PIPE,
|
##################################### CLIENT CODE #####################################
|
||||||
stderr=subprocess.PIPE)
|
##########################################################################################
|
||||||
|
|
||||||
|
class BufferedSender(object):
|
||||||
|
|
||||||
|
def __init__(self, socket):
|
||||||
|
self.socket = socket
|
||||||
|
self.wbuf, self.pbuf = [], []
|
||||||
|
self.wlock, self.plock = RLock(), RLock()
|
||||||
|
self.timer = RepeatingTimer(0.5, self.send)
|
||||||
|
self.prefix = prefix
|
||||||
|
self.timer.start()
|
||||||
|
|
||||||
|
def write(self, msg):
|
||||||
|
if not isinstance(msg, basestring):
|
||||||
|
msg = unicode(msg)
|
||||||
|
with self.wlock:
|
||||||
|
self.wbuf.append(msg)
|
||||||
|
|
||||||
|
def send(self):
|
||||||
|
if not select([], [self.socket], [], 30)[1]:
|
||||||
|
print >>sys.__stderr__, 'Cannot pipe to overseer'
|
||||||
return
|
return
|
||||||
|
|
||||||
output = open(os.path.join(job_dir, 'output.txt'), 'wb')
|
with self.wlock:
|
||||||
p = popen([python, '-c', cmd], stdout=output, stderr=output,
|
if self.wbuf:
|
||||||
stdin=subprocess.PIPE)
|
msg = cPickle.dumps(self.wbuf, -1)
|
||||||
p.stdin.close()
|
self.wbuf = []
|
||||||
while p.returncode is None:
|
write(self.socket, 'OUTPUT:'+msg)
|
||||||
if job_id in self.kill_jobs:
|
read(self.socket, 10)
|
||||||
self._terminate(p)
|
|
||||||
return self.KILL_RESULT, None, None, _('Job killed by user')
|
|
||||||
time.sleep(0.1)
|
|
||||||
p.poll()
|
|
||||||
|
|
||||||
|
with self.plock:
|
||||||
|
if self.pbuf:
|
||||||
|
msg = cPickle.dumps(self.pbuf, -1)
|
||||||
|
self.pbuf = []
|
||||||
|
write(self.socket, 'PROGRESS:'+msg)
|
||||||
|
read(self.socket, 10)
|
||||||
|
|
||||||
output.close()
|
def notify(self, percent, msg=''):
|
||||||
job_result = os.path.join(job_dir, 'job_result.pickle')
|
with self.plock:
|
||||||
if not os.path.exists(job_result):
|
self.pbuf.append((percent, msg))
|
||||||
result, exception, traceback = None, ('ParallelRuntimeError',
|
|
||||||
'The worker process died unexpectedly.'), ''
|
|
||||||
else:
|
|
||||||
result, exception, traceback = cPickle.load(open(job_result, 'rb'))
|
|
||||||
log = open(output.name, 'rb').read()
|
|
||||||
|
|
||||||
return result, exception, traceback, log
|
def flush(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def work(client_socket, func, args, kwdargs):
|
||||||
def run_job(job_data):
|
|
||||||
global sa, job_id
|
|
||||||
if SingleApplication is not None:
|
|
||||||
sa = SingleApplication('calibre GUI')
|
|
||||||
job_data = binascii.unhexlify(job_data)
|
|
||||||
base = os.path.dirname(job_data)
|
|
||||||
job_result = os.path.join(base, 'job_result.pickle')
|
|
||||||
job_id, func, args, kwdargs = cPickle.load(open(job_data, 'rb'))
|
|
||||||
func = PARALLEL_FUNCS[func]
|
func = PARALLEL_FUNCS[func]
|
||||||
exception, tb = None, None
|
if hasattr(func, 'keywords'):
|
||||||
|
for key, val in func.keywords.items():
|
||||||
|
if val == _notify and hasattr(sys.stdout, 'notify'):
|
||||||
|
func.keywords[key] = sys.stdout.notify
|
||||||
|
res = func(*args, **kwdargs)
|
||||||
|
if hasattr(sys.stdout, 'send'):
|
||||||
|
sys.stdout.send()
|
||||||
|
return res
|
||||||
|
|
||||||
|
|
||||||
|
def worker(host, port):
|
||||||
|
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
client_socket.connect((host, port))
|
||||||
|
write(client_socket, 'CALIBRE_WORKER:%d'%os.getpid())
|
||||||
|
msg = read(client_socket, timeout=10)
|
||||||
|
if msg != 'OK':
|
||||||
|
return 1
|
||||||
|
write(client_socket, 'WAITING')
|
||||||
|
|
||||||
|
sys.stdout = BufferedSender(client_socket)
|
||||||
|
sys.stderr = sys.stdout
|
||||||
|
|
||||||
|
while True:
|
||||||
|
msg = read(client_socket, timeout=60)
|
||||||
|
if msg.startswith('JOB:'):
|
||||||
|
func, args, kwdargs = cPickle.loads(msg[4:])
|
||||||
|
write(client_socket, 'OK')
|
||||||
|
try:
|
||||||
|
result = work(client_socket, func, args, kwdargs)
|
||||||
|
write(client_socket, 'RESULT:'+ cPickle.dumps(result))
|
||||||
|
except (Exception, SystemExit), err:
|
||||||
|
exception = (err.__class__.__name__, unicode(str(err), 'utf-8', 'replace'))
|
||||||
|
tb = traceback.format_exc()
|
||||||
|
write(client_socket, 'ERROR:'+cPickle.dumps((exception, tb),-1))
|
||||||
|
if read(client_socket, 10) != 'OK':
|
||||||
|
break
|
||||||
|
gc.collect()
|
||||||
|
elif msg == 'STOP:':
|
||||||
|
return 0
|
||||||
|
elif not msg:
|
||||||
|
time.sleep(1)
|
||||||
|
else:
|
||||||
|
print >>sys.__stderr__, 'Invalid protocols message', msg
|
||||||
|
return 1
|
||||||
|
|
||||||
|
def free_spirit(path):
|
||||||
|
func, args, kwdargs = cPickle.load(open(binascii.unhexlify(path), 'rb'))
|
||||||
try:
|
try:
|
||||||
result = func(*args, **kwdargs)
|
os.unlink(path)
|
||||||
except (Exception, SystemExit), err:
|
except:
|
||||||
result = None
|
pass
|
||||||
exception = (err.__class__.__name__, unicode(str(err), 'utf-8', 'replace'))
|
PARALLEL_FUNCS[func](*args, **kwdargs)
|
||||||
tb = traceback.format_exc()
|
|
||||||
|
|
||||||
if os.path.exists(os.path.dirname(job_result)):
|
|
||||||
cPickle.dump((result, exception, tb), open(job_result, 'wb'))
|
|
||||||
|
|
||||||
def main():
|
|
||||||
src = sys.argv[2]
|
|
||||||
job_data = re.search(r'run_job\(\'([a-f0-9A-F]+)\'\)', src).group(1)
|
|
||||||
run_job(job_data)
|
|
||||||
|
|
||||||
|
def main(args=sys.argv):
|
||||||
|
args = args[1].split(':')
|
||||||
|
if len(args) == 1:
|
||||||
|
free_spirit(args[0].replace("'", ''))
|
||||||
|
else:
|
||||||
|
worker(args[0].replace("'", ''), int(args[1]))
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
sys.exit(main())
|
sys.exit(main())
|
||||||
|
|
||||||
|
|
||||||
|
@ -94,7 +94,7 @@ class TerminalController:
|
|||||||
except: return
|
except: return
|
||||||
|
|
||||||
# If the stream isn't a tty, then assume it has no capabilities.
|
# If the stream isn't a tty, then assume it has no capabilities.
|
||||||
if not hasattr(term_stream, 'isatty') or not term_stream.isatty(): return
|
if hasattr(sys, 'in_worker') or not hasattr(term_stream, 'isatty') or not term_stream.isatty(): return
|
||||||
|
|
||||||
# Check the terminal type. If we fail, then assume that the
|
# Check the terminal type. If we fail, then assume that the
|
||||||
# terminal has no capabilities.
|
# terminal has no capabilities.
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
__license__ = 'GPL v3'
|
__license__ = 'GPL v3'
|
||||||
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
|
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||||
import re, glob
|
import re
|
||||||
from pkg_resources import resource_filename
|
from pkg_resources import resource_filename
|
||||||
|
|
||||||
from trac.core import Component, implements
|
from trac.core import Component, implements
|
||||||
@ -12,7 +12,7 @@ from trac.util import Markup
|
|||||||
__appname__ = 'calibre'
|
__appname__ = 'calibre'
|
||||||
DOWNLOAD_DIR = '/var/www/calibre.kovidgoyal.net/htdocs/downloads'
|
DOWNLOAD_DIR = '/var/www/calibre.kovidgoyal.net/htdocs/downloads'
|
||||||
LINUX_INSTALLER = '/var/www/calibre.kovidgoyal.net/calibre/src/calibre/linux_installer.py'
|
LINUX_INSTALLER = '/var/www/calibre.kovidgoyal.net/calibre/src/calibre/linux_installer.py'
|
||||||
|
MOBILEREAD = 'https://dev.mobileread.com/dist/kovid/calibre/'
|
||||||
|
|
||||||
class OS(dict):
|
class OS(dict):
|
||||||
"""Dictionary with a default value for unknown keys."""
|
"""Dictionary with a default value for unknown keys."""
|
||||||
@ -119,7 +119,7 @@ class Download(Component):
|
|||||||
if req.path_info == '/download':
|
if req.path_info == '/download':
|
||||||
return self.top_level(req)
|
return self.top_level(req)
|
||||||
elif req.path_info == '/download_linux_binary_installer':
|
elif req.path_info == '/download_linux_binary_installer':
|
||||||
req.send(open(LINUX_INSTALLER).read(), 'text/x-python')
|
req.send(open(LINUX_INSTALLER).read().replace('%version', self.version_from_filename()), 'text/x-python')
|
||||||
else:
|
else:
|
||||||
match = re.match(r'\/download_(\S+)', req.path_info)
|
match = re.match(r'\/download_(\S+)', req.path_info)
|
||||||
if match:
|
if match:
|
||||||
@ -153,8 +153,7 @@ class Download(Component):
|
|||||||
|
|
||||||
def version_from_filename(self):
|
def version_from_filename(self):
|
||||||
try:
|
try:
|
||||||
file = glob.glob(DOWNLOAD_DIR+'/*.exe')[0]
|
return open(DOWNLOAD_DIR+'/latest_version', 'rb').read().strip()
|
||||||
return re.search(r'\S+-(\d+\.\d+\.\d+)\.', file).group(1)
|
|
||||||
except:
|
except:
|
||||||
return '0.0.0'
|
return '0.0.0'
|
||||||
|
|
||||||
@ -165,7 +164,7 @@ class Download(Component):
|
|||||||
installer_name='Windows installer',
|
installer_name='Windows installer',
|
||||||
title='Download %s for windows'%(__appname__),
|
title='Download %s for windows'%(__appname__),
|
||||||
compatibility='%s works on Windows XP and Windows Vista.'%(__appname__,),
|
compatibility='%s works on Windows XP and Windows Vista.'%(__appname__,),
|
||||||
path='/downloads/'+file, app=__appname__,
|
path=MOBILEREAD+file, app=__appname__,
|
||||||
note=Markup(\
|
note=Markup(\
|
||||||
'''
|
'''
|
||||||
<p>If you are using the <b>SONY PRS-500</b> and %(appname)s does not detect your reader, read on:</p>
|
<p>If you are using the <b>SONY PRS-500</b> and %(appname)s does not detect your reader, read on:</p>
|
||||||
@ -203,7 +202,7 @@ You can uninstall a driver by right clicking on it and selecting uninstall.
|
|||||||
installer_name='OS X universal dmg',
|
installer_name='OS X universal dmg',
|
||||||
title='Download %s for OS X'%(__appname__),
|
title='Download %s for OS X'%(__appname__),
|
||||||
compatibility='%s works on OS X Tiger and above.'%(__appname__,),
|
compatibility='%s works on OS X Tiger and above.'%(__appname__,),
|
||||||
path='/downloads/'+file, app=__appname__,
|
path=MOBILEREAD+file, app=__appname__,
|
||||||
note=Markup(\
|
note=Markup(\
|
||||||
'''
|
'''
|
||||||
<ol>
|
<ol>
|
||||||
|
@ -7,7 +7,7 @@ __docformat__ = 'restructuredtext en'
|
|||||||
Enforces running of only a single application instance and allows for messaging between
|
Enforces running of only a single application instance and allows for messaging between
|
||||||
applications using a local socket.
|
applications using a local socket.
|
||||||
'''
|
'''
|
||||||
import atexit
|
import atexit, os
|
||||||
|
|
||||||
from PyQt4.QtCore import QByteArray, QDataStream, QIODevice, SIGNAL, QObject, Qt, QString
|
from PyQt4.QtCore import QByteArray, QDataStream, QIODevice, SIGNAL, QObject, Qt, QString
|
||||||
from PyQt4.QtNetwork import QLocalSocket, QLocalServer
|
from PyQt4.QtNetwork import QLocalSocket, QLocalServer
|
||||||
@ -94,8 +94,23 @@ class LocalServer(QLocalServer):
|
|||||||
for conn in pop:
|
for conn in pop:
|
||||||
self.connections.remove(conn)
|
self.connections.remove(conn)
|
||||||
|
|
||||||
|
def listen(self, name):
|
||||||
|
if not QLocalServer.listen(self, name):
|
||||||
|
try:
|
||||||
|
os.unlink(self.fullServerName())
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
return QLocalServer.listen(self, name)
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def send_message(msg, name, server_name='calibre_server', timeout=5000):
|
||||||
|
socket = QLocalSocket()
|
||||||
|
socket.connectToServer(server_name)
|
||||||
|
if socket.waitForConnected(timeout_connect):
|
||||||
|
if read_message(socket) == name:
|
||||||
|
write_message(socket, name+':'+msg, timeout)
|
||||||
|
|
||||||
class SingleApplication(QObject):
|
class SingleApplication(QObject):
|
||||||
|
|
||||||
def __init__(self, name, parent=None, server_name='calibre_server'):
|
def __init__(self, name, parent=None, server_name='calibre_server'):
|
||||||
@ -124,8 +139,7 @@ class SingleApplication(QObject):
|
|||||||
self.mr, Qt.QueuedConnection)
|
self.mr, Qt.QueuedConnection)
|
||||||
|
|
||||||
if not self.server.listen(self.server_name):
|
if not self.server.listen(self.server_name):
|
||||||
if not self.server.listen(self.server_name):
|
self.server = None
|
||||||
self.server = None
|
|
||||||
if self.server is not None:
|
if self.server is not None:
|
||||||
atexit.register(self.server.close)
|
atexit.register(self.server.close)
|
||||||
|
|
||||||
|
80
upload.py
80
upload.py
@ -1,5 +1,5 @@
|
|||||||
#!/usr/bin/python
|
#!/usr/bin/python
|
||||||
import sys, os, shutil, time, tempfile, socket, fcntl, struct
|
import sys, os, shutil, time, tempfile, socket, fcntl, struct, cStringIO, pycurl, re
|
||||||
sys.path.append('src')
|
sys.path.append('src')
|
||||||
import subprocess
|
import subprocess
|
||||||
from subprocess import check_call as _check_call
|
from subprocess import check_call as _check_call
|
||||||
@ -24,6 +24,7 @@ DOCS = PREFIX+"/htdocs/apidocs"
|
|||||||
USER_MANUAL = PREFIX+'/htdocs/user_manual'
|
USER_MANUAL = PREFIX+'/htdocs/user_manual'
|
||||||
HTML2LRF = "src/calibre/ebooks/lrf/html/demo"
|
HTML2LRF = "src/calibre/ebooks/lrf/html/demo"
|
||||||
TXT2LRF = "src/calibre/ebooks/lrf/txt/demo"
|
TXT2LRF = "src/calibre/ebooks/lrf/txt/demo"
|
||||||
|
MOBILEREAD = 'ftp://dev.mobileread.com/calibre/'
|
||||||
BUILD_SCRIPT ='''\
|
BUILD_SCRIPT ='''\
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
cd ~/build && \
|
cd ~/build && \
|
||||||
@ -110,19 +111,72 @@ def upload_demo():
|
|||||||
check_call('cd src/calibre/ebooks/lrf/txt/demo/ && zip -j /tmp/txt-demo.zip * /tmp/txt2lrf.lrf')
|
check_call('cd src/calibre/ebooks/lrf/txt/demo/ && zip -j /tmp/txt-demo.zip * /tmp/txt2lrf.lrf')
|
||||||
check_call('''scp /tmp/txt-demo.zip divok:%s/'''%(DOWNLOADS,))
|
check_call('''scp /tmp/txt-demo.zip divok:%s/'''%(DOWNLOADS,))
|
||||||
|
|
||||||
|
def curl_list_dir(url=MOBILEREAD, listonly=1):
|
||||||
|
c = pycurl.Curl()
|
||||||
|
c.setopt(pycurl.URL, url)
|
||||||
|
c.setopt(c.FTP_USE_EPSV, 1)
|
||||||
|
c.setopt(c.NETRC, c.NETRC_REQUIRED)
|
||||||
|
c.setopt(c.FTPLISTONLY, listonly)
|
||||||
|
c.setopt(c.FTP_CREATE_MISSING_DIRS, 1)
|
||||||
|
b = cStringIO.StringIO()
|
||||||
|
c.setopt(c.WRITEFUNCTION, b.write)
|
||||||
|
c.perform()
|
||||||
|
c.close()
|
||||||
|
return b.getvalue().split() if listonly else b.getvalue().splitlines()
|
||||||
|
|
||||||
|
def curl_delete_file(path, url=MOBILEREAD):
|
||||||
|
c = pycurl.Curl()
|
||||||
|
c.setopt(pycurl.URL, url)
|
||||||
|
c.setopt(c.FTP_USE_EPSV, 1)
|
||||||
|
c.setopt(c.NETRC, c.NETRC_REQUIRED)
|
||||||
|
print 'Deleting file %s on %s'%(path, url)
|
||||||
|
c.setopt(c.QUOTE, ['dele '+ path])
|
||||||
|
c.perform()
|
||||||
|
c.close()
|
||||||
|
|
||||||
|
|
||||||
|
def curl_upload_file(stream, url):
|
||||||
|
c = pycurl.Curl()
|
||||||
|
c.setopt(pycurl.URL, url)
|
||||||
|
c.setopt(pycurl.UPLOAD, 1)
|
||||||
|
c.setopt(c.NETRC, c.NETRC_REQUIRED)
|
||||||
|
c.setopt(pycurl.READFUNCTION, stream.read)
|
||||||
|
stream.seek(0, 2)
|
||||||
|
c.setopt(pycurl.INFILESIZE_LARGE, stream.tell())
|
||||||
|
stream.seek(0)
|
||||||
|
c.setopt(c.NOPROGRESS, 0)
|
||||||
|
c.setopt(c.FTP_CREATE_MISSING_DIRS, 1)
|
||||||
|
print 'Uploading file %s to url %s' % (getattr(stream, 'name', ''), url)
|
||||||
|
try:
|
||||||
|
c.perform()
|
||||||
|
c.close()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
files = curl_list_dir(listonly=0)
|
||||||
|
for line in files:
|
||||||
|
line = line.split()
|
||||||
|
if url.endswith(line[-1]):
|
||||||
|
size = long(line[4])
|
||||||
|
stream.seek(0,2)
|
||||||
|
if size != stream.tell():
|
||||||
|
raise RuntimeError('curl failed to upload %s correctly'%getattr(stream, 'name', ''))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def upload_installer(name):
|
||||||
|
bname = os.path.basename(name)
|
||||||
|
pat = re.compile(bname.replace(__version__, r'\d+\.\d+\.\d+'))
|
||||||
|
for f in curl_list_dir():
|
||||||
|
if pat.search(f):
|
||||||
|
curl_delete_file('/calibre/'+f)
|
||||||
|
curl_upload_file(open(name, 'rb'), MOBILEREAD+os.path.basename(name))
|
||||||
|
|
||||||
def upload_installers():
|
def upload_installers():
|
||||||
exe, dmg, tbz2 = installer_name('exe'), installer_name('dmg'), installer_name('tar.bz2')
|
for i in ('dmg', 'exe', 'tar.bz2'):
|
||||||
if exe and os.path.exists(exe):
|
upload_installer(installer_name(i))
|
||||||
check_call('''ssh divok rm -f %s/calibre\*.exe'''%(DOWNLOADS,))
|
|
||||||
check_call('''scp %s divok:%s/'''%(exe, DOWNLOADS))
|
check_call('''ssh divok echo %s \\> %s/latest_version'''%(__version__, DOWNLOADS))
|
||||||
if dmg and os.path.exists(dmg):
|
|
||||||
check_call('''ssh divok rm -f %s/calibre\*.dmg'''%(DOWNLOADS,))
|
|
||||||
check_call('''scp %s divok:%s/'''%(dmg, DOWNLOADS))
|
|
||||||
if tbz2 and os.path.exists(tbz2):
|
|
||||||
check_call('''ssh divok rm -f %s/calibre-\*-i686.tar.bz2 %s/latest-linux-binary.tar.bz2'''%(DOWNLOADS,DOWNLOADS))
|
|
||||||
check_call('''scp %s divok:%s/'''%(tbz2, DOWNLOADS))
|
|
||||||
check_call('''ssh divok ln -s %s/calibre-\*-i686.tar.bz2 %s/latest-linux-binary.tar.bz2'''%(DOWNLOADS,DOWNLOADS))
|
|
||||||
check_call('''ssh divok chmod a+r %s/\*'''%(DOWNLOADS,))
|
|
||||||
|
|
||||||
def upload_docs():
|
def upload_docs():
|
||||||
check_call('''epydoc --config epydoc.conf''')
|
check_call('''epydoc --config epydoc.conf''')
|
||||||
|
@ -514,6 +514,12 @@ class BuildEXE(build_exe):
|
|||||||
f.write('src\\calibre\\gui2\\main.py', 'calibre\\gui2\\main.py')
|
f.write('src\\calibre\\gui2\\main.py', 'calibre\\gui2\\main.py')
|
||||||
f.close()
|
f.close()
|
||||||
|
|
||||||
|
print
|
||||||
|
print 'Doing DLL redirection' # See http://msdn.microsoft.com/en-us/library/ms682600(VS.85).aspx
|
||||||
|
for f in glob.glob(os.path.join('build', 'py2exe', '*.exe')):
|
||||||
|
open(f + '.local', 'wb').write('\n')
|
||||||
|
|
||||||
|
|
||||||
print
|
print
|
||||||
print
|
print
|
||||||
print 'Building Installer'
|
print 'Building Installer'
|
||||||
@ -557,12 +563,12 @@ def main():
|
|||||||
'win32file', 'pythoncom', 'rtf2xml',
|
'win32file', 'pythoncom', 'rtf2xml',
|
||||||
'lxml', 'lxml._elementpath', 'genshi',
|
'lxml', 'lxml._elementpath', 'genshi',
|
||||||
'path', 'pydoc', 'IPython.Extensions.*',
|
'path', 'pydoc', 'IPython.Extensions.*',
|
||||||
'calibre.web.feeds.recipes.*', 'pydoc',
|
'calibre.web.feeds.recipes.*', 'PyQt4.QtWebKit',
|
||||||
],
|
],
|
||||||
'packages' : ['PIL'],
|
'packages' : ['PIL'],
|
||||||
'excludes' : ["Tkconstants", "Tkinter", "tcl",
|
'excludes' : ["Tkconstants", "Tkinter", "tcl",
|
||||||
"_imagingtk", "ImageTk", "FixTk",
|
"_imagingtk", "ImageTk", "FixTk"
|
||||||
'pydoc'],
|
],
|
||||||
'dll_excludes' : ['mswsock.dll'],
|
'dll_excludes' : ['mswsock.dll'],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
Loading…
x
Reference in New Issue
Block a user