Merge upstream changes.

This commit is contained in:
Marshall T. Vandegrift 2008-12-17 13:45:32 -05:00
commit 28064174fc
51 changed files with 5674 additions and 4606 deletions

View File

@ -5,9 +5,5 @@
<pydev_property name="org.python.pydev.PYTHON_PROJECT_VERSION">python 2.5</pydev_property>
<pydev_pathproperty name="org.python.pydev.PROJECT_SOURCE_PATH">
<path>/calibre/src</path>
<path>/calibre/devices</path>
<path>/calibre/libprs500.devices.prs500</path>
<path>/calibre/prs500</path>
<path>/calibre/gui2</path>
</pydev_pathproperty>
</pydev_project>

View File

@ -122,6 +122,8 @@ def freeze():
elif exe not in executables:
print >>sys.stderr, 'Invalid invocation of calibre loader. CALIBRE_CX_EXE=%%s is unknown'%%exe
else:
from PyQt4.QtCore import QCoreApplication
QCoreApplication.setLibraryPaths([sys.frozen_path, os.path.join(sys.frozen_path, "qtplugins")])
sys.argv[0] = exe
module, func = executables[exe]
module = __import__(module, fromlist=[1])
@ -179,7 +181,7 @@ def freeze():
if not f.endswith('.so') or 'designer' in dirpath or 'codecs' in dirpath or 'sqldrivers' in dirpath:
continue
f = os.path.join(dirpath, f)
dest_dir = dirpath.replace(plugdir, os.path.join(FREEZE_DIR, 'qtlugins'))
dest_dir = dirpath.replace(plugdir, os.path.join(FREEZE_DIR, 'qtplugins'))
copy_binary(f, dest_dir)
print 'Creating launchers'

View File

@ -2,7 +2,7 @@ __license__ = 'GPL v3'
__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
__docformat__ = 'restructuredtext en'
__appname__ = 'calibre'
__version__ = '0.4.113'
__version__ = '0.4.115'
__author__ = "Kovid Goyal <kovid@kovidgoyal.net>"
'''
Various run time constants.

View File

@ -88,10 +88,10 @@ def initialize_container(path_to_container, opf_name='metadata.opf'):
zf.writestr('META-INF/container.xml', CONTAINER)
return zf
def config(defaults=None):
def config(defaults=None, name='epub'):
desc = _('Options to control the conversion to EPUB')
if defaults is None:
c = Config('epub', desc)
c = Config(name, desc)
else:
c = StringConfig(defaults, desc)

View File

@ -116,7 +116,8 @@ def unarchive(path, tdir):
return f, ext
return find_html_index(files)
def any2epub(opts, path, notification=None):
def any2epub(opts, path, notification=None, create_epub=True,
oeb_cover=False, extract_to=None):
ext = os.path.splitext(path)[1]
if not ext:
raise ValueError('Unknown file type: '+path)
@ -139,7 +140,9 @@ def any2epub(opts, path, notification=None):
raise ValueError('Conversion from %s is not supported'%ext.upper())
print 'Creating EPUB file...'
html2epub(path, opts, notification=notification)
html2epub(path, opts, notification=notification,
create_epub=create_epub, oeb_cover=oeb_cover,
extract_to=extract_to)
def config(defaults=None):
return common_config(defaults=defaults)
@ -148,14 +151,14 @@ def config(defaults=None):
def formats():
return ['html', 'rar', 'zip', 'oebzip']+list(MAP.keys())
def option_parser():
return config().option_parser(usage=_('''\
USAGE = _('''\
%%prog [options] filename
Convert any of a large number of ebook formats to an epub file. Supported formats are: %s
''')%formats()
)
Convert any of a large number of ebook formats to a %s file. Supported formats are: %s
''')
def option_parser(usage=USAGE):
return config().option_parser(usage=usage%('EPUB', formats()))
def main(args=sys.argv):
parser = option_parser()

View File

@ -32,14 +32,14 @@ Conversion of HTML/OPF files follows several stages:
* The EPUB container is created.
'''
import os, sys, cStringIO, logging, re, functools
import os, sys, cStringIO, logging, re, functools, shutil
from lxml.etree import XPath
from lxml import html
from PyQt4.Qt import QApplication, QPixmap
from calibre.ebooks.html import Processor, merge_metadata, get_filelist,\
opf_traverse, create_metadata, rebase_toc, Link
opf_traverse, create_metadata, rebase_toc, Link, parser
from calibre.ebooks.epub import config as common_config, tostring
from calibre.ptempfile import TemporaryDirectory
from calibre.ebooks.metadata.toc import TOC
@ -62,9 +62,10 @@ def remove_bad_link(element, attribute, link, pos):
def check(opf_path, pretty_print):
'''
Find a remove all invalid links in the HTML files
Find and remove all invalid links in the HTML files
'''
print '\tChecking files for bad links...'
logger = logging.getLogger('html2epub')
logger.info('\tChecking files for bad links...')
pathtoopf = os.path.abspath(opf_path)
with CurrentDir(os.path.dirname(pathtoopf)):
opf = OPF(open(pathtoopf, 'rb'), os.path.dirname(pathtoopf))
@ -76,7 +77,7 @@ def check(opf_path, pretty_print):
for path in html_files:
base = os.path.dirname(path)
root = html.fromstring(open(content(path), 'rb').read())
root = html.fromstring(open(content(path), 'rb').read(), parser=parser)
for element, attribute, link, pos in list(root.iterlinks()):
link = to_unicode(link)
plink = Link(link, base)
@ -209,17 +210,16 @@ TITLEPAGE = '''\
</html>
'''
def create_cover_image(src, dest, screen_size):
from PyQt4.Qt import QApplication, QImage, Qt
if QApplication.instance() is None:
app = QApplication([])
app
im = QImage()
def create_cover_image(src, dest, screen_size, rescale_cover=True):
try:
from PyQt4.Qt import QImage, Qt
if QApplication.instance() is None:
QApplication([])
im = QImage()
im.load(src)
if im.isNull():
raise ValueError
if screen_size is not None:
raise ValueError('Invalid cover image')
if rescale_cover and screen_size is not None:
width, height = im.width(), im.height()
dw, dh = (screen_size[0]-width)/float(width), (screen_size[1]-height)/float(height)
delta = min(dw, dh)
@ -227,7 +227,7 @@ def create_cover_image(src, dest, screen_size):
nwidth = int(width + delta*(width))
nheight = int(height + delta*(height))
im = im.scaled(int(nwidth), int(nheight), Qt.IgnoreAspectRatio, Qt.SmoothTransformation)
im.save(dest)
im.save(dest)
except:
import traceback
traceback.print_exc()
@ -240,7 +240,6 @@ def process_title_page(mi, filelist, htmlfilemap, opts, tdir):
if mi.cover:
if f(filelist[0].path) == f(mi.cover):
old_title_page = htmlfilemap[filelist[0].path]
#logger = logging.getLogger('html2epub')
metadata_cover = mi.cover
if metadata_cover and not os.path.exists(metadata_cover):
@ -249,14 +248,15 @@ def process_title_page(mi, filelist, htmlfilemap, opts, tdir):
cpath = '/'.join(('resources', '_cover_.jpg'))
cover_dest = os.path.join(tdir, 'content', *cpath.split('/'))
if metadata_cover is not None:
if not create_cover_image(metadata_cover, cover_dest, opts.profile.screen_size):
if not create_cover_image(metadata_cover, cover_dest,
opts.profile.screen_size):
metadata_cover = None
specified_cover = opts.cover
if specified_cover and not os.path.exists(specified_cover):
specified_cover = None
if specified_cover is not None:
if not create_cover_image(specified_cover, cover_dest, opts.profile.screen_size):
if not create_cover_image(specified_cover, cover_dest,
opts.profile.screen_size):
specified_cover = None
cover = metadata_cover if specified_cover is None or (opts.prefer_metadata_cover and metadata_cover is not None) else specified_cover
@ -271,9 +271,16 @@ def process_title_page(mi, filelist, htmlfilemap, opts, tdir):
elif os.path.exists(cover_dest):
os.remove(cover_dest)
return None, old_title_page is not None
def convert(htmlfile, opts, notification=None):
def find_oeb_cover(htmlfile):
if os.stat(htmlfile).st_size > 2048:
return None
match = re.search(r'(?i)<img[^<>]+src\s*=\s*[\'"](.+?)[\'"]', open(htmlfile, 'rb').read())
if match:
return match.group(1)
def convert(htmlfile, opts, notification=None, create_epub=True,
oeb_cover=False, extract_to=None):
htmlfile = os.path.abspath(htmlfile)
if opts.output is None:
opts.output = os.path.splitext(os.path.basename(htmlfile))[0] + '.epub'
@ -325,7 +332,7 @@ def convert(htmlfile, opts, notification=None):
title_page, has_title_page = process_title_page(mi, filelist, htmlfile_map, opts, tdir)
spine = [htmlfile_map[f.path] for f in filelist]
if title_page is not None:
if not oeb_cover and title_page is not None:
spine = [title_page] + spine
mi.cover = None
mi.cover_data = (None, None)
@ -357,24 +364,43 @@ def convert(htmlfile, opts, notification=None):
check(opf_path, opts.pretty_print)
opf = OPF(opf_path, tdir)
opf.remove_guide()
if has_title_page:
oeb_cover_file = None
if oeb_cover and title_page is not None:
oeb_cover_file = find_oeb_cover(os.path.join(tdir, 'content', title_page))
if has_title_page or (oeb_cover and oeb_cover_file):
opf.create_guide_element()
opf.add_guide_item('cover', 'Cover', 'content/'+spine[0])
if has_title_page and not oeb_cover:
opf.add_guide_item('cover', 'Cover', 'content/'+spine[0])
if oeb_cover and oeb_cover_file:
opf.add_guide_item('cover', 'Cover', 'content/'+oeb_cover_file)
opf.add_path_to_manifest(os.path.join(tdir, 'content', 'resources', '_cover_.jpg'), 'image/jpeg')
cpath = os.path.join(tdir, 'content', 'resources', '_cover_.jpg')
if os.path.exists(cpath):
opf.add_path_to_manifest(cpath, 'image/jpeg')
with open(opf_path, 'wb') as f:
raw = opf.render()
if not raw.startswith('<?xml '):
raw = '<?xml version="1.0" encoding="UTF-8"?>\n'+raw
f.write(raw)
epub = initialize_container(opts.output)
epub.add_dir(tdir)
if create_epub:
epub = initialize_container(opts.output)
epub.add_dir(tdir)
epub.close()
logger.info(_('Output written to ')+opts.output)
if opts.show_opf:
print open(os.path.join(tdir, 'metadata.opf')).read()
logger.info('Output written to %s'%opts.output)
if opts.extract_to is not None:
epub.extractall(opts.extract_to)
epub.close()
if os.path.exists(opts.extract_to):
shutil.rmtree(opts.extract_to)
shutil.copytree(tdir, opts.extract_to)
if extract_to is not None:
if os.path.exists(extract_to):
shutil.rmtree(extract_to)
shutil.copytree(tdir, extract_to)
def main(args=sys.argv):

