Merge from trunk

This commit is contained in:
Charles Haley 2012-01-12 13:02:30 +01:00
commit dde61529c8
25 changed files with 630 additions and 185 deletions

View File

@ -6,7 +6,7 @@ __license__ = 'GPL v3'
__copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>' __copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en' __docformat__ = 'restructuredtext en'
import os, cPickle, re, shutil, marshal, zipfile, glob, time import os, cPickle, re, shutil, marshal, zipfile, glob, time, subprocess, sys
from zlib import compress from zlib import compress
from setup import Command, basenames, __appname__ from setup import Command, basenames, __appname__
@ -35,8 +35,8 @@ class Coffee(Command): # {{{
help='Display the generated javascript') help='Display the generated javascript')
def run(self, opts): def run(self, opts):
from calibre.utils.coffeescript import compile_coffeescript cc = self.j(self.SRC, 'calibre', 'utils', 'serve_coffee.py')
self.compiler = compile_coffeescript self.compiler = [sys.executable, cc, 'compile']
self.do_coffee_compile(opts) self.do_coffee_compile(opts)
if opts.watch: if opts.watch:
try: try:
@ -63,24 +63,24 @@ class Coffee(Command): # {{{
if self.newer(js, x): if self.newer(js, x):
print ('\t%sCompiling %s'%(time.strftime('[%H:%M:%S] ') if print ('\t%sCompiling %s'%(time.strftime('[%H:%M:%S] ') if
timestamp else '', os.path.basename(x))) timestamp else '', os.path.basename(x)))
with open(x, 'rb') as f: try:
cs, errs = self.compiler(f.read()) cs = subprocess.check_output(self.compiler +
for line in errs: [x]).decode('utf-8')
print (line) except Exception as e:
if cs and not errs: print ('\n\tCompilation of %s failed'%os.path.basename(x))
print (e)
if ignore_errors:
with open(js, 'wb') as f:
f.write('# Compilation from coffeescript failed')
else:
raise SystemExit(1)
else:
with open(js, 'wb') as f: with open(js, 'wb') as f:
f.write(cs.encode('utf-8')) f.write(cs.encode('utf-8'))
if opts.show_js: if opts.show_js:
self.show_js(js) self.show_js(js)
print ('#'*80) print ('#'*80)
print ('#'*80) print ('#'*80)
else:
print ('\n\tCompilation of %s failed'%os.path.basename(x))
if ignore_errors:
with open(js, 'wb') as f:
f.write('# Compilation from coffeescript failed')
else:
raise SystemExit(1)
def clean(self): def clean(self):
for toplevel, dest in self.COFFEE_DIRS.iteritems(): for toplevel, dest in self.COFFEE_DIRS.iteritems():

View File

@ -31,7 +31,7 @@ if False:
# Prevent pyflakes from complaining # Prevent pyflakes from complaining
winutil, winutilerror, __appname__, islinux, __version__ winutil, winutilerror, __appname__, islinux, __version__
fcntl, win32event, isfrozen, __author__ fcntl, win32event, isfrozen, __author__
winerror, win32api, isbsd winerror, win32api, isbsd, config_dir
_mt_inited = False _mt_inited = False
def _init_mimetypes(): def _init_mimetypes():
@ -699,69 +699,6 @@ if isosx:
traceback.print_exc() traceback.print_exc()
def ipython(user_ns=None): def ipython(user_ns=None):
old_argv = sys.argv from calibre.utils.ipython import ipython
sys.argv = ['ipython'] ipython(user_ns=user_ns)
if user_ns is None:
user_ns = locals()
ipydir = os.path.join(config_dir, ('_' if iswindows else '.')+'ipython')
os.environ['IPYTHONDIR'] = ipydir
if not os.path.exists(ipydir):
os.makedirs(ipydir)
for x in ('', '.ini'):
rc = os.path.join(ipydir, 'ipythonrc'+x)
if not os.path.exists(rc):
open(rc, 'wb').write(' ')
UC = '''
import IPython.ipapi
ip = IPython.ipapi.get()
# You probably want to uncomment this if you did %upgrade -nolegacy
import ipy_defaults
import os, re, sys
def main():
# Handy tab-completers for %cd, %run, import etc.
# Try commenting this out if you have completion problems/slowness
import ipy_stock_completers
# uncomment if you want to get ipython -p sh behaviour
# without having to use command line switches
import ipy_profile_sh
# Configure your favourite editor?
# Good idea e.g. for %edit os.path.isfile
import ipy_editors
# Choose one of these:
#ipy_editors.scite()
#ipy_editors.scite('c:/opt/scite/scite.exe')
#ipy_editors.komodo()
#ipy_editors.idle()
# ... or many others, try 'ipy_editors??' after import to see them
# Or roll your own:
#ipy_editors.install_editor("c:/opt/jed +$line $file")
ipy_editors.kate()
o = ip.options
# An example on how to set options
#o.autocall = 1
o.system_verbose = 0
o.confirm_exit = 0
main()
'''
uc = os.path.join(ipydir, 'ipy_user_conf.py')
if not os.path.exists(uc):
open(uc, 'wb').write(UC)
from IPython.Shell import IPShellEmbed
ipshell = IPShellEmbed(user_ns=user_ns)
ipshell()
sys.argv = old_argv

