mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Merge from trunk
This commit is contained in:
commit
dde61529c8
@ -6,7 +6,7 @@ __license__ = 'GPL v3'
|
||||
__copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>'
|
||||
__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 setup import Command, basenames, __appname__
|
||||
@ -35,8 +35,8 @@ class Coffee(Command): # {{{
|
||||
help='Display the generated javascript')
|
||||
|
||||
def run(self, opts):
|
||||
from calibre.utils.coffeescript import compile_coffeescript
|
||||
self.compiler = compile_coffeescript
|
||||
cc = self.j(self.SRC, 'calibre', 'utils', 'serve_coffee.py')
|
||||
self.compiler = [sys.executable, cc, 'compile']
|
||||
self.do_coffee_compile(opts)
|
||||
if opts.watch:
|
||||
try:
|
||||
@ -63,24 +63,24 @@ class Coffee(Command): # {{{
|
||||
if self.newer(js, x):
|
||||
print ('\t%sCompiling %s'%(time.strftime('[%H:%M:%S] ') if
|
||||
timestamp else '', os.path.basename(x)))
|
||||
with open(x, 'rb') as f:
|
||||
cs, errs = self.compiler(f.read())
|
||||
for line in errs:
|
||||
print (line)
|
||||
if cs and not errs:
|
||||
try:
|
||||
cs = subprocess.check_output(self.compiler +
|
||||
[x]).decode('utf-8')
|
||||
except Exception as e:
|
||||
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:
|
||||
f.write(cs.encode('utf-8'))
|
||||
if opts.show_js:
|
||||
self.show_js(js)
|
||||
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):
|
||||
for toplevel, dest in self.COFFEE_DIRS.iteritems():
|
||||
|
@ -31,7 +31,7 @@ if False:
|
||||
# Prevent pyflakes from complaining
|
||||
winutil, winutilerror, __appname__, islinux, __version__
|
||||
fcntl, win32event, isfrozen, __author__
|
||||
winerror, win32api, isbsd
|
||||
winerror, win32api, isbsd, config_dir
|
||||
|
||||
_mt_inited = False
|
||||
def _init_mimetypes():
|
||||
@ -699,69 +699,6 @@ if isosx:
|
||||
traceback.print_exc()
|
||||
|
||||
def ipython(user_ns=None):
|
||||
old_argv = sys.argv
|
||||
sys.argv = ['ipython']
|
||||
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
|
||||
from calibre.utils.ipython import ipython
|
||||
ipython(user_ns=user_ns)
|
||||
|
||||
|
@ -153,3 +153,12 @@ else:
|
||||
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
|
||||
|
||||
|
||||
|
@ -95,11 +95,11 @@ class POCKETBOOK360(EB600):
|
||||
|
||||
FORMATS = ['epub', 'fb2', 'prc', 'mobi', 'pdf', 'djvu', 'rtf', 'chm', 'txt']
|
||||
|
||||
VENDOR_NAME = ['PHILIPS', '__POCKET']
|
||||
WINDOWS_MAIN_MEM = WINDOWS_CARD_A_MEM = ['MASS_STORGE', 'BOOK_USB_STORAGE']
|
||||
VENDOR_NAME = ['PHILIPS', '__POCKET', 'POCKETBO']
|
||||
WINDOWS_MAIN_MEM = WINDOWS_CARD_A_MEM = ['MASS_STORGE', 'BOOK_USB_STORAGE',
|
||||
'POCKET_611_61']
|
||||
|
||||
OSX_MAIN_MEM = 'Philips Mass Storge Media'
|
||||
OSX_CARD_A_MEM = 'Philips Mass Storge Media'
|
||||
OSX_MAIN_MEM = OSX_CARD_A_MEM = 'Philips Mass Storge Media'
|
||||
OSX_MAIN_MEM_VOL_PAT = re.compile(r'/Pocket')
|
||||
|
||||
@classmethod
|
||||
|
@ -568,7 +568,11 @@ class MobiMLizer(object):
|
||||
if isblock:
|
||||
para = bstate.para
|
||||
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.istate = None
|
||||
vmargin = asfloat(style['margin-bottom'])
|
||||
|
@ -244,7 +244,9 @@ class MetadataHeader(BookHeader):
|
||||
|
||||
|
||||
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')
|
||||
|
||||
def __init__(self, filename_or_stream, log, user_encoding=None, debug=None,
|
||||
@ -539,6 +541,9 @@ class MobiReader(object):
|
||||
x.getparent().remove(x)
|
||||
svg_tags = []
|
||||
forwardable_anchors = []
|
||||
pagebreak_anchors = []
|
||||
BLOCK_TAGS = {'h1', 'h2', 'h3', 'h4', 'h5', 'h6',
|
||||
'div', 'p'}
|
||||
for i, tag in enumerate(root.iter(etree.Element)):
|
||||
tag.attrib.pop('xmlns', '')
|
||||
for x in tag.attrib:
|
||||
@ -657,6 +662,10 @@ class MobiReader(object):
|
||||
if not tag.text:
|
||||
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:
|
||||
attrib['id'] = attrib.pop('filepos-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')
|
||||
and not tag.text and (tag.tail is None or not
|
||||
tag.tail.strip()) and getattr(tag.getnext(), 'tag',
|
||||
None) in ('h1', 'h2', 'h3', 'h4', 'h5', 'h6',
|
||||
'div', 'p')):
|
||||
None) in BLOCK_TAGS):
|
||||
# This is an empty anchor immediately before a block tag, move
|
||||
# the id onto the block tag instead
|
||||
forwardable_anchors.append(tag)
|
||||
@ -704,6 +712,18 @@ class MobiReader(object):
|
||||
if hasattr(parent, 'remove'):
|
||||
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:
|
||||
block = tag.getnext()
|
||||
tag.getparent().remove(tag)
|
||||
@ -919,7 +939,7 @@ class MobiReader(object):
|
||||
|
||||
def replace_page_breaks(self):
|
||||
self.processed_html = self.PAGE_BREAK_PAT.sub(
|
||||
'<div class="mbp_pagebreak" />',
|
||||
r'<div \1 class="mbp_pagebreak" />',
|
||||
self.processed_html)
|
||||
|
||||
def add_anchors(self):
|
||||
@ -1047,3 +1067,22 @@ def get_metadata(stream):
|
||||
im.convert('RGB').save(obuf, format='JPEG')
|
||||
mi.cover_data = ('jpg', obuf.getvalue())
|
||||
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.
BIN
src/calibre/ebooks/oeb/display/test-cfi/birds.webm
Normal file
BIN
src/calibre/ebooks/oeb/display/test-cfi/birds.webm
Normal file
Binary file not shown.
@ -32,6 +32,11 @@ window_ypos = (pos=null) ->
|
||||
mark_and_reload = (evt) ->
|
||||
# Remove image in case the click was on the image itself, we want the cfi to
|
||||
# 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")
|
||||
if ms
|
||||
ms.parentNode?.removeChild(ms)
|
||||
|
@ -71,7 +71,8 @@
|
||||
righteous indignation and dislike men who are so beguiled and
|
||||
demoralized by the charms of pleasure of the moment, so blinded
|
||||
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>
|
||||
<h2>Some entities and comments</h2>
|
||||
@ -103,7 +104,8 @@
|
||||
they cannot foresee</p>
|
||||
|
||||
<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>
|
||||
<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>
|
||||
|
||||
<h2>Images</h2>
|
||||
<p>Try clicking at different points along the image. Also try changing the magnification and then hitting reload.</p>
|
||||
<img src="marker.png" width="150" height="200" alt="Test Image" style="border: solid 1px black"/>
|
||||
<p>Try clicking at different points along the image. Also try
|
||||
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>
|
||||
<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
|
||||
clicked. To play the video you should right click on it and select
|
||||
play (otherwise the click will cause a reload). This is currently
|
||||
broken because of issues in the python server use to serve test
|
||||
content. I lack the patience to track down the bug. </p>
|
||||
<video width="320" height="240" controls="controls" preload="auto" src="birds.mp4" type="video/mp4" />
|
||||
play (otherwise the click will cause a reload).
|
||||
</p>
|
||||
<video width="320" height="240" controls="controls" preload="auto"
|
||||
src="birds.webm" type="video/webm" />
|
||||
|
||||
</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>
|
||||
</html>
|
||||
|
||||
|
@ -214,6 +214,9 @@ class SchedulerDialog(QDialog, Ui_Dialog):
|
||||
|
||||
self.recipes.setModel(self.recipe_model)
|
||||
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.recipes.currentChanged = self.current_changed
|
||||
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: '),
|
||||
author=recipe.get('author', _('Unknown')),
|
||||
description=recipe.get('description', '')))
|
||||
|
||||
self.download_button.setToolTip(
|
||||
_('Downlod %s now')%recipe.get('title'))
|
||||
scheduled = schedule_info is not None
|
||||
self.schedule.setChecked(scheduled)
|
||||
self.toggle_schedule_info()
|
||||
|
@ -53,8 +53,8 @@
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>524</width>
|
||||
<height>504</height>
|
||||
<width>518</width>
|
||||
<height>498</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_5">
|
||||
@ -318,13 +318,6 @@
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="download_button">
|
||||
<property name="text">
|
||||
<string>&Download now</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
|
@ -158,7 +158,7 @@ def email_news(mi, remove, get_fmts, done, job_manager):
|
||||
return sent_mails
|
||||
|
||||
plugboard_email_value = 'email'
|
||||
plugboard_email_formats = ['epub']
|
||||
plugboard_email_formats = ['epub', 'mobi']
|
||||
|
||||
class EmailMixin(object): # {{{
|
||||
|
||||
|
@ -5,14 +5,14 @@ __license__ = 'GPL v3'
|
||||
__copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
import functools, sys, os
|
||||
import functools
|
||||
|
||||
from PyQt4.Qt import Qt, QStackedWidget, QMenu, \
|
||||
QSize, QSizePolicy, QStatusBar, QLabel, QFont
|
||||
|
||||
from calibre.utils.config import prefs
|
||||
from calibre.constants import isosx, __appname__, preferred_encoding, \
|
||||
__version__
|
||||
from calibre.constants import (isosx, __appname__, preferred_encoding,
|
||||
get_version)
|
||||
from calibre.gui2 import config, is_widescreen, gprefs
|
||||
from calibre.gui2.library.views import BooksView, DeviceBooksView
|
||||
from calibre.gui2.widgets import Splitter
|
||||
@ -187,11 +187,7 @@ class StatusBar(QStatusBar): # {{{
|
||||
self.clearMessage()
|
||||
|
||||
def get_version(self):
|
||||
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
|
||||
return get_version()
|
||||
|
||||
def show_message(self, msg, timeout=0):
|
||||
self.showMessage(msg, timeout)
|
||||
|
@ -466,7 +466,7 @@ class BooksModel(QAbstractTableModel): # {{{
|
||||
if cpb:
|
||||
newmi = mi.deepcopy_metadata()
|
||||
newmi.template_to_attribute(mi, cpb)
|
||||
if newmi:
|
||||
if newmi is not None:
|
||||
_set_metadata(pt, newmi, format)
|
||||
else:
|
||||
_set_metadata(pt, mi, format)
|
||||
|
@ -11,7 +11,7 @@ SHORTCUTS = {
|
||||
'Next Page' : (['PgDown', 'Space'],
|
||||
_('Scroll to the next page')),
|
||||
|
||||
'Previous Page' : (['PgUp', 'Backspace'],
|
||||
'Previous Page' : (['PgUp', 'Backspace', 'Shift+Space'],
|
||||
_('Scroll to the previous page')),
|
||||
|
||||
'Next Section' : (['Ctrl+PgDown', 'Ctrl+Down'],
|
||||
|
@ -546,7 +546,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
||||
authors = self.authors(id, index_is_id=True)
|
||||
if not authors:
|
||||
authors = _('Unknown')
|
||||
author = ascii_filename(authors.split(',')[0]
|
||||
author = ascii_filename(authors.split(',')[0].replace('|', ',')
|
||||
)[:self.PATH_LIMIT].decode('ascii', 'replace')
|
||||
title = ascii_filename(self.title(id, index_is_id=True)
|
||||
)[:self.PATH_LIMIT].decode('ascii', 'replace')
|
||||
@ -565,7 +565,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
||||
authors = self.authors(id, index_is_id=True)
|
||||
if not authors:
|
||||
authors = _('Unknown')
|
||||
author = ascii_filename(authors.split(',')[0]
|
||||
author = ascii_filename(authors.split(',')[0].replace('|', ',')
|
||||
)[:self.PATH_LIMIT].decode('ascii', 'replace')
|
||||
title = ascii_filename(self.title(id, index_is_id=True)
|
||||
)[:self.PATH_LIMIT].decode('ascii', 'replace')
|
||||
|
@ -65,7 +65,8 @@ to the latest code, use the command::
|
||||
|
||||
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
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
@ -66,9 +66,10 @@ class Worker(object):
|
||||
if isfrozen:
|
||||
return os.path.join(sys.executables_location, e)
|
||||
|
||||
c = os.path.join(sys.executables_location, e)
|
||||
if os.access(c, os.X_OK):
|
||||
return c
|
||||
if hasattr(sys, 'executables_location'):
|
||||
c = os.path.join(sys.executables_location, e)
|
||||
if os.access(c, os.X_OK):
|
||||
return c
|
||||
return e
|
||||
|
||||
|
||||
|
150
src/calibre/utils/ipc/simple_worker.py
Normal file
150
src/calibre/utils/ipc/simple_worker.py
Normal file
@ -0,0 +1,150 @@
|
||||
#!/usr/bin/env python
|
||||
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
|
||||
from __future__ import (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)
|
||||
|
||||
|
||||
|
@ -167,9 +167,13 @@ def main():
|
||||
# so launch the gui as usual
|
||||
from calibre.gui2.main import main as gui_main
|
||||
return gui_main(['calibre'])
|
||||
if 'CALIBRE_LAUNCH_INTERPRETER' in os.environ:
|
||||
from calibre.utils.pyconsole.interpreter import main
|
||||
return main()
|
||||
csw = os.environ.get('CALIBRE_SIMPLE_WORKER', None)
|
||||
if csw:
|
||||
mod, _, func = csw.partition(':')
|
||||
mod = importlib.import_module(mod)
|
||||
func = getattr(mod, func)
|
||||
func()
|
||||
return
|
||||
address = cPickle.loads(unhexlify(os.environ['CALIBRE_WORKER_ADDRESS']))
|
||||
key = unhexlify(os.environ['CALIBRE_WORKER_KEY'])
|
||||
resultf = unhexlify(os.environ['CALIBRE_WORKER_RESULT'])
|
||||
|
113
src/calibre/utils/ipython.py
Normal file
113
src/calibre/utils/ipython.py
Normal 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)
|
||||
|
@ -39,7 +39,8 @@ class Controller(QThread):
|
||||
authkey=self.auth_key, backlog=4)
|
||||
|
||||
self.env = {
|
||||
'CALIBRE_LAUNCH_INTERPRETER': '1',
|
||||
'CALIBRE_SIMPLE_WORKER':
|
||||
'calibre.utils.pyconsole.interpreter:main',
|
||||
'CALIBRE_WORKER_ADDRESS':
|
||||
hexlify(cPickle.dumps(self.listener.address, -1)),
|
||||
'CALIBRE_WORKER_KEY': hexlify(self.auth_key)
|
||||
|
File diff suppressed because one or more lines are too long
@ -1158,7 +1158,7 @@ ol, ul { padding-left: 2em; }
|
||||
""" Anchors start """
|
||||
self.writedata()
|
||||
href = attrs[(XLINKNS,"href")].split("|")[0]
|
||||
if href[0] == "#":
|
||||
if href[:1] == "#": # Changed by Kovid
|
||||
href = "#" + self.get_anchor(href[1:])
|
||||
self.opentag('a', {'href':href})
|
||||
self.purgedata()
|
||||
|
Loading…
x
Reference in New Issue
Block a user