View File

@ -170,7 +170,10 @@ class EbookIterator(object):
dat = self.serialize_bookmarks(bookmarks)
if os.path.splitext(self.pathtoebook)[1].lower() == '.epub' and \
os.access(self.pathtoebook, os.R_OK):
zf = open(self.pathtoebook, 'r+b')
try:
zf = open(self.pathtoebook, 'r+b')
except IOError:
return
zipf = ZipFile(zf, mode='a')
for name in zipf.namelist():
if name == 'META-INF/calibre_bookmarks.txt':

View File

@ -0,0 +1,59 @@
from __future__ import with_statement
__license__ = 'GPL v3'
__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
__docformat__ = 'restructuredtext en'
'''
Convert any ebook format to LIT.
'''
import sys, os, glob, logging
from calibre.ebooks.epub.from_any import any2epub, formats, USAGE
from calibre.ebooks.epub import config as common_config
from calibre.ptempfile import TemporaryDirectory
from calibre.ebooks.lit.writer import oeb2lit
def config(defaults=None):
c = common_config(defaults=defaults, name='lit')
return c
def option_parser(usage=USAGE):
return config().option_parser(usage=usage%('LIT', formats()))
def any2lit(opts, path):
ext = os.path.splitext(path)[1]
if not ext:
raise ValueError('Unknown file type: '+path)
ext = ext.lower()[1:]
if opts.output is None:
opts.output = os.path.splitext(os.path.basename(path))[0]+'.lit'
opts.output = os.path.abspath(opts.output)
orig_output = opts.output
with TemporaryDirectory('_any2lit') as tdir:
oebdir = os.path.join(tdir, 'oeb')
os.mkdir(oebdir)
opts.output = os.path.join(tdir, 'dummy.epub')
opts.profile = 'None'
any2epub(opts, path, create_epub=False, oeb_cover=True, extract_to=oebdir)
opf = glob.glob(os.path.join(oebdir, '*.opf'))[0]
opts.output = orig_output
logging.getLogger('html2epub').info(_('Creating LIT file from EPUB...'))
oeb2lit(opts, opf)
def main(args=sys.argv):
parser = option_parser()
opts, args = parser.parse_args(args)
if len(args) < 2:
parser.print_help()
print 'No input file specified.'
return 1
any2lit(opts, args[1])
return 0
if __name__ == '__main__':
sys.exit(main())

View File