View File

@ -153,3 +153,12 @@ else:
atexit.register(cleanup_cdir) atexit.register(cleanup_cdir)
# }}} # }}}
def get_version():
'''Return version string that indicates if we are running in a dev env'''
dv = os.environ.get('CALIBRE_DEVELOP_FROM', None)
v = __version__
if getattr(sys, 'frozen', False) and dv and os.path.abspath(dv) in sys.path:
v += '*'
return v

View File

@ -95,11 +95,11 @@ class POCKETBOOK360(EB600):
FORMATS = ['epub', 'fb2', 'prc', 'mobi', 'pdf', 'djvu', 'rtf', 'chm', 'txt'] FORMATS = ['epub', 'fb2', 'prc', 'mobi', 'pdf', 'djvu', 'rtf', 'chm', 'txt']
VENDOR_NAME = ['PHILIPS', '__POCKET'] VENDOR_NAME = ['PHILIPS', '__POCKET', 'POCKETBO']
WINDOWS_MAIN_MEM = WINDOWS_CARD_A_MEM = ['MASS_STORGE', 'BOOK_USB_STORAGE'] WINDOWS_MAIN_MEM = WINDOWS_CARD_A_MEM = ['MASS_STORGE', 'BOOK_USB_STORAGE',
'POCKET_611_61']
OSX_MAIN_MEM = 'Philips Mass Storge Media' OSX_MAIN_MEM = OSX_CARD_A_MEM = 'Philips Mass Storge Media'
OSX_CARD_A_MEM = 'Philips Mass Storge Media'
OSX_MAIN_MEM_VOL_PAT = re.compile(r'/Pocket') OSX_MAIN_MEM_VOL_PAT = re.compile(r'/Pocket')
@classmethod @classmethod

View File

@ -568,7 +568,11 @@ class MobiMLizer(object):
if isblock: if isblock:
para = bstate.para para = bstate.para
if para is not None and para.text == u'\xa0' and len(para) < 1: if para is not None and para.text == u'\xa0' and len(para) < 1:
para.getparent().replace(para, etree.Element(XHTML('br'))) if style.height > 2:
para.getparent().replace(para, etree.Element(XHTML('br')))
else:
# This is too small to be rendered effectively, drop it
para.getparent().remove(para)
bstate.para = None bstate.para = None
bstate.istate = None bstate.istate = None
vmargin = asfloat(style['margin-bottom']) vmargin = asfloat(style['margin-bottom'])

View File

@ -244,7 +244,9 @@ class MetadataHeader(BookHeader):
class MobiReader(object): class MobiReader(object):
PAGE_BREAK_PAT = re.compile(r'(<[/]{0,1}mbp:pagebreak\s*[/]{0,1}>)+', re.IGNORECASE) PAGE_BREAK_PAT = re.compile(
r'<\s*/{0,1}\s*mbp:pagebreak((?:\s+[^/>]*){0,1})/{0,1}\s*>\s*(?:<\s*/{0,1}\s*mbp:pagebreak\s*/{0,1}\s*>)*',
re.IGNORECASE)
IMAGE_ATTRS = ('lowrecindex', 'recindex', 'hirecindex') IMAGE_ATTRS = ('lowrecindex', 'recindex', 'hirecindex')
def __init__(self, filename_or_stream, log, user_encoding=None, debug=None, def __init__(self, filename_or_stream, log, user_encoding=None, debug=None,
@ -539,6 +541,9 @@ class MobiReader(object):
x.getparent().remove(x) x.getparent().remove(x)
svg_tags = [] svg_tags = []
forwardable_anchors = [] forwardable_anchors = []
pagebreak_anchors = []
BLOCK_TAGS = {'h1', 'h2', 'h3', 'h4', 'h5', 'h6',
'div', 'p'}
for i, tag in enumerate(root.iter(etree.Element)): for i, tag in enumerate(root.iter(etree.Element)):
tag.attrib.pop('xmlns', '') tag.attrib.pop('xmlns', '')
for x in tag.attrib: for x in tag.attrib:
@ -657,6 +662,10 @@ class MobiReader(object):
if not tag.text: if not tag.text:
tag.tag = 'div' tag.tag = 'div'
if (attrib.get('class', None) == 'mbp_pagebreak' and tag.tag ==
'div' and 'filepos-id' in attrib):
pagebreak_anchors.append(tag)
if 'filepos-id' in attrib: if 'filepos-id' in attrib:
attrib['id'] = attrib.pop('filepos-id') attrib['id'] = attrib.pop('filepos-id')
if 'name' in attrib and attrib['name'] != attrib['id']: if 'name' in attrib and attrib['name'] != attrib['id']:
@ -670,8 +679,7 @@ class MobiReader(object):
if (tag.tag == 'a' and attrib.get('id', '').startswith('filepos') if (tag.tag == 'a' and attrib.get('id', '').startswith('filepos')
and not tag.text and (tag.tail is None or not and not tag.text and (tag.tail is None or not
tag.tail.strip()) and getattr(tag.getnext(), 'tag', tag.tail.strip()) and getattr(tag.getnext(), 'tag',
None) in ('h1', 'h2', 'h3', 'h4', 'h5', 'h6', None) in BLOCK_TAGS):
'div', 'p')):
# This is an empty anchor immediately before a block tag, move # This is an empty anchor immediately before a block tag, move
# the id onto the block tag instead # the id onto the block tag instead
forwardable_anchors.append(tag) forwardable_anchors.append(tag)
@ -704,6 +712,18 @@ class MobiReader(object):
if hasattr(parent, 'remove'): if hasattr(parent, 'remove'):
parent.remove(tag) parent.remove(tag)
for tag in pagebreak_anchors:
anchor = tag.attrib['id']
del tag.attrib['id']
if 'name' in tag.attrib:
del tag.attrib['name']
p = tag.getparent()
a = p.makeelement('a')
a.attrib['id'] = anchor
p.insert(p.index(tag)+1, a)
if getattr(a.getnext(), 'tag', None) in BLOCK_TAGS:
forwardable_anchors.append(a)
for tag in forwardable_anchors: for tag in forwardable_anchors:
block = tag.getnext() block = tag.getnext()
tag.getparent().remove(tag) tag.getparent().remove(tag)
@ -919,7 +939,7 @@ class MobiReader(object):
def replace_page_breaks(self): def replace_page_breaks(self):
self.processed_html = self.PAGE_BREAK_PAT.sub( self.processed_html = self.PAGE_BREAK_PAT.sub(
'<div class="mbp_pagebreak" />', r'<div \1 class="mbp_pagebreak" />',
self.processed_html) self.processed_html)
def add_anchors(self): def add_anchors(self):
@ -1047,3 +1067,22 @@ def get_metadata(stream):
im.convert('RGB').save(obuf, format='JPEG') im.convert('RGB').save(obuf, format='JPEG')
mi.cover_data = ('jpg', obuf.getvalue()) mi.cover_data = ('jpg', obuf.getvalue())
return mi return mi
def test_mbp_regex():
for raw, m in {
'<mbp:pagebreak></mbp:pagebreak>':'',
'<mbp:pagebreak xxx></mbp:pagebreak>yyy':' xxxyyy',
'<mbp:pagebreak> </mbp:pagebreak>':'',
'<mbp:pagebreak>xxx':'xxx',
'<mbp:pagebreak/>xxx':'xxx',
'<mbp:pagebreak sdf/ >xxx':' sdfxxx',
'<mbp:pagebreak / >':' ',
'</mbp:pagebreak>':'',
'</mbp:pagebreak sdf>':' sdf',
'</mbp:pagebreak><mbp:pagebreak></mbp:pagebreak>xxx':'xxx',
}.iteritems():
ans = MobiReader.PAGE_BREAK_PAT.sub(r'\1', raw)
if ans != m:
raise Exception('%r != %r for %r'%(ans, m, raw))

Binary file not shown.

View File

@ -32,6 +32,11 @@ window_ypos = (pos=null) ->
mark_and_reload = (evt) -> mark_and_reload = (evt) ->
# Remove image in case the click was on the image itself, we want the cfi to # Remove image in case the click was on the image itself, we want the cfi to
# be on the underlying element # be on the underlying element
if evt.button == 2
return # Right mouse click, generated only in firefox
reset = document.getElementById('reset')
if document.elementFromPoint(evt.clientX, evt.clientY) == reset
return
ms = document.getElementById("marker") ms = document.getElementById("marker")
if ms if ms
ms.parentNode?.removeChild(ms) ms.parentNode?.removeChild(ms)

View File

@ -71,7 +71,8 @@
righteous indignation and dislike men who are so beguiled and righteous indignation and dislike men who are so beguiled and
demoralized by the charms of pleasure of the moment, so blinded demoralized by the charms of pleasure of the moment, so blinded
by desire, that they cannot foresee by desire, that they cannot foresee
<img src="marker.png" width="150" height="200" alt="Test Image" style="border: solid 1px black; display:block"/> <img src="marker.png" width="150" height="200" alt="Test Image"
style="border: solid 1px black; display:block"/>
</div> </div>
<h2>Some entities and comments</h2> <h2>Some entities and comments</h2>
@ -103,7 +104,8 @@
they cannot foresee</p> they cannot foresee</p>
<h2>Lots of collapsed whitespace</h2> <h2>Lots of collapsed whitespace</h2>
<p>Try clicking the A character after the colon: A suffix</p> <p>Try clicking the A character after the colon:
A suffix</p>
<h2>Lots of nested/sibling tags</h2> <h2>Lots of nested/sibling tags</h2>
<p>A <span>bunch of <span>nested<span> and</span> <span>sibling</span> <p>A <span>bunch of <span>nested<span> and</span> <span>sibling</span>
@ -111,20 +113,23 @@
over this paragraph to test<span> things.</span></p> over this paragraph to test<span> things.</span></p>
<h2>Images</h2> <h2>Images</h2>
<p>Try clicking at different points along the image. Also try changing the magnification and then hitting reload.</p> <p>Try clicking at different points along the image. Also try
<img src="marker.png" width="150" height="200" alt="Test Image" style="border: solid 1px black"/> changing the magnification and then hitting reload.</p>
<img src="marker.png" width="150" height="200" alt="Test Image"
style="border: solid 1px black"/>
<h2>Video</h2> <h2>Video</h2>
<p>Try clicking on this video while it is playing. The page should <p>Try clicking on this video while it is playing. The page should
reload with the video paused at the point it was at when you reload with the video paused at the point it was at when you
clicked. To play the video you should right click on it and select clicked. To play the video you should right click on it and select
play (otherwise the click will cause a reload). This is currently play (otherwise the click will cause a reload).
broken because of issues in the python server use to serve test </p>
content. I lack the patience to track down the bug. </p> <video width="320" height="240" controls="controls" preload="auto"
<video width="320" height="240" controls="controls" preload="auto" src="birds.mp4" type="video/mp4" /> src="birds.webm" type="video/webm" />
</div> </div>
<img id="marker" style="position: absolute; display:none; z-index:10" src="marker.png" alt="Marker" /> <img id="marker" style="position: absolute; display:none; z-index:10"
src="marker.png" alt="Marker" />
</body> </body>
</html> </html>

View File

@ -214,6 +214,9 @@ class SchedulerDialog(QDialog, Ui_Dialog):
self.recipes.setModel(self.recipe_model) self.recipes.setModel(self.recipe_model)
self.detail_box.setVisible(False) self.detail_box.setVisible(False)
self.download_button = self.buttonBox.addButton(_('&Download now'),
self.buttonBox.ActionRole)
self.download_button.setIcon(QIcon(I('arrow-down.png')))
self.download_button.setVisible(False) self.download_button.setVisible(False)
self.recipes.currentChanged = self.current_changed self.recipes.currentChanged = self.current_changed
for b, c in self.SCHEDULE_TYPES.iteritems(): for b, c in self.SCHEDULE_TYPES.iteritems():
@ -371,7 +374,8 @@ class SchedulerDialog(QDialog, Ui_Dialog):
'''%dict(title=recipe.get('title'), cb=_('Created by: '), '''%dict(title=recipe.get('title'), cb=_('Created by: '),
author=recipe.get('author', _('Unknown')), author=recipe.get('author', _('Unknown')),
description=recipe.get('description', ''))) description=recipe.get('description', '')))
self.download_button.setToolTip(
_('Downlod %s now')%recipe.get('title'))
scheduled = schedule_info is not None scheduled = schedule_info is not None
self.schedule.setChecked(scheduled) self.schedule.setChecked(scheduled)
self.toggle_schedule_info() self.toggle_schedule_info()

View File

@ -53,8 +53,8 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>524</width> <width>518</width>
<height>504</height> <height>498</height>
</rect> </rect>
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout_5"> <layout class="QVBoxLayout" name="verticalLayout_5">
@ -318,13 +318,6 @@
</property> </property>
</spacer> </spacer>
</item> </item>
<item>
<widget class="QPushButton" name="download_button">
<property name="text">
<string>&amp;Download now</string>
</property>
</widget>
</item>
</layout> </layout>
</widget> </widget>
</widget> </widget>

View File

@ -158,7 +158,7 @@ def email_news(mi, remove, get_fmts, done, job_manager):
return sent_mails return sent_mails
plugboard_email_value = 'email' plugboard_email_value = 'email'
plugboard_email_formats = ['epub'] plugboard_email_formats = ['epub', 'mobi']
class EmailMixin(object): # {{{ class EmailMixin(object): # {{{

View File

@ -5,14 +5,14 @@ __license__ = 'GPL v3'
__copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>' __copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en' __docformat__ = 'restructuredtext en'
import functools, sys, os import functools
from PyQt4.Qt import Qt, QStackedWidget, QMenu, \ from PyQt4.Qt import Qt, QStackedWidget, QMenu, \
QSize, QSizePolicy, QStatusBar, QLabel, QFont QSize, QSizePolicy, QStatusBar, QLabel, QFont
from calibre.utils.config import prefs from calibre.utils.config import prefs
from calibre.constants import isosx, __appname__, preferred_encoding, \ from calibre.constants import (isosx, __appname__, preferred_encoding,
__version__ get_version)
from calibre.gui2 import config, is_widescreen, gprefs from calibre.gui2 import config, is_widescreen, gprefs
from calibre.gui2.library.views import BooksView, DeviceBooksView from calibre.gui2.library.views import BooksView, DeviceBooksView
from calibre.gui2.widgets import Splitter from calibre.gui2.widgets import Splitter
@ -187,11 +187,7 @@ class StatusBar(QStatusBar): # {{{
self.clearMessage() self.clearMessage()
def get_version(self): def get_version(self):
dv = os.environ.get('CALIBRE_DEVELOP_FROM', None) return get_version()
v = __version__
if getattr(sys, 'frozen', False) and dv and os.path.abspath(dv) in sys.path:
v += '*'
return v
def show_message(self, msg, timeout=0): def show_message(self, msg, timeout=0):
self.showMessage(msg, timeout) self.showMessage(msg, timeout)

View File

@ -466,7 +466,7 @@ class BooksModel(QAbstractTableModel): # {{{
if cpb: if cpb:
newmi = mi.deepcopy_metadata() newmi = mi.deepcopy_metadata()
newmi.template_to_attribute(mi, cpb) newmi.template_to_attribute(mi, cpb)
if newmi: if newmi is not None:
_set_metadata(pt, newmi, format) _set_metadata(pt, newmi, format)
else: else:
_set_metadata(pt, mi, format) _set_metadata(pt, mi, format)

View File

@ -11,7 +11,7 @@ SHORTCUTS = {
'Next Page' : (['PgDown', 'Space'], 'Next Page' : (['PgDown', 'Space'],
_('Scroll to the next page')), _('Scroll to the next page')),
'Previous Page' : (['PgUp', 'Backspace'], 'Previous Page' : (['PgUp', 'Backspace', 'Shift+Space'],
_('Scroll to the previous page')), _('Scroll to the previous page')),
'Next Section' : (['Ctrl+PgDown', 'Ctrl+Down'], 'Next Section' : (['Ctrl+PgDown', 'Ctrl+Down'],

View File

@ -546,7 +546,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
authors = self.authors(id, index_is_id=True) authors = self.authors(id, index_is_id=True)
if not authors: if not authors:
authors = _('Unknown') authors = _('Unknown')
author = ascii_filename(authors.split(',')[0] author = ascii_filename(authors.split(',')[0].replace('|', ',')
)[:self.PATH_LIMIT].decode('ascii', 'replace') )[:self.PATH_LIMIT].decode('ascii', 'replace')
title = ascii_filename(self.title(id, index_is_id=True) title = ascii_filename(self.title(id, index_is_id=True)
)[:self.PATH_LIMIT].decode('ascii', 'replace') )[:self.PATH_LIMIT].decode('ascii', 'replace')
@ -565,7 +565,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
authors = self.authors(id, index_is_id=True) authors = self.authors(id, index_is_id=True)
if not authors: if not authors:
authors = _('Unknown') authors = _('Unknown')
author = ascii_filename(authors.split(',')[0] author = ascii_filename(authors.split(',')[0].replace('|', ',')
)[:self.PATH_LIMIT].decode('ascii', 'replace') )[:self.PATH_LIMIT].decode('ascii', 'replace')
title = ascii_filename(self.title(id, index_is_id=True) title = ascii_filename(self.title(id, index_is_id=True)
)[:self.PATH_LIMIT].decode('ascii', 'replace') )[:self.PATH_LIMIT].decode('ascii', 'replace')

View File

@ -65,7 +65,8 @@ to the latest code, use the command::
bzr merge bzr merge
You can also download the source code as a tarball (archive) from `here <http://status.calibre-ebook.com/dist/src>`_. The calibre repository is huge so the branch operation above takes along time (about an hour). If you want to get the code faster, the sourcecode for the latest release is always available as an
`archive <http://status.calibre-ebook.com/dist/src>`_.
Submitting your changes to be included Submitting your changes to be included
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

View File

@ -66,9 +66,10 @@ class Worker(object):
if isfrozen: if isfrozen:
return os.path.join(sys.executables_location, e) return os.path.join(sys.executables_location, e)
c = os.path.join(sys.executables_location, e) if hasattr(sys, 'executables_location'):
if os.access(c, os.X_OK): c = os.path.join(sys.executables_location, e)
return c if os.access(c, os.X_OK):
return c
return e return e

View File

@ -0,0 +1,150 @@
#!/usr/bin/env python
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
from __future__ import (unicode_literals, division, absolute_import,
print_function)
__license__ = 'GPL v3'
__copyright__ = '2012, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en'
import os, cPickle, traceback, time, importlib
from binascii import hexlify, unhexlify
from multiprocessing.connection import Listener, arbitrary_address, Client
from threading import Thread
from contextlib import closing
from calibre.constants import iswindows
from calibre.utils.ipc.launch import Worker
class WorkerError(Exception):
def __init__(self, msg, orig_tb=''):
Exception.__init__(self, msg)
self.org_tb = orig_tb
class ConnectedWorker(Thread):
def __init__(self, listener, args):
Thread.__init__(self)
self.daemon = True
self.listener = listener
self.args = args
self.accepted = False
self.tb = None
self.res = None
def run(self):
conn = tb = None
for i in range(2):
# On OS X an EINTR can interrupt the accept() call
try:
conn = self.listener.accept()
break
except:
tb = traceback.format_exc()
pass
if conn is None:
self.tb = tb
return
self.accepted = True
with closing(conn):
try:
try:
conn.send(self.args)
except:
# Maybe an EINTR
conn.send(self.args)
try:
self.res = conn.recv()
except:
# Maybe an EINTR
self.res = conn.recv()
except:
self.tb = traceback.format_exc()
def communicate(ans, worker, listener, args, timeout=300):
cw = ConnectedWorker(listener, args)
cw.start()
st = time.time()
while worker.is_alive and cw.is_alive():
cw.join(0.01)
delta = time.time() - st
if not cw.accepted and delta > min(10, timeout):
break
if delta > timeout:
raise WorkerError('Worker appears to have hung')
if not cw.accepted:
if not cw.tb:
raise WorkerError('Failed to connect to worker process')
raise WorkerError('Failed to connect to worker process', cw.tb)
if cw.tb:
raise WorkerError('Failed to communicate with worker process')
if cw.res.get('tb', None):
raise WorkerError('Worker failed', cw.res['tb'])
ans['result'] = cw.res['result']
def fork_job(mod_name, func_name, args=(), kwargs={}, timeout=300, # seconds
cwd=None, priority='normal', env={}, no_output=False):
ans = {'result':None, 'stdout_stderr':None}
address = arbitrary_address('AF_PIPE' if iswindows else 'AF_UNIX')
if iswindows and address[1] == ':':
address = address[2:]
auth_key = os.urandom(32)
listener = Listener(address=address, authkey=auth_key)
env = dict(env)
env.update({
'CALIBRE_WORKER_ADDRESS' :
hexlify(cPickle.dumps(listener.address, -1)),
'CALIBRE_WORKER_KEY' : hexlify(auth_key),
'CALIBRE_SIMPLE_WORKER':
'calibre.utils.ipc.simple_worker:main',
})
w = Worker(env)
w(cwd=cwd, priority=priority)
try:
communicate(ans, w, listener, (mod_name, func_name, args, kwargs),
timeout=timeout)
finally:
t = Thread(target=w.kill)
t.daemon=True
t.start()
if no_output:
try:
os.remove(w.log_path)
except:
pass
if not no_output:
ans['stdout_stderr'] = w.log_path
return ans
def main():
# The entry point for the simple worker process
address = cPickle.loads(unhexlify(os.environ['CALIBRE_WORKER_ADDRESS']))
key = unhexlify(os.environ['CALIBRE_WORKER_KEY'])
with closing(Client(address, authkey=key)) as conn:
try:
args = conn.recv()
except:
# Maybe EINTR
args = conn.recv()
try:
mod, func, args, kwargs = args
mod = importlib.import_module(mod)
func = getattr(mod, func)
res = {'result':func(*args, **kwargs)}
except:
res = {'tb': traceback.format_exc()}
try:
conn.send(res)
except:
# Maybe EINTR
conn.send(res)

View File

@ -167,9 +167,13 @@ def main():
# so launch the gui as usual # so launch the gui as usual
from calibre.gui2.main import main as gui_main from calibre.gui2.main import main as gui_main
return gui_main(['calibre']) return gui_main(['calibre'])
if 'CALIBRE_LAUNCH_INTERPRETER' in os.environ: csw = os.environ.get('CALIBRE_SIMPLE_WORKER', None)
from calibre.utils.pyconsole.interpreter import main if csw:
return main() mod, _, func = csw.partition(':')
mod = importlib.import_module(mod)
func = getattr(mod, func)
func()
return
address = cPickle.loads(unhexlify(os.environ['CALIBRE_WORKER_ADDRESS'])) address = cPickle.loads(unhexlify(os.environ['CALIBRE_WORKER_ADDRESS']))
key = unhexlify(os.environ['CALIBRE_WORKER_KEY']) key = unhexlify(os.environ['CALIBRE_WORKER_KEY'])
resultf = unhexlify(os.environ['CALIBRE_WORKER_RESULT']) resultf = unhexlify(os.environ['CALIBRE_WORKER_RESULT'])

View File

@ -0,0 +1,113 @@
#!/usr/bin/env python
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
from __future__ import (unicode_literals, division, absolute_import,
print_function)
__license__ = 'GPL v3'
__copyright__ = '2012, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en'
import sys, os
from calibre.constants import iswindows, config_dir, get_version
ipydir = os.path.join(config_dir, ('_' if iswindows else '.')+'ipython')
def old_ipython(user_ns=None): # {{{
old_argv = sys.argv
sys.argv = ['ipython']
if user_ns is None:
user_ns = locals()
os.environ['IPYTHONDIR'] = ipydir
if not os.path.exists(ipydir):
os.makedirs(ipydir)
for x in ('', '.ini'):
rc = os.path.join(ipydir, 'ipythonrc'+x)
if not os.path.exists(rc):
open(rc, 'wb').write(' ')
UC = '''
import IPython.ipapi
ip = IPython.ipapi.get()
# You probably want to uncomment this if you did %upgrade -nolegacy
import ipy_defaults
import os, re, sys
def main():
# Handy tab-completers for %cd, %run, import etc.
# Try commenting this out if you have completion problems/slowness
import ipy_stock_completers
# uncomment if you want to get ipython -p sh behaviour
# without having to use command line switches
import ipy_profile_sh
# Configure your favourite editor?
# Good idea e.g. for %edit os.path.isfile
import ipy_editors
# Choose one of these:
#ipy_editors.scite()
#ipy_editors.scite('c:/opt/scite/scite.exe')
#ipy_editors.komodo()
#ipy_editors.idle()
# ... or many others, try 'ipy_editors??' after import to see them
# Or roll your own:
#ipy_editors.install_editor("c:/opt/jed +$line $file")
ipy_editors.kate()
o = ip.options
# An example on how to set options
#o.autocall = 1
o.system_verbose = 0
o.confirm_exit = 0
main()
'''
uc = os.path.join(ipydir, 'ipy_user_conf.py')
if not os.path.exists(uc):
open(uc, 'wb').write(UC)
from IPython.Shell import IPShellEmbed
ipshell = IPShellEmbed(user_ns=user_ns)
ipshell()
sys.argv = old_argv
# }}}
def ipython(user_ns=None):
try:
import IPython
from IPython.config.loader import Config
except ImportError:
return old_ipython(user_ns=user_ns)
if not user_ns:
user_ns = {}
c = Config()
c.InteractiveShellApp.exec_lines = [
'from __future__ import division, absolute_import, unicode_literals, print_function',
]
c.TerminalInteractiveShell.confirm_exit = False
c.PromptManager.in_template = (r'{color.LightGreen}calibre '
'{color.LightBlue}[{color.LightCyan}%s{color.LightBlue}]'
r'{color.Green}|\#> '%get_version())
c.PromptManager.in2_template = r'{color.Green}|{color.LightGreen}\D{color.Green}> '
c.PromptManager.out_template = r'<\#> '
c.TerminalInteractiveShell.banner1 = ('Welcome to the interactive calibre'
' shell!\n\n')
c.PromptManager.justify = True
c.TerminalIPythonApp.ipython_dir = ipydir
os.environ['IPYTHONDIR'] = ipydir
c.InteractiveShell.separate_in = ''
c.InteractiveShell.separate_out = ''
c.InteractiveShell.separate_out2 = ''
c.PrefilterManager.multi_line_specials = True
IPython.embed(config=c, user_ns=user_ns)

View File

@ -39,7 +39,8 @@ class Controller(QThread):
authkey=self.auth_key, backlog=4) authkey=self.auth_key, backlog=4)
self.env = { self.env = {
'CALIBRE_LAUNCH_INTERPRETER': '1', 'CALIBRE_SIMPLE_WORKER':
'calibre.utils.pyconsole.interpreter:main',
'CALIBRE_WORKER_ADDRESS': 'CALIBRE_WORKER_ADDRESS':
hexlify(cPickle.dumps(self.listener.address, -1)), hexlify(cPickle.dumps(self.listener.address, -1)),
'CALIBRE_WORKER_KEY': hexlify(self.auth_key) 'CALIBRE_WORKER_KEY': hexlify(self.auth_key)

File diff suppressed because one or more lines are too long

View File

@ -1158,7 +1158,7 @@ ol, ul { padding-left: 2em; }
""" Anchors start """ """ Anchors start """
self.writedata() self.writedata()
href = attrs[(XLINKNS,"href")].split("|")[0] href = attrs[(XLINKNS,"href")].split("|")[0]
if href[0] == "#": if href[:1] == "#": # Changed by Kovid
href = "#" + self.get_anchor(href[1:]) href = "#" + self.get_anchor(href[1:])
self.opentag('a', {'href':href}) self.opentag('a', {'href':href})
self.purgedata() self.purgedata()