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>'
|
__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():
|
||||||
|
@ -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
|
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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'])
|
||||||
|
@ -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.
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) ->
|
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)
|
||||||
|
@ -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>
|
||||||
|
|
||||||
|
@ -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()
|
||||||
|
@ -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>&Download now</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
</widget>
|
</widget>
|
||||||
|
@ -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): # {{{
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
@ -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)
|
||||||
|
@ -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'],
|
||||||
|
@ -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')
|
||||||
|
@ -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
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
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
|
# 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'])
|
||||||
|
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)
|
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
@ -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()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user