@ -13,7 +13,9 @@ from types import StringTypes
from itertools import izip, count
from urlparse import urldefrag, urlparse, urlunparse
from urllib import unquote as urlunquote
import logging
from lxml import etree
from calibre import LoggingInterface
XML_PARSER = etree.XMLParser(recover=True, resolve_entities=False)
XML_NS = 'http://www.w3.org/XML/1998/namespace'
@ -88,6 +90,13 @@ def urlnormalize(href):
return urlunparse(parts)
class FauxLogger(object):
def __getattr__(self, name):
return self
def __call__(self, message):
print message
class AbstractContainer(object):
def read_xml(self, path):
return etree.fromstring(
@ -108,6 +117,10 @@ class DirContainer(AbstractContainer):
with open(urlunquote(path), 'wb') as f:
return f.write(data)
def exists(self, path):
path = os.path.join(self.rootdir, path)
return os.path.isfile(path)
class Metadata(object):
TERMS = set(['contributor', 'coverage', 'creator', 'date', 'description',
@ -530,13 +543,14 @@ class TOC(object):
node.to_ncx(point, playorder, depth+1)
return parent
class OEBBook(object):
def __init__(self, opfpath=None, container=None):
def __init__(self, opfpath=None, container=None, logger=FauxLogger()):
if not container:
container = DirContainer(os.path.dirname(opfpath))
opfpath = os.path.basename(opfpath)
self.container = container
self.logger = logger
opf = self._read_opf(opfpath)
self._all_from_opf(opf)
@ -590,17 +604,28 @@ class OEBBook(object):
if item.id == uid:
self.uid = item
break
else:
self.logger.log_warn(u'Unique-identifier %r not found.' % uid)
self.uid = metadata.identifier[0]
def _manifest_from_opf(self, opf):
self.manifest = manifest = Manifest(self)
for elem in xpath(opf, '/o2:package/o2:manifest/o2:item'):
manifest.add(elem.get('id'), elem.get('href'),
elem.get('media-type'), elem.get('fallback'))
href = elem.get('href')
if not self.container.exists(href):
self.logger.log_warn(u'Manifest item %r not found.' % href)
continue
manifest.add(elem.get('id'), href, elem.get('media-type'),
elem.get('fallback'))
def _spine_from_opf(self, opf):
self.spine = spine = Spine(self)
for elem in xpath(opf, '/o2:package/o2:spine/o2:itemref'):
item = self.manifest[elem.get('idref')]
idref = elem.get('idref')
if idref not in self.manifest:
self.logger.log_warn(u'Spine item %r not found.' % idref)
continue
item = self.manifest[idref]
spine.add(item, elem.get('linear'))
extras = []
for item in self.manifest.values():
@ -614,7 +639,11 @@ class OEBBook(object):
def _guide_from_opf(self, opf):
self.guide = guide = Guide(self)
for elem in xpath(opf, '/o2:package/o2:guide/o2:reference'):
guide.add(elem.get('type'), elem.get('title'), elem.get('href'))
href = elem.get('href')
if href not in self.manifest.hrefs:
self.logger.log_warn(u'Guide reference %r not found' % href)
continue
guide.add(elem.get('type'), elem.get('title'), href)
def _toc_from_navpoint(self, toc, navpoint):
children = xpath(navpoint, 'ncx:navPoint')

View File

@ -9,7 +9,7 @@ __copyright__ = '2008, Marshall T. Vandegrift <llasram@gmail.com>'
import sys
import os
from cStringIO import StringIO
from struct import pack, unpack
from struct import pack
from itertools import izip, count, chain
import time
import random
@ -17,19 +17,20 @@ import re
import copy
import uuid
import functools
import logging
from urlparse import urldefrag
from urllib import unquote as urlunquote
from lxml import etree
from calibre.ebooks.lit import LitError
from calibre.ebooks.lit.reader import msguid, DirectoryEntry
from calibre.ebooks.lit.reader import DirectoryEntry
import calibre.ebooks.lit.maps as maps
from calibre.ebooks.lit.oeb import OEB_DOCS, OEB_STYLES, OEB_CSS_MIME, \
CSS_MIME, XHTML_MIME, OPF_MIME, XML_NS, XML
CSS_MIME, OPF_MIME, XML_NS, XML
from calibre.ebooks.lit.oeb import namespace, barename, urlnormalize, xpath
from calibre.ebooks.lit.oeb import OEBBook
from calibre.ebooks.lit.oeb import FauxLogger, OEBBook
from calibre.ebooks.lit.stylizer import Stylizer
from calibre.ebooks.lit.lzx import Compressor
import calibre
from calibre import LoggingInterface
from calibre import plugins
msdes, msdeserror = plugins['msdes']
import calibre.ebooks.lit.mssha1 as mssha1
@ -135,11 +136,15 @@ def decint(value):
def randbytes(n):
return ''.join(chr(random.randint(0, 255)) for x in xrange(n))
def warn(x):
print x
class ReBinary(object):
NSRMAP = {'': None, XML_NS: 'xml'}
def __init__(self, root, path, oeb, map=HTML_MAP):
def __init__(self, root, path, oeb, map=HTML_MAP, logger=FauxLogger()):
self.path = path
self.logger = logger
self.dir = os.path.dirname(path)
self.manifest = oeb.manifest
self.tags, self.tattrs = map
@ -268,8 +273,8 @@ class ReBinary(object):
def build_ahc(self):
if len(self.anchors) > 6:
print "calibre: warning: More than six anchors in file %r. " \
"Some links may not work properly." % self.path
self.logger.log_warn("More than six anchors in file %r. " \
"Some links may not work properly." % self.path)
data = StringIO()
data.write(unichr(len(self.anchors)).encode('utf-8'))
for anchor, offset in self.anchors:
@ -293,8 +298,9 @@ def preserve(function):
return wrapper
class LitWriter(object):
def __init__(self, oeb):
def __init__(self, oeb, logger=FauxLogger()):
self._oeb = oeb
self._logger = logger
self._litize_oeb()
def _litize_oeb(self):
@ -307,6 +313,9 @@ class LitWriter(object):
elif MS_COVER_TYPE in oeb.guide:
href = oeb.guide[MS_COVER_TYPE].href
cover = oeb.manifest.hrefs[href]
elif 'cover' in oeb.guide:
href = oeb.guide['cover'].href
cover = oeb.manifest.hrefs[href]
else:
html = oeb.spine[0].data
imgs = xpath(html, '//img[position()=1]')
@ -319,7 +328,7 @@ class LitWriter(object):
if type not in oeb.guide:
oeb.guide.add(type, title, cover.href)
else:
print "calibre: warning: No suitable cover image found."
self._logger.log_warn('No suitable cover image found.')
def dump(self, stream):
self._stream = stream
@ -461,15 +470,16 @@ class LitWriter(object):
self._add_folder('/data')
for item in self._oeb.manifest.values():
if item.media_type not in LIT_MIMES:
print "calibre: warning: File %r of unknown media-type %r " \
"excluded from output." % (item.href, item.media_type)
self._logger.log_warn("File %r of unknown media-type %r " \
"excluded from output." % (item.href, item.media_type))
continue
name = '/data/' + item.id
data = item.data
secnum = 0
if not isinstance(data, basestring):
self._add_folder(name)
rebin = ReBinary(data, item.href, self._oeb)
rebin = ReBinary(data, item.href, self._oeb, map=HTML_MAP,
logger=self._logger)
self._add_file(name + '/ahc', rebin.ahc, 0)
self._add_file(name + '/aht', rebin.aht, 0)
item.page_breaks = rebin.page_breaks
@ -548,7 +558,8 @@ class LitWriter(object):
meta.attrib['ms--minimum_level'] = '0'
meta.attrib['ms--attr5'] = '1'
meta.attrib['ms--guid'] = '{%s}' % str(uuid.uuid4()).upper()
rebin = ReBinary(meta, 'content.opf', self._oeb, OPF_MAP)
rebin = ReBinary(meta, 'content.opf', self._oeb, map=OPF_MAP,
logger=self._logger)
meta = rebin.content
self._meta = meta
self._add_file('/meta', meta)
@ -707,8 +718,25 @@ def option_parser():
parser.add_option(
'-o', '--output', default=None,
help=_('Output file. Default is derived from input filename.'))
parser.add_option(
'--verbose', default=False, action='store_true',
help=_('Useful for debugging.'))
return parser
def oeb2lit(opts, opfpath):
logger = LoggingInterface(logging.getLogger('oeb2lit'))
logger.setup_cli_handler(opts.verbose)
litpath = opts.output
if litpath is None:
litpath = os.path.basename(opfpath)
litpath = os.path.splitext(litpath)[0] + '.lit'
litpath = os.path.abspath(litpath)
lit = LitWriter(OEBBook(opfpath))
with open(litpath, 'wb') as f:
lit.dump(f)
logger.log_info(_('Output written to ')+litpath)
def main(argv=sys.argv):
parser = option_parser()
opts, args = parser.parse_args(argv[1:])
@ -716,14 +744,7 @@ def main(argv=sys.argv):
parser.print_help()
return 1
opfpath = args[0]
litpath = opts.output
if litpath is None:
litpath = os.path.basename(opfpath)
litpath = os.path.splitext(litpath)[0] + '.lit'
lit = LitWriter(OEBBook(opfpath))
with open(litpath, 'wb') as f:
lit.dump(f)
print _('LIT ebook created at'), litpath
oeb2lit(opts, opfpath)
return 0
if __name__ == '__main__':

View File

@ -1920,7 +1920,7 @@ def process_file(path, options, logger=None):
options.anchor_ids = True
files = options.spine if (options.use_spine and hasattr(options, 'spine')) else [path]
conv = HTMLConverter(book, fonts, options, logger, files)
if options.use_spine and hasattr(options, 'toc'):
if options.use_spine and hasattr(options, 'toc') and options.toc is not None:
conv.create_toc(options.toc)
oname = options.output
if not oname:

View File

@ -101,7 +101,6 @@ class ConfigDialog(QDialog, Ui_Dialog):
for item in items:
self.language.addItem(item[1], QVariant(item[0]))
self.output_format.setCurrentIndex(0 if prefs['output_format'] == 'LRF' else 1)
self.pdf_metadata.setChecked(prefs['read_file_metadata'])
added_html = False
@ -255,16 +254,11 @@ class ConfigDialog(QDialog, Ui_Dialog):
sc.set('max_cover', mcs)
config['delete_news_from_library_on_upload'] = self.delete_news.isChecked()
config['upload_news_to_device'] = self.sync_news.isChecked()
of = str(self.output_format.currentText())
fmts = []
for i in range(self.viewer.count()):
if self.viewer.item(i).checkState() == Qt.Checked:
fmts.append(str(self.viewer.item(i).text()))
config['internally_viewed_formats'] = fmts
if of != prefs['output_format'] and 'epub' in of.lower():
warning_dialog(self, 'Warning',
'<p>EPUB support is still in beta. If you find bugs, please report them by opening a <a href="http://calibre.kovidgoyal.net">ticket</a>.').exec_()
prefs['output_format'] = of
if not path or not os.path.exists(path) or not os.path.isdir(path):
d = error_dialog(self, _('Invalid database location'),

View File

@ -149,7 +149,7 @@
</item>
<item>
<layout class="QGridLayout" name="gridLayout_2" >
<item row="1" column="0" >
<item row="0" column="0" >
<widget class="QLabel" name="label_5" >
<property name="text" >
<string>Format for &amp;single file save:</string>
@ -159,10 +159,10 @@
</property>
</widget>
</item>
<item row="1" column="1" >
<item row="0" column="1" >
<widget class="QComboBox" name="single_format" />
</item>
<item row="2" column="0" >
<item row="1" column="0" >
<widget class="QLabel" name="label_2" >
<property name="text" >
<string>Default network &amp;timeout:</string>
@ -172,7 +172,7 @@
</property>
</widget>
</item>
<item row="2" column="1" >
<item row="1" column="1" >
<widget class="QSpinBox" name="timeout" >
<property name="toolTip" >
<string>Set the default timeout for network fetches (i.e. anytime we go out to the internet to get information)</string>
@ -191,10 +191,10 @@
</property>
</widget>
</item>
<item row="3" column="1" >
<item row="2" column="1" >
<widget class="QComboBox" name="language" />
</item>
<item row="3" column="0" >
<item row="2" column="0" >
<widget class="QLabel" name="label_7" >
<property name="text" >
<string>Choose &amp;language (requires restart):</string>
@ -204,34 +204,7 @@
</property>
</widget>
</item>
<item row="0" column="1" >
<widget class="QComboBox" name="output_format" >
<property name="toolTip" >
<string>The default output format for ebook conversions.</string>
</property>
<item>
<property name="text" >
<string>LRF</string>
</property>
</item>
<item>
<property name="text" >
<string>EPUB</string>
</property>
</item>
</widget>
</item>
<item row="0" column="0" >
<widget class="QLabel" name="label_8" >
<property name="text" >
<string>&amp;Output format:</string>
</property>
<property name="buddy" >
<cstring>output_format</cstring>
</property>
</widget>
</item>
<item row="4" column="1" >
<item row="3" column="1" >
<widget class="QComboBox" name="priority" >
<item>
<property name="text" >
@ -250,7 +223,7 @@
</item>
</widget>
</item>
<item row="4" column="0" >
<item row="3" column="0" >
<widget class="QLabel" name="priority_label" >
<property name="text" >
<string>Job &amp;priority:</string>

View File

@ -0,0 +1,581 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Generator: Adobe Illustrator 12.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 51448) -->
<svg
xmlns:ns="http://ns.adobe.com/SaveForWeb/1.0/"
xmlns:i="http://ns.adobe.com/AdobeIllustrator/10.0/"
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://web.resource.org/cc/"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
version="1.1"
id="Layer_1"
width="128"
height="128"
viewBox="0 0 128 128"
overflow="visible"
enable-background="new 0 0 128 128"
xml:space="preserve"
sodipodi:version="0.32"
inkscape:version="0.45.1"
sodipodi:docname="edit-copy.svg"
sodipodi:docbase="/home/david/sandbox"
inkscape:output_extension="org.inkscape.output.svg.inkscape"
inkscape:export-filename="/home/david/sandbox/edit-copy.png"
inkscape:export-xdpi="22.5"
inkscape:export-ydpi="22.5"><defs
id="defs105"><linearGradient
id="linearGradient3291"><stop
style="stop-color:black;stop-opacity:1"
offset="0"
id="stop3293" /><stop
style="stop-color:black;stop-opacity:0"
offset="1"
id="stop3295" /></linearGradient><linearGradient
y2="0"
x2="28"
y1="57.5"
x1="28"
gradientUnits="userSpaceOnUse"
id="linearGradient18668">
<stop
id="stop18670"
style="stop-color:#fffccf;stop-opacity:1;"
offset="0" />
<stop
id="stop18672"
style="stop-color:white;stop-opacity:0;"
offset="1" />
</linearGradient><linearGradient
y2="0"
x2="28"
y1="57.5"
x1="28"
gradientUnits="userSpaceOnUse"
id="linearGradient15967">
<stop
id="stop15969"
style="stop-color:white;stop-opacity:1;"
offset="0" />
<stop
id="stop15971"
style="stop-color:white;stop-opacity:0;"
offset="1" />
</linearGradient><linearGradient
id="XMLID_2_"
gradientUnits="userSpaceOnUse"
x1="28"
y1="57.5"
x2="28"
y2="0">
<stop
offset="0"
style="stop-color:#FFEA00"
id="stop12" />
<stop
offset="1"
style="stop-color:#FFCC00"
id="stop14" />
</linearGradient><linearGradient
inkscape:collect="always"
xlink:href="#XMLID_9_"
id="linearGradient2391"
gradientUnits="userSpaceOnUse"
x1="94.3438"
y1="102.3447"
x2="86.5356"
y2="94.5366" /><linearGradient
inkscape:collect="always"
xlink:href="#XMLID_10_"
id="linearGradient2393"
gradientUnits="userSpaceOnUse"
x1="95"
y1="103"
x2="86.5865"
y2="94.5865" /><linearGradient
inkscape:collect="always"
xlink:href="#XMLID_11_"
id="linearGradient2395"
gradientUnits="userSpaceOnUse"
x1="95"
y1="103"
x2="87.293"
y2="95.293" /><linearGradient
inkscape:collect="always"
xlink:href="#XMLID_12_"
id="linearGradient2397"
gradientUnits="userSpaceOnUse"
x1="96"
y1="104"
x2="88.0002"
y2="96.0002" /><radialGradient
inkscape:collect="always"
xlink:href="#XMLID_7_"
id="radialGradient2465"
gradientUnits="userSpaceOnUse"
cx="102"
cy="112.3047"
r="139.5585" /><radialGradient
inkscape:collect="always"
xlink:href="#XMLID_8_"
id="radialGradient2467"
gradientUnits="userSpaceOnUse"
cx="102"
cy="112.3047"
r="139.55859" />
<foreignObject
requiredExtensions="http://ns.adobe.com/AdobeIllustrator/10.0/"
x="0"
y="0"
width="1"
height="1"
id="foreignObject7">
<i:pgfRef
xlink:href="#adobe_illustrator_pgf">
</i:pgfRef>
</foreignObject>
<linearGradient
inkscape:collect="always"
xlink:href="#XMLID_2_"
id="linearGradient12378"
gradientUnits="userSpaceOnUse"
x1="28"
y1="57.5"
x2="28"
y2="0" /><linearGradient
inkscape:collect="always"
xlink:href="#linearGradient2309"
id="linearGradient14180"
gradientUnits="userSpaceOnUse"
x1="-74.820707"
y1="100.82378"
x2="-18.121965"
y2="100.82378" /><linearGradient
inkscape:collect="always"
xlink:href="#linearGradient2309"
id="linearGradient14189"
gradientUnits="userSpaceOnUse"
x1="-74.820707"
y1="100.82378"
x2="-18.121965"
y2="100.82378" /><linearGradient
inkscape:collect="always"
xlink:href="#linearGradient15967"
id="linearGradient15973"
gradientUnits="userSpaceOnUse"
x1="27.719746"
y1="7.881104"
x2="27.719746"
y2="30.441185"
gradientTransform="translate(1.470416e-5,0)" /><linearGradient
inkscape:collect="always"
xlink:href="#linearGradient2309"
id="linearGradient15977"
gradientUnits="userSpaceOnUse"
x1="-74.820707"
y1="100.82378"
x2="-18.121965"
y2="100.82378" /><radialGradient
inkscape:collect="always"
xlink:href="#linearGradient15967"
id="radialGradient15986"
cx="26.954102"
cy="31.045055"
fx="26.954102"
fy="31.045055"
r="8.968153"
gradientTransform="matrix(0.754978,-2.959381e-2,0,0.905772,7.650275,10.87807)"
gradientUnits="userSpaceOnUse" /><linearGradient
inkscape:collect="always"
xlink:href="#XMLID_2_"
id="linearGradient18657"
gradientUnits="userSpaceOnUse"
x1="28"
y1="57.5"
x2="28"
y2="0" />
<linearGradient
id="linearGradient18649"
gradientUnits="userSpaceOnUse"
x1="28"
y1="57.5"
x2="28"
y2="0">
<stop
offset="0"
style="stop-color:#FFEA00"
id="stop18651" />
<stop
offset="1"
style="stop-color:#FFCC00"
id="stop18653" />
</linearGradient>
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient18668"
id="linearGradient18674"
gradientUnits="userSpaceOnUse"
x1="-39.53125"
y1="78"
x2="-39.53125"
y2="51.1875" /><linearGradient
inkscape:collect="always"
xlink:href="#linearGradient18668"
id="linearGradient18746"
gradientUnits="userSpaceOnUse"
x1="-39.53125"
y1="78"
x2="-39.53125"
y2="51.1875" /><radialGradient
inkscape:collect="always"
xlink:href="#XMLID_8_"
id="radialGradient2311"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.856383,0,0,0.8415585,11.191492,18.14026)"
cx="99.080742"
cy="109.33402"
r="139.55859"
fx="99.080742"
fy="109.33402" /><radialGradient
inkscape:collect="always"
xlink:href="#XMLID_7_"
id="radialGradient2314"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.8749999,0,0,0.8571428,10.000003,17.142857)"
cx="102"
cy="112.3047"
r="139.5585" /><linearGradient
inkscape:collect="always"
xlink:href="#XMLID_12_"
id="linearGradient2339"
gradientUnits="userSpaceOnUse"
x1="96"
y1="104"
x2="86.571632"
y2="94.104362"
gradientTransform="matrix(0.8749999,0,0,0.8571428,10.000003,17.142857)" /><filter
inkscape:collect="always"
id="filter6241"><feGaussianBlur
inkscape:collect="always"
stdDeviation="1.2065414"
id="feGaussianBlur6243" /></filter><radialGradient
inkscape:collect="always"
xlink:href="#XMLID_7_"
id="radialGradient6272"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.8749999,0,0,0.8571428,10.000003,17.142857)"
cx="102"
cy="112.3047"
r="139.5585" /><radialGradient
inkscape:collect="always"
xlink:href="#XMLID_8_"
id="radialGradient6274"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.856383,0,0,0.8415585,11.191492,18.14026)"
cx="99.080742"
cy="109.33402"
fx="99.080742"
fy="109.33402"
r="139.55859" /><linearGradient
inkscape:collect="always"
xlink:href="#XMLID_12_"
id="linearGradient6276"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.8749999,0,0,0.8571428,10.000003,17.142857)"
x1="96"
y1="104"
x2="86.571632"
y2="94.104362" /><filter
inkscape:collect="always"
id="filter3217"><feGaussianBlur
inkscape:collect="always"
stdDeviation="0.89955545"
id="feGaussianBlur3219" /></filter></defs><sodipodi:namedview
inkscape:window-height="670"
inkscape:window-width="1022"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
guidetolerance="10.0"
gridtolerance="10.0"
objecttolerance="10.0"
borderopacity="1.0"
bordercolor="#666666"
pagecolor="#ffffff"
id="base"
inkscape:zoom="4"
inkscape:cx="92.02737"
inkscape:cy="54.798944"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:current-layer="g6245"
showgrid="true"
gridspacingx="4px"
gridspacingy="4px"
gridempspacing="0"
inkscape:grid-points="false"
inkscape:grid-bbox="true"
inkscape:object-points="true" />
<metadata
id="metadata3">
<ns:sfw>
<ns:slices>
<ns:slice
y="0"
x="0"
height="128"
width="128"
sliceID="1316743234" />
</ns:slices>
<ns:sliceSourceBounds
y="0"
x="0"
height="128"
width="128"
bottomLeftOrigin="true" />
<ns:optimizationSettings>
<ns:targetSettings
targetSettingsID="0"
fileFormat="PNG24Format">
<ns:PNG24Format
transparency="true"
filtered="false"
matteColor="#FFFFFF"
noMatteColor="false"
interlaced="false">
</ns:PNG24Format>
</ns:targetSettings>
<ns:targetSettings
targetSettingsID="1696735251"
fileFormat="PNG24Format">
<ns:PNG24Format
transparency="true"
filtered="false"
matteColor="#FFFFFF"
noMatteColor="false"
interlaced="false">
</ns:PNG24Format>
</ns:targetSettings>
</ns:optimizationSettings>
</ns:sfw>
<rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata>
<radialGradient
id="XMLID_7_"
cx="102"
cy="112.3047"
r="139.5585"
gradientUnits="userSpaceOnUse">
<stop
offset="0"
style="stop-color:#00537D"
id="stop16" />
<stop
offset="0.0151"
style="stop-color:#186389"
id="stop18" />
<stop
offset="0.0558"
style="stop-color:#558CA8"
id="stop20" />
<stop
offset="0.0964"
style="stop-color:#89AFC3"
id="stop22" />
<stop
offset="0.1357"
style="stop-color:#B3CCD8"
id="stop24" />
<stop
offset="0.1737"
style="stop-color:#D4E2E9"
id="stop26" />
<stop
offset="0.2099"
style="stop-color:#ECF2F5"
id="stop28" />
<stop
offset="0.2435"
style="stop-color:#FAFCFD"
id="stop30" />
<stop
offset="0.2722"
style="stop-color:#FFFFFF"
id="stop32" />
</radialGradient>
<radialGradient
id="XMLID_8_"
cx="102"
cy="112.3047"
r="139.55859"
gradientUnits="userSpaceOnUse">
<stop
offset="0"
style="stop-color:#7a7d80;stop-opacity:1;"
id="stop37" />
<stop
offset="0.12617962"
style="stop-color:#c2c2c2;stop-opacity:1;"
id="stop47" />
<stop
offset="0.23250513"
style="stop-color:#FAFAFA"
id="stop49" />
<stop
offset="0.2722"
style="stop-color:#FFFFFF"
id="stop51" />
<stop
offset="0.5313"
style="stop-color:#FAFAFA"
id="stop53" />
<stop
offset="0.8449"
style="stop-color:#EBECEC"
id="stop55" />
<stop
offset="1"
style="stop-color:#E1E2E3"
id="stop57" />
</radialGradient>
<linearGradient
id="XMLID_9_"
gradientUnits="userSpaceOnUse"
x1="94.3438"
y1="102.3447"
x2="86.5356"
y2="94.5366">
<stop
offset="0"
style="stop-color:#FFFFFF"
id="stop62" />
<stop
offset="1"
style="stop-color:#555753"
id="stop64" />
</linearGradient>
<linearGradient
id="XMLID_10_"
gradientUnits="userSpaceOnUse"
x1="95"
y1="103"
x2="86.5865"
y2="94.5865">
<stop
offset="0"
style="stop-color:#FFFFFF"
id="stop69" />
<stop
offset="1"
style="stop-color:#555753"
id="stop71" />
</linearGradient>
<linearGradient
id="XMLID_11_"
gradientUnits="userSpaceOnUse"
x1="95"
y1="103"
x2="87.293"
y2="95.293">
<stop
offset="0"
style="stop-color:#FFFFFF"
id="stop76" />
<stop
offset="1"
style="stop-color:#393B38"
id="stop78" />
</linearGradient>
<linearGradient
id="XMLID_12_"
gradientUnits="userSpaceOnUse"
x1="96"
y1="104"
x2="88.0002"
y2="96.0002">
<stop
offset="0"
style="stop-color:#888A85"
id="stop83" />
<stop
offset="0.0072"
style="stop-color:#8C8E89"
id="stop85" />
<stop
offset="0.0673"
style="stop-color:#ABACA9"
id="stop87" />
<stop
offset="0.1347"
style="stop-color:#C5C6C4"
id="stop89" />
<stop
offset="0.2115"
style="stop-color:#DBDBDA"
id="stop91" />
<stop
offset="0.3012"
style="stop-color:#EBEBEB"
id="stop93" />
<stop
offset="0.4122"
style="stop-color:#F7F7F6"
id="stop95" />
<stop
offset="0.5679"
style="stop-color:#FDFDFD"
id="stop97" />
<stop
offset="1"
style="stop-color:#FFFFFF"
id="stop99" />
</linearGradient>
<g
id="g6245"><g
id="g6263"
transform="translate(12,0)"
style="opacity:1"><path
sodipodi:nodetypes="cccccc"
id="path2350"
d="M 23,25 L 23,121 L 76.525498,121 C 76.989247,121 107,91.601715 107,91.147428 L 107,25 L 23,25 z "
style="opacity:0.6;fill:#000000;fill-opacity:1;filter:url(#filter3217)"
transform="matrix(1.047619,0,0,1.0416667,-2.0952381,-4.041666)" /><path
style="fill:url(#radialGradient6272)"
d="M 24.000002,24 L 24.000002,120 L 77.5255,120 C 77.989249,120 108,90.601715 108,90.147428 L 108,24 L 24.000002,24 z "
id="path34"
sodipodi:nodetypes="cccccc" /><path
style="fill:url(#radialGradient6274);fill-opacity:1"
d="M 26.606384,25.714285 C 26.134518,25.714285 25.750001,26.092145 25.750001,26.555844 L 25.750001,117.44415 C 25.750001,117.9087 26.134518,118.28572 26.606384,118.28572 L 77.280277,118.28572 C 77.505506,118.28572 77.726453,118.19652 77.885739,118.03914 L 105.99908,90.412457 C 106.15921,90.255085 106.25,90.038805 106.25,89.817475 L 106.25,26.555844 C 106.25,26.092145 105.86634,25.714285 105.39361,25.714285 L 26.606384,25.714285 z "
id="path59" /><path
d="M 76.5255,120 C 76.5255,120 88.18749,110.99999 92.99999,106.28571 C 97.81249,101.57142 107,90.147428 107,90.147428 C 107,90.147428 99,96 83,96 C 83,112 76.5255,120 76.5255,120 z "
id="path101"
style="opacity:0.5;fill:#000000;fill-opacity:1;stroke:none;filter:url(#filter6241)"
sodipodi:nodetypes="csccc" /><path
sodipodi:nodetypes="csccc"
style="fill:url(#linearGradient6276)"
id="path6233"
d="M 77.5255,120 C 77.5255,120 89.18749,110.99999 93.99999,106.28571 C 98.81249,101.57142 108,90.147428 108,90.147428 C 108,90.147428 100,96 84,96 C 84,112 77.5255,120 77.5255,120 z " /></g><use
x="0"
y="0"
xlink:href="#g6263"
id="use6270"
width="128"
height="128"
transform="translate(-28,-16)" /></g>
</svg>

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 899 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 670 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

View File

@ -188,6 +188,6 @@ class DetailView(QDialog, Ui_Dialog):
def update(self):
self.log.setPlainText(self.job.gui_text())
self.log.setPlainText(self.job.console_text())
vbar = self.log.verticalScrollBar()
vbar.setValue(vbar.maximum())

View File

@ -575,7 +575,7 @@ class BooksView(TableView):
self.setItemDelegateForColumn(col, self.rating_delegate)
def set_context_menu(self, edit_metadata, send_to_device, convert, view,
save, open_folder, book_details):
save, open_folder, book_details, similar_menu=None):
self.setContextMenuPolicy(Qt.DefaultContextMenu)
self.context_menu = QMenu(self)
if edit_metadata is not None:
@ -590,6 +590,8 @@ class BooksView(TableView):
self.context_menu.addAction(open_folder)
if book_details is not None:
self.context_menu.addAction(book_details)
if similar_menu is not None:
self.context_menu.addMenu(similar_menu)
def contextMenuEvent(self, event):
self.context_menu.popup(event.globalPos())

View File

@ -126,6 +126,21 @@ class Main(MainWindow, Ui_MainWindow):
self.location_selected)
QObject.connect(self.stack, SIGNAL('currentChanged(int)'),
self.location_view.location_changed)
self.output_formats = sorted(['EPUB', 'LRF'])
for f in self.output_formats:
self.output_format.addItem(f)
self.output_format.setCurrentIndex(self.output_formats.index(prefs['output_format']))
def change_output_format(x):
of = unicode(x).strip()
if of != prefs['output_format']:
if of in ('EPUB', 'LIT'):
warning_dialog(self, 'Warning',
'<p>%s support is still in beta. If you find bugs, please report them by opening a <a href="http://calibre.kovidgoyal.net">ticket</a>.'%of).exec_()
prefs.set('output_format', of)
self.connect(self.output_format, SIGNAL('currentIndexChanged(QString)'),
change_output_format)
####################### Vanity ########################
self.vanity_template = _('<p>For help visit <a href="http://%s.kovidgoyal.net/user_manual">%s.kovidgoyal.net</a><br>')%(__appname__, __appname__)
@ -234,10 +249,33 @@ class Main(MainWindow, Ui_MainWindow):
QObject.connect(self.advanced_search_button, SIGNAL('clicked(bool)'), self.do_advanced_search)
####################### Library view ########################
similar_menu = QMenu(_('Similar books...'))
similar_menu.addAction(self.action_books_by_same_author)
similar_menu.addAction(self.action_books_in_this_series)
similar_menu.addAction(self.action_books_with_the_same_tags)
similar_menu.addAction(self.action_books_by_this_publisher)
self.action_books_by_same_author.setShortcut(Qt.ALT + Qt.Key_A)
self.action_books_in_this_series.setShortcut(Qt.ALT + Qt.Key_S)
self.action_books_by_this_publisher.setShortcut(Qt.ALT + Qt.Key_P)
self.action_books_with_the_same_tags.setShortcut(Qt.ALT+Qt.Key_T)
self.addAction(self.action_books_by_same_author)
self.addAction(self.action_books_by_this_publisher)
self.addAction(self.action_books_in_this_series)
self.addAction(self.action_books_with_the_same_tags)
self.similar_menu = similar_menu
self.connect(self.action_books_by_same_author, SIGNAL('triggered()'),
lambda : self.show_similar_books('author'))
self.connect(self.action_books_in_this_series, SIGNAL('triggered()'),
lambda : self.show_similar_books('series'))
self.connect(self.action_books_with_the_same_tags, SIGNAL('triggered()'),
lambda : self.show_similar_books('tag'))
self.connect(self.action_books_by_this_publisher, SIGNAL('triggered()'),
lambda : self.show_similar_books('publisher'))
self.library_view.set_context_menu(self.action_edit, self.action_sync,
self.action_convert, self.action_view,
self.action_save, self.action_open_containing_folder,
self.action_show_book_details)
self.action_show_book_details,
similar_menu=similar_menu)
self.memory_view.set_context_menu(None, None, None, self.action_view, self.action_save, None, None)
self.card_view.set_context_menu(None, None, None, self.action_view, self.action_save, None, None)
QObject.connect(self.library_view, SIGNAL('files_dropped(PyQt_PyObject)'),
@ -258,7 +296,7 @@ class Main(MainWindow, Ui_MainWindow):
db = LibraryDatabase2(self.library_path)
except OSError, err:
error_dialog(self, _('Bad database location'), unicode(err)).exec_()
dir = unicode(QFileDialog.getExistingDirectory(self,
dir = unicode(QFileDialog.getExistingDirectory(self,
_('Choose a location for your ebook library.'), os.path.expanduser('~')))
if not dir:
QCoreApplication.exit(1)
@ -341,6 +379,34 @@ class Main(MainWindow, Ui_MainWindow):
error_dialog(self, _('Failed to start content server'),
unicode(self.content_server.exception)).exec_()
def show_similar_books(self, type):
search, join = [], ' '
idx = self.library_view.currentIndex()
if not idx.isValid():
return
row = idx.row()
if type == 'series':
series = idx.model().db.series(row)
if series:
search = ['series:'+series]
elif type == 'publisher':
publisher = idx.model().db.publisher(row)
if publisher:
search = ['publisher:'+publisher]
elif type == 'tag':
tags = idx.model().db.tags(row)
if tags:
search = ['tag:'+t for t in tags.split(',')]
elif type == 'author':
authors = idx.model().db.authors(row)
if authors:
search = ['author:'+a.strip().replace('|', ',') for a in authors.split(',')]
join = ' or '
if search:
self.search.set_search_string(join.join(search))
def toggle_cover_flow(self, show):
if show:
self.library_view.setCurrentIndex(self.library_view.currentIndex())
@ -438,7 +504,7 @@ class Main(MainWindow, Ui_MainWindow):
return
info, cp, fs = job.result
self.location_view.model().update_devices(cp, fs)
self.device_info = _('Connected ')+' '.join(info[:-1])
self.device_info = _('Connected ')+info[0]
self.vanity.setText(self.vanity_template%dict(version=self.latest_version, device=self.device_info))
self.device_manager.books(Dispatcher(self.metadata_downloaded))
@ -621,13 +687,13 @@ class Main(MainWindow, Ui_MainWindow):
files, names, on_card=on_card,
titles=titles
)
self.upload_memory[job] = (metadata, on_card, memory)
self.upload_memory[job] = (metadata, on_card, memory, files)
def books_uploaded(self, job):
'''
Called once books have been uploaded.
'''
metadata, on_card, memory = self.upload_memory.pop(job)
metadata, on_card, memory, files = self.upload_memory.pop(job)
if job.exception is not None:
if isinstance(job.exception, FreeSpaceError):
@ -648,6 +714,8 @@ class Main(MainWindow, Ui_MainWindow):
view = self.card_view if on_card else self.memory_view
view.model().resort(reset=False)
view.model().research()
for f in files:
getattr(f, 'close', lambda : True)()
if memory and memory[1]:
self.library_view.model().delete_books_by_id(memory[1])

View File

@ -27,15 +27,9 @@
<normaloff>:/library</normaloff>:/library</iconset>
</property>
<widget class="QWidget" name="centralwidget" >
<layout class="QGridLayout" >
<layout class="QGridLayout" name="gridLayout" >
<item row="0" column="0" >
<layout class="QHBoxLayout" >
<property name="spacing" >
<number>6</number>
</property>
<property name="margin" >
<number>0</number>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_3" >
<item>
<widget class="LocationView" name="location_view" >
<property name="sizePolicy" >
@ -89,29 +83,47 @@
</widget>
</item>
<item>
<widget class="QLabel" name="vanity" >
<property name="sizePolicy" >
<sizepolicy vsizetype="Preferred" hsizetype="Preferred" >
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize" >
<size>
<width>16777215</width>
<height>90</height>
</size>
</property>
<property name="text" >
<string/>
</property>
<property name="textFormat" >
<enum>Qt::RichText</enum>
</property>
<property name="openExternalLinks" >
<bool>true</bool>
</property>
</widget>
<layout class="QVBoxLayout" name="verticalLayout_3" >
<item>
<widget class="QLabel" name="vanity" >
<property name="sizePolicy" >
<sizepolicy vsizetype="Preferred" hsizetype="Preferred" >
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize" >
<size>
<width>16777215</width>
<height>90</height>
</size>
</property>
<property name="text" >
<string/>
</property>
<property name="textFormat" >
<enum>Qt::RichText</enum>
</property>
<property name="openExternalLinks" >
<bool>true</bool>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2" >
<item>
<widget class="QLabel" name="label_2" >
<property name="text" >
<string>Output:</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="output_format" />
</item>
</layout>
</item>
</layout>
</item>
</layout>
</item>
@ -581,6 +593,42 @@
<string>Show book details</string>
</property>
</action>
<action name="action_books_by_same_author" >
<property name="icon" >
<iconset resource="images.qrc" >
<normaloff>:/images/user_profile.svg</normaloff>:/images/user_profile.svg</iconset>
</property>
<property name="text" >
<string>Books by same author</string>
</property>
</action>
<action name="action_books_in_this_series" >
<property name="icon" >
<iconset resource="images.qrc" >
<normaloff>:/images/books_in_series.svg</normaloff>:/images/books_in_series.svg</iconset>
</property>
<property name="text" >
<string>Books in this series</string>
</property>
</action>
<action name="action_books_by_this_publisher" >
<property name="icon" >
<iconset resource="images.qrc" >
<normaloff>:/images/publisher.png</normaloff>:/images/publisher.png</iconset>
</property>
<property name="text" >
<string>Books by this publisher</string>
</property>
</action>
<action name="action_books_with_the_same_tags" >
<property name="icon" >
<iconset resource="images.qrc" >
<normaloff>:/images/tags.svg</normaloff>:/images/tags.svg</iconset>
</property>
<property name="text" >
<string>Books with the same tags</string>
</property>
</action>
</widget>
<customwidgets>
<customwidget>

View File

@ -32,7 +32,10 @@ class BookInfoDisplay(QWidget):
self.setMaximumWidth(width)
QLabel.setPixmap(self, pixmap)
aspect_ratio = pixmap.width()/float(pixmap.height())
try:
aspect_ratio = pixmap.width()/float(pixmap.height())
except ZeroDivisionError:
aspect_ratio = 1
self.setMaximumWidth(int(aspect_ratio*self.HEIGHT))
def sizeHint(self):

View File

@ -473,9 +473,12 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
return current_page
def save_current_position(self):
pos = self.view.bookmark()
bookmark = '%d#%s'%(self.current_index, pos)
self.iterator.add_bookmark(('calibre_current_page_bookmark', bookmark))
try:
pos = self.view.bookmark()
bookmark = '%d#%s'%(self.current_index, pos)
self.iterator.add_bookmark(('calibre_current_page_bookmark', bookmark))
except:
traceback.print_exc()
def load_ebook(self, pathtoebook):
if self.iterator is not None:

View File

@ -47,6 +47,7 @@ entry_points = {
'fb2-meta = calibre.ebooks.metadata.fb2:main',
'any2lrf = calibre.ebooks.lrf.any.convert_from:main',
'any2epub = calibre.ebooks.epub.from_any:main',
'any2lit = calibre.ebooks.lit.from_any:main',
'lrf2lrs = calibre.ebooks.lrf.lrfparser:main',
'lrs2lrf = calibre.ebooks.lrf.lrs.convert_from:main',
'pdfreflow = calibre.ebooks.lrf.pdf.reflow:main',
@ -184,6 +185,7 @@ def setup_completion(fatal_errors):
from calibre.ebooks.odt.to_oeb import option_parser as odt2oeb
from calibre.ebooks.epub.from_feeds import option_parser as feeds2epub
from calibre.ebooks.epub.from_any import option_parser as any2epub
from calibre.ebooks.lit.from_any import option_parser as any2lit
from calibre.ebooks.epub.from_comic import option_parser as comic2epub
from calibre.gui2.main import option_parser as guiop
any_formats = ['epub', 'htm', 'html', 'xhtml', 'xhtm', 'rar', 'zip',
@ -207,7 +209,8 @@ def setup_completion(fatal_errors):
f.write(opts_and_exts('pdf2lrf', htmlop, ['pdf']))
f.write(opts_and_exts('any2lrf', htmlop, any_formats))
f.write(opts_and_exts('calibre', guiop, any_formats))
f.write(opts_and_exts('any2lrf', any2epub, any_formats))
f.write(opts_and_exts('any2epub', any2epub, any_formats))
f.write(opts_and_exts('any2lit', any2lit, any_formats))
f.write(opts_and_exts('lrf2lrs', lrf2lrsop, ['lrf']))
f.write(opts_and_exts('lrf-meta', metaop, ['lrf']))
f.write(opts_and_exts('rtf-meta', metaop, ['rtf']))
@ -423,7 +426,8 @@ def install_man_pages(fatal_errors):
raise
print 'Failed to install MAN pages as help2man is missing from your system'
break
raw = re.compile(r'^\.IP\s*^([A-Z :]+)$', re.MULTILINE).sub(r'.SS\n\1', p.stdout.read())
o = p.stdout.read()
raw = re.compile(r'^\.IP\s*^([A-Z :]+)$', re.MULTILINE).sub(r'.SS\n\1', o)
if not raw.strip():
print 'Unable to create MAN page for', prog
continue

View File

@ -567,23 +567,27 @@ class Job(object):
return 'ERROR'
def console_text(self):
ans = [u'Error in job: ']
ans = [u'Job: ']
if self.description:
ans[0] += self.description
if self.exception is not None:
header = unicode(self.exception.__class__.__name__) if \
hasattr(self.exception, '__class__') else u'Error'
header = u'**%s**'%header
header += u': '
try:
header += unicode(self.exception)
except:
header += unicode(repr(self.exception))
ans.append(header)
if self.traceback:
ans.append(u'**Traceback**:')
ans.extend(self.traceback.split('\n'))
if self.log:
if isinstance(self.log, str):
self.log = unicode(self.log, 'utf-8', 'replace')
ans.append(self.log)
header = unicode(self.exception.__class__.__name__) if \
hasattr(self.exception, '__class__') else u'Error'
header += u': '
try:
header += unicode(self.exception)
except:
header += unicode(repr(self.exception))
ans.append(header)
if self.traceback:
ans.append(self.traceback)
return (u'\n'.join(ans)).encode('utf-8')
def gui_text(self):
@ -611,7 +615,7 @@ class Job(object):
self.log = unicode(self.log, 'utf-8', 'replace')
ans.extend(self.log.split('\n'))
return '\n'.join(ans)
return '<br>'.join(ans)
class ParallelJob(Job):

View File

@ -240,7 +240,7 @@ If not, head over to <a href="http://calibre.kovidgoyal.net/wiki/Development#Tra
return 'download.html', data, None
LINUX_INSTALLER = '''
LINUX_INSTALLER = r'''
import sys, os, shutil, tarfile, subprocess, tempfile, urllib2, re, stat
MOBILEREAD='https://dev.mobileread.com/dist/kovid/calibre/'
@ -428,7 +428,7 @@ def main():
print 'Extracting files to %s ...'%destdir
extract_tarball(f, destdir)
pi = os.path.join(destdir, calibre_postinstall)
subprocess.call('pi', shell=True)
pi = os.path.join(destdir, 'calibre_postinstall')
subprocess.call(pi, shell=True)
return 0
'''
'''

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -175,7 +175,7 @@ mark_frame(void *context, uint32_t uncomp, uint32_t comp)
PyObject *rtable = self->rtable;
PyObject *entry = NULL;
entry = Py_BuildValue("(LL)", uncomp, comp);
entry = Py_BuildValue("(II)", uncomp, comp);
if (entry) {
PyList_Append(rtable, entry);
Py_DECREF(entry);

View File

@ -17,7 +17,8 @@ from PyQt4.Qt import QApplication, QFile, Qt, QPalette, QSize, QImage, QPainter,
from PyQt4.QtWebKit import QWebPage
from calibre import browser, __appname__, iswindows, LoggingInterface, strftime, __version__
from calibre import browser, __appname__, iswindows, LoggingInterface, \
strftime, __version__, preferred_encoding
from calibre.ebooks.BeautifulSoup import BeautifulSoup, NavigableString, CData, Tag
from calibre.ebooks.metadata.opf import OPFCreator
from calibre.ebooks.lrf import entity_to_unicode
@ -788,6 +789,7 @@ class BasicNewsRecipe(object, LoggingInterface):
html= u'''\
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<style type="text/css">
body {
background: white no-repeat fixed center center;
@ -817,12 +819,13 @@ class BasicNewsRecipe(object, LoggingInterface):
</div>
</body>
</html>
'''%dict(title=self.title, author=self.__author__,
date=time.strftime(self.timefmt),
'''%dict(title=self.title if isinstance(self.title, unicode) else self.title.decode(preferred_encoding, 'replace'),
author=self.__author__ if isinstance(self.__author__, unicode) else self.__author__.decode(preferred_encoding, 'replace'),
date=strftime(self.timefmt),
app=__appname__ +' '+__version__,
img=img)
f2 = tempfile.NamedTemporaryFile(suffix='cover.html')
f2.write(html)
f2.write(html.encode('utf-8'))
f2.flush()
page = QWebPage()
pal = page.palette()

View File

@ -17,7 +17,7 @@ recipe_modules = [
'blic', 'novosti', 'danas', 'vreme', 'times_online', 'the_scotsman',
'nytimes_sub', 'security_watch', 'cyberpresse', 'st_petersburg_times',
'clarin', 'financial_times', 'heise', 'le_monde', 'harpers', 'science_aas',
'science_news', 'the_nation', 'lrb', 'harpers_full'
'science_news', 'the_nation', 'lrb', 'harpers_full', 'liberation',
]
import re, imp, inspect, time, os

View File

@ -0,0 +1,38 @@
#!/usr/bin/env python
__license__ = 'GPL v3'
__copyright__ = '2008, Darko Miletic <darko.miletic at gmail.com>'
'''
liberation.fr
'''
from calibre.web.feeds.news import BasicNewsRecipe
class Liberation(BasicNewsRecipe):
title = u'Liberation'
__author__ = 'Darko Miletic'
description = 'News from France'
oldest_article = 7
max_articles_per_feed = 100
no_stylesheets = True
use_embedded_content = False
html2lrf_options = ['--base-font-size', '10']
keep_only_tags = [
dict(name='h1')
,dict(name='div', attrs={'class':'articleContent'})
,dict(name='div', attrs={'class':'entry'})
]
remove_tags = [
dict(name='p', attrs={'class':'clear'})
,dict(name='ul', attrs={'class':'floatLeft clear'})
,dict(name='div', attrs={'class':'clear floatRight'})
,dict(name='object')
]
feeds = [
(u'La une', u'http://www.liberation.fr/rss/laune')
,(u'Monde' , u'http://www.liberation.fr/rss/monde')
,(u'Sports', u'http://www.liberation.fr/rss/sports')
]

View File

@ -1,78 +1,34 @@
#!/usr/bin/env python
__license__ = 'GPL v3'
__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
__docformat__ = 'restructuredtext en'
import re
from calibre import strftime
from calibre.web.feeds.news import BasicNewsRecipe
from calibre.ebooks.BeautifulSoup import NavigableString
class NewYorker(BasicNewsRecipe):
title = 'The New Yorker'
__author__ = 'Kovid Goyal'
description = 'News and opinion'
remove_tags = [
dict(name='div', id=['printoptions', 'header', 'articleBottom']),
dict(name='div', attrs={'class':['utils', 'icons']})
]
def parse_index(self):
toc_pat = re.compile(r'/magazine/toc/\d+/\d+/\d+/toc_\d+')
soup = self.soup(self.browser.open('http://www.newyorker.com/').read())
a = soup.find('a', href=toc_pat)
if a is None:
raise Exception('Could not find the current issue of The New Yorker')
href = a['href']
href = 'http://www.newyorker.com'+href[href.index('/magazine'):]
soup = self.soup(self.browser.open(href).read())
img = soup.find(id='inThisIssuePhoto')
if img is not None:
self.cover_url = 'http://www.newyorker.com'+img['src']
alt = img.get('alt', None)
if alt:
self.timefmt = ' [%s]'%alt
features = soup.findAll(attrs={'class':re.compile('feature')})
category, sections, articles = None, [], []
for feature in features:
head = feature.find('img', alt=True, attrs={'class':'featurehed'})
if head is None:
continue
if articles:
sections.append((category, articles))
category, articles = head['alt'], []
if category in ('', 'AUDIO', 'VIDEO', 'BLOGS', 'GOINGS ON'):
continue
for a in feature.findAll('a', href=True):
href = 'http://www.newyorker.com'+a['href']+'?printable=true'
title, in_title, desc = '', True, ''
for tag in a.contents:
if getattr(tag, 'name', None) == 'br':
in_title = False
continue
if isinstance(tag, NavigableString):
text = unicode(tag)
if in_title:
title += text
else:
desc += text
if title and not 'Audio:' in title:
art = {
'title': title,
'desc': desc, 'content':'',
'url': href,
'date': strftime('%a, %d %b'),
}
articles.append(art)
# from IPython.Shell import IPShellEmbed
# ipshell = IPShellEmbed()
# ipshell()
# raise Exception()
return sections
#!/usr/bin/env python
__license__ = 'GPL v3'
__copyright__ = '2008, Darko Miletic <darko.miletic at gmail.com>'
'''
newyorker.com
'''
from calibre.web.feeds.news import BasicNewsRecipe
class NewYorker(BasicNewsRecipe):
title = u'The New Yorker'
__author__ = 'Darko Miletic'
description = 'Best of the US journalism'
oldest_article = 7
max_articles_per_feed = 100
no_stylesheets = False
use_embedded_content = False
keep_only_tags = [
dict(name='div' , attrs={'id':'printbody' })
]
remove_tags = [
dict(name='div' , attrs={'class':'utils' })
,dict(name='div' , attrs={'id':'bottomFeatures' })
,dict(name='div' , attrs={'id':'articleBottom' })
]
feeds = [
(u'The New Yorker', u'http://feeds.newyorker.com/services/rss/feeds/everything.xml')
]
def print_version(self, url):
return url + '?printable=true'

View File

@ -213,7 +213,7 @@ def upload_src_tarball():
check_call('scp dist/calibre-*.tar.gz divok:%s/'%DOWNLOADS)
def stage_one():
check_call('sudo rm -rf build', shell=True)
check_call('sudo rm -rf build src/calibre/plugins/*', shell=True)
os.mkdir('build')
shutil.rmtree('docs')
os.mkdir('docs')