From 015c313b8db907ec40b5a3bf264ead02f29912ce Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 24 Jul 2010 09:45:31 -0600 Subject: [PATCH 01/18] Don't crash if fail to decode python config file --- src/calibre/manual/customize.rst | 3 ++- src/calibre/utils/config.py | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/calibre/manual/customize.rst b/src/calibre/manual/customize.rst index 0852550c83..9f4d15cfd4 100644 --- a/src/calibre/manual/customize.rst +++ b/src/calibre/manual/customize.rst @@ -45,7 +45,8 @@ All static resources are stored in the resources sub-folder of the calibre insta from the calibre website it will be :file:`/opt/calibre/resources`. These paths can change depending on where you choose to install |app|. You should not change the files in this resources folder, as your changes will get overwritten the next time you update |app|. Instead, go to -:guilabel:`Preferences->Advanced` and click :guilabel:`Open calibre configuration directory`. In this configuration directory, create a sub-folder called resources and place the files you want to override in it. |app| will automatically use your custom file in preference to the builtin one the next time it is started. +:guilabel:`Preferences->Advanced` and click :guilabel:`Open calibre configuration directory`. In this configuration directory, create a sub-folder called resources and place the files you want to override in it. Place the files in the appropriate sub folders, for example place images in :file:`resources/images`, etc. +|app| will automatically use your custom file in preference to the builtin one the next time it is started. For example, if you wanted to change the icon for the :guilabel:`Remove books` action, you would first look in the builtin resources folder and see that the relevant file is :file:`resources/images/trash.svg`. Assuming you have an alternate icon in svg format called :file:`mytrash.svg` you would save it in the configuration directory as :file:`resources/images/trash.svg`. All the icons used by the calibre user interface are in :file:`resources/images` and its sub-folders. diff --git a/src/calibre/utils/config.py b/src/calibre/utils/config.py index 720f3ca977..c8cfbadac5 100644 --- a/src/calibre/utils/config.py +++ b/src/calibre/utils/config.py @@ -312,10 +312,10 @@ class OptionSet(object): def parse_string(self, src): options = {'cPickle':cPickle} - if not isinstance(src, unicode): - src = src.decode('utf-8') if src is not None: try: + if not isinstance(src, unicode): + src = src.decode('utf-8') exec src in options except: print 'Failed to parse options string:' From 4c67448ba54cdce6bce4e8360a83342379db6387 Mon Sep 17 00:00:00 2001 From: John Schember Date: Sat, 24 Jul 2010 15:58:04 -0400 Subject: [PATCH 02/18] Fix bug #6098: RTF output ignores italics. --- src/calibre/ebooks/rtf/rtfml.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/calibre/ebooks/rtf/rtfml.py b/src/calibre/ebooks/rtf/rtfml.py index d6b20402ce..c3d8cb38fc 100644 --- a/src/calibre/ebooks/rtf/rtfml.py +++ b/src/calibre/ebooks/rtf/rtfml.py @@ -119,10 +119,11 @@ class RTFMLizer(object): output += '{\\page } ' for item in self.oeb_book.spine: self.log.debug('Converting %s to RTF markup...' % item.href) - stylizer = Stylizer(item.data, item.href, self.oeb_book, self.opts, self.opts.output_profile) - content = unicode(etree.tostring(item.data.find(XHTML('body')), encoding=unicode)) + content = unicode(etree.tostring(item.data, encoding=unicode)) content = self.remove_newlines(content) - output += self.dump_text(etree.fromstring(content), stylizer) + content = etree.fromstring(content) + stylizer = Stylizer(content, item.href, self.oeb_book, self.opts, self.opts.output_profile) + output += self.dump_text(content.find(XHTML('body')), stylizer) output += self.footer() output = self.insert_images(output) output = self.clean_text(output) From a69641ac1b6f2499401397053ec75afb4a657a64 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 25 Jul 2010 09:01:40 -0600 Subject: [PATCH 03/18] Add a button to reset confirm dialogs to Preferences->General --- resources/images/mimetypes/mobi.svg | 648 ++- resources/images/mimetypes/rtf.svg | 4088 ++----------------- src/calibre/gui2/dialogs/config/__init__.py | 9 + src/calibre/gui2/dialogs/config/config.ui | 11 +- src/calibre/gui2/dialogs/confirm_delete.py | 10 +- 5 files changed, 924 insertions(+), 3842 deletions(-) diff --git a/resources/images/mimetypes/mobi.svg b/resources/images/mimetypes/mobi.svg index 1d290d2330..88d19f6c0d 100644 --- a/resources/images/mimetypes/mobi.svg +++ b/resources/images/mimetypes/mobi.svg @@ -1,8 +1,9 @@ + - + width="128" + height="128" + id="svg4486" + inkscape:version="0.47 r22583" + sodipodi:docname="epub.svg"> + id="metadata52"> image/svg+xml + + + id="defs4488"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + transform="matrix(1.0408163,0,0,0.6302428,-1.5714269,43.690218)" + id="g2478"> + + + + + + + + + + + mobi + + + diff --git a/resources/images/mimetypes/rtf.svg b/resources/images/mimetypes/rtf.svg index 485ac41350..c40103d283 100644 --- a/resources/images/mimetypes/rtf.svg +++ b/resources/images/mimetypes/rtf.svg @@ -1,3791 +1,387 @@ + width="48" + height="48" + id="svg2454"> + id="defs2456"> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + id="linearGradient5048"> + id="stop5050" + style="stop-color:#000000;stop-opacity:0" + offset="0" /> + id="stop5056" + style="stop-color:#000000;stop-opacity:1" + offset="0.5" /> + + gradientTransform="matrix(6.732488e-2,0,0,1.470022e-2,-0.3411391,37.040146)" /> + + + id="stop5062" + style="stop-color:#000000;stop-opacity:1" + offset="0" /> - - - - - - - + id="stop5064" + style="stop-color:#000000;stop-opacity:0" + offset="1" /> + + + + + + + + + + + + + + + + + + + + + + + + id="stop41" + style="stop-color:#000000;stop-opacity:1" + offset="0" /> + id="stop47" + style="stop-color:#000000;stop-opacity:1" + offset="0.18851049" /> + id="stop49" + style="stop-color:#000000;stop-opacity:0" + offset="0.25718147" /> - - - + id="stop51" + style="stop-color:#000000;stop-opacity:0" + offset="1" /> - - - - - - + r="139.55859" + id="radialGradient3721" + xlink:href="#XMLID_8_" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.3617022,0,0,-0.3907784,0.8510637,47.517004)" /> + + + + + + + + + + + + + + + + + + + + id="filter3212" + color-interpolation-filters="sRGB" + height="1.3286875" + width="1.2969251" + y="-0.16434373" + x="-0.14846256"> + id="feGaussianBlur3214" + stdDeviation="0.77391625" /> + + + + + + + + + + - - - - - image/svg+xml - - - - + + d="M 7.7378475,42.430102 C 7.7378475,42.430102 7.7378475,45.999958 7.7378475,45.999958 C 6.5513473,46.006658 4.869468,45.200135 4.869468,44.2148 C 4.869468,43.229467 6.1935126,42.430103 7.7378475,42.430102 L 7.7378475,42.430102 z" + id="path2881" + style="opacity:0.3;fill:url(#radialGradient3732);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible" /> + d="M 40.246148,42.430102 C 40.246148,42.430102 40.246148,45.999958 40.246148,45.999958 C 41.432648,46.006658 43.114528,45.200135 43.114528,44.2148 C 43.114528,43.229467 41.790483,42.430103 40.246148,42.430102 z" + id="path2883" + style="opacity:0.3;fill:url(#radialGradient3729);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible" /> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + d="M 6.4999609,0.49719839 C 14.520256,0.49719839 22.540551,0.49719839 30.560847,0.49719839 C 31.086081,2.4573981 36.693941,7.3488012 41.500042,10.123605 C 41.500042,21.583338 41.500042,33.04307 41.500042,44.502803 C 29.833348,44.502803 18.166655,44.502803 6.4999609,44.502803 C 6.4999609,29.834268 6.4999609,15.165733 6.4999609,0.49719839 L 6.4999609,0.49719839 z" + id="path4160" + style="fill:url(#radialGradient3724);fill-opacity:1;stroke:url(#linearGradient3726);stroke-width:0.99992186;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;display:inline" /> + d="M 18.912879,14 C 14.941321,19.117861 10.971558,24.242219 7,29.36008 C 7,34.571159 7,39.788922 7,45 C 16.433735,45 25.867403,45 35.301136,45 C 37.911168,41.636631 38.313471,41.123931 41,37.661986 C 41,32.936269 41,28.228706 41,23.48345 C 33.204587,20.134987 23.975066,16.174425 18.912879,14 z" + id="path3743" + style="opacity:0.1;fill:url(#linearGradient2721);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.91176528;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" /> + d="M 18.912879,13 C 14.941321,18.117861 10.971558,23.242219 7,28.36008 C 7,33.571159 7,38.788922 7,44 C 16.433735,44 25.867403,44 35.301136,44 C 37.911168,40.636631 38.313471,40.123931 41,36.661986 C 41,31.936269 41,27.228706 41,22.48345 C 33.204587,19.134987 23.975066,15.174425 18.912879,13 z" + id="path3697" + style="opacity:0.4;fill:url(#linearGradient2718);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.91176528;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" /> + + + + + + + + + diff --git a/src/calibre/gui2/dialogs/config/__init__.py b/src/calibre/gui2/dialogs/config/__init__.py index 5dcc4feb1a..78cac1b887 100644 --- a/src/calibre/gui2/dialogs/config/__init__.py +++ b/src/calibre/gui2/dialogs/config/__init__.py @@ -512,9 +512,18 @@ class ConfigDialog(ResizableDialog, Ui_Dialog): idx = i self.opt_toolbar_text.addItem(x[0], x[1]) self.opt_toolbar_text.setCurrentIndex(idx) + self.reset_confirmation_button.clicked.connect(self.reset_confirmation) self.category_view.setCurrentIndex(self.category_view.model().index_for_name(initial_category)) + def reset_confirmation(self): + from calibre.gui2 import dynamic + for key in dynamic.keys(): + if key.endswith('_again') and dynamic[key] is False: + dynamic[key] = True + info_dialog(self, _('Done'), + _('Confirmation dialogs have all been reset'), show=True) + def check_port_value(self, *args): port = self.port.value() if port < 1025: diff --git a/src/calibre/gui2/dialogs/config/config.ui b/src/calibre/gui2/dialogs/config/config.ui index df19aa2a26..9035e64bb1 100644 --- a/src/calibre/gui2/dialogs/config/config.ui +++ b/src/calibre/gui2/dialogs/config/config.ui @@ -89,8 +89,8 @@ 0 0 - 724 - 683 + 720 + 679 @@ -222,6 +222,13 @@ + + + + Reset all disabled &confirmation dialogs + + + diff --git a/src/calibre/gui2/dialogs/confirm_delete.py b/src/calibre/gui2/dialogs/confirm_delete.py index 42a1be41c0..1aabcb7b9e 100644 --- a/src/calibre/gui2/dialogs/confirm_delete.py +++ b/src/calibre/gui2/dialogs/confirm_delete.py @@ -5,7 +5,7 @@ __docformat__ = 'restructuredtext en' from calibre.gui2 import dynamic from calibre.gui2.dialogs.confirm_delete_ui import Ui_Dialog -from PyQt4.Qt import QDialog, SIGNAL, Qt +from PyQt4.Qt import QDialog, Qt, QPixmap, QIcon def _config_name(name): return name + '_again' @@ -18,15 +18,17 @@ class Dialog(QDialog, Ui_Dialog): self.msg.setText(msg) self.name = name - self.connect(self.again, SIGNAL('stateChanged(int)'), self.toggle) + self.again.stateChanged.connect(self.toggle) self.buttonBox.setFocus(Qt.OtherFocusReason) - def toggle(self, x): + def toggle(self, *args): dynamic[_config_name(self.name)] = self.again.isChecked() -def confirm(msg, name, parent=None): +def confirm(msg, name, parent=None, pixmap='dialog_warning.svg'): if not dynamic.get(_config_name(name), True): return True d = Dialog(msg, name, parent) + d.label.setPixmap(QPixmap(I(pixmap))) + d.setWindowIcon(QIcon(I(pixmap))) return d.exec_() == d.Accepted From 48df7c38bdb65d163d7ee307849480721e4e008b Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 25 Jul 2010 13:27:55 -0600 Subject: [PATCH 04/18] Switch to Sphinx 1.0 to generate User Manual --- setup/publish.py | 8 +- src/calibre/manual/Makefile | 2 +- src/calibre/manual/conf.py | 18 +- src/calibre/manual/custom.py | 13 +- src/calibre/manual/epub.py | 331 +++-------------------- src/calibre/manual/resources/calibre.css | 5 - src/calibre/manual/resources/logo.png | Bin 9261 -> 10566 bytes 7 files changed, 64 insertions(+), 313 deletions(-) delete mode 100644 src/calibre/manual/resources/calibre.css diff --git a/setup/publish.py b/setup/publish.py index 599c881be3..ba8a4992a7 100644 --- a/setup/publish.py +++ b/setup/publish.py @@ -73,11 +73,11 @@ class Manual(Command): os.makedirs(d) if not os.path.exists('.build'+os.sep+'html'): os.makedirs('.build'+os.sep+'html') - os.environ['__appname__']= __appname__ - os.environ['__version__']= __version__ - subprocess.check_call(['sphinx-build', '-b', 'custom', '-t', 'online', + os.environ['__appname__'] = __appname__ + os.environ['__version__'] = __version__ + subprocess.check_call(['sphinx-build', '-b', 'html', '-t', 'online', '-d', '.build/doctrees', '.', '.build/html']) - subprocess.check_call(['sphinx-build', '-b', 'epub', '-d', + subprocess.check_call(['sphinx-build', '-b', 'myepub', '-d', '.build/doctrees', '.', '.build/epub']) shutil.copyfile(self.j('.build', 'epub', 'calibre.epub'), self.j('.build', 'html', 'calibre.epub')) diff --git a/src/calibre/manual/Makefile b/src/calibre/manual/Makefile index c856e105fd..f991cb8777 100644 --- a/src/calibre/manual/Makefile +++ b/src/calibre/manual/Makefile @@ -37,7 +37,7 @@ qthelp: epub: mkdir -p .build/qthelp .build/doctrees - $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) .build/epub + $(SPHINXBUILD) -b myepub $(ALLSPHINXOPTS) .build/epub @echo @echo "Build finished." diff --git a/src/calibre/manual/conf.py b/src/calibre/manual/conf.py index 3866008f1f..fc8962bcfd 100644 --- a/src/calibre/manual/conf.py +++ b/src/calibre/manual/conf.py @@ -23,9 +23,11 @@ custom # General configuration # --------------------- +needs_sphinx = '1.0' + # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.addons.*') or your custom ones. -extensions = ['sphinx.ext.autodoc', 'custom'] +extensions = ['sphinx.ext.autodoc', 'custom', 'sphinx.ext.viewcode'] # Add any paths that contain templates here, relative to this directory. templates_path = ['templates'] @@ -36,6 +38,9 @@ source_suffix = '.rst' # The master toctree document. master_doc = 'index' +# The language +language = 'en' + # General substitutions. project = __appname__ copyright = '2008, Kovid Goyal' @@ -81,7 +86,6 @@ pygments_style = 'sphinx' # given in html_static_path. html_theme = 'default' html_theme_options = {'stickysidebar':'true', 'relbarbgcolor':'black'} -html_style = 'calibre.css' # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, @@ -100,8 +104,16 @@ html_use_smartypants = True html_title = 'calibre User Manual' html_short_title = 'Start' html_logo = 'resources/logo.png' + epub_author = 'Kovid Goyal' -epub_cover = 'resources/epub_cover.jpg' +epub_cover = 'epub_cover.jpg' +epub_publisher = 'Kovid Goyal' +epub_identifier = 'http://calibre-ebook.com/user_manual' +epub_scheme = 'url' +epub_uid = 'S54a88f8e9d42455e9c6db000e989225f' +epub_tocdepth = 4 +epub_tocdup = True +epub_pre_files = [('epub_titlepage.html', 'Cover')] # Custom sidebar templates, maps document names to template names. #html_sidebars = {} diff --git a/src/calibre/manual/custom.py b/src/calibre/manual/custom.py index 917b927086..b50853f6d5 100644 --- a/src/calibre/manual/custom.py +++ b/src/calibre/manual/custom.py @@ -9,9 +9,6 @@ sys.path.insert(0, os.path.abspath('../../')) sys.extensions_location = '../plugins' sys.resources_location = '../../../resources' -from sphinx.builders.html import StandaloneHTMLBuilder -from qthelp import QtHelpBuilder -from epub import EPUBHelpBuilder from sphinx.util import rpartition from sphinx.util.console import bold from sphinx.ext.autodoc import prepare_docstring @@ -20,12 +17,7 @@ from docutils import nodes sys.path.append(os.path.abspath('../../../')) from calibre.linux import entry_points - -class CustomBuilder(StandaloneHTMLBuilder): - name = 'custom' - -class CustomQtBuild(QtHelpBuilder): - name = 'customqt' +from epub import EPUBHelpBuilder def substitute(app, doctree): pass @@ -305,9 +297,6 @@ def auto_member(dirname, arguments, options, content, lineno, def setup(app): app.add_config_value('epub_cover', None, False) - app.add_config_value('epub_author', '', False) - app.add_builder(CustomBuilder) - app.add_builder(CustomQtBuild) app.add_builder(EPUBHelpBuilder) app.add_directive('automember', auto_member, 1, (1, 0, 1)) app.connect('doctree-read', substitute) diff --git a/src/calibre/manual/epub.py b/src/calibre/manual/epub.py index d54eb99a8d..a162303b09 100644 --- a/src/calibre/manual/epub.py +++ b/src/calibre/manual/epub.py @@ -6,298 +6,53 @@ __license__ = 'GPL v3' __copyright__ = '2009, Kovid Goyal ' __docformat__ = 'restructuredtext en' -import os, mimetypes, uuid, shutil -from datetime import datetime -from docutils import nodes -from xml.sax.saxutils import escape, quoteattr -from urlparse import urldefrag -from zipfile import ZipFile, ZIP_STORED, ZipInfo +import os, time -from sphinx import addnodes -from sphinx.builders.html import StandaloneHTMLBuilder +from sphinx.builders.epub import EpubBuilder -NCX = '''\ - - - - - - - - - - Table of Contents - - {navpoints} - - -''' +class EPUBHelpBuilder(EpubBuilder): + name = 'myepub' -OPF = '''\ - - - - {title} - {author} - Sphinx - {uid} - {date} - - - - - {manifest} - - - {spine} - - - {guide} - - -''' + def add_cover(self, outdir, cover_fname): + href = '_static/'+cover_fname + opf = os.path.join(self.outdir, 'content.opf') -CONTAINER='''\ - - - - - - -''' - -SVG_TEMPLATE = '''\ - - - - - Cover - - - - - - - - -''' - -class TOC(list): - - def __init__(self, title=None, href=None): - list.__init__(self) - self.title, self.href = title, href - - def create_child(self, title, href): - self.append(TOC(title, href)) - return self[-1] - - def depth(self): - try: - return max(node.depth() for node in self)+1 - except ValueError: - return 1 + cover = '''\ + + + + + Cover + + + + + + + + + '''%href + self.files.append('epub_titlepage.html') + open(os.path.join(outdir, self.files[-1]), 'wb').write(cover) -class EPUBHelpBuilder(StandaloneHTMLBuilder): - """ - Builder that also outputs Qt help project, contents and index files. - """ - name = 'epub' - - # don't copy the reST source - copysource = False - - supported_image_types = ['image/svg+xml', 'image/png', 'image/gif', - 'image/jpeg'] - - # don't add links - add_permalinks = False - # don't add sidebar etc. - embedded = True - - def init(self): - StandaloneHTMLBuilder.init(self) - self.out_suffix = '.html' - self.link_suffix = '.html' - self.html_outdir = self.outdir = os.path.join(self.outdir, 'src') - self.conf = self.config - - def finish(self): - StandaloneHTMLBuilder.finish(self) - self.create_titlepage() - self.outdir = os.path.dirname(self.outdir) - cwd = os.getcwd() - os.chdir(self.html_outdir) - try: - self.generate_manifest() - self.generate_toc() - self.render_opf() - self.render_epub() - finally: - os.chdir(cwd) - - def render_epub(self): - container = CONTAINER.format('content.opf') - path = os.path.abspath('..'+os.sep+self.conf.project+'.epub') - zf = ZipFile(path, 'w') - zi = ZipInfo('mimetype') - zi.compress_type = ZIP_STORED - zf.writestr(zi, 'application/epub+zip') - zf.writestr('META-INF/container.xml', container) - for url in self.manifest: - fp = os.path.join(self.html_outdir, *url.split('/')) - zf.write(fp, url) - zf.close() - self.info('EPUB created at: '+path) - - - def render_opf(self): - manifest = [] - for href in self.manifest: - mt, id = self.manifest[href] - manifest.append(' '*8 + ''%\ - tuple(map(quoteattr, (id, href, mt)))) - manifest = '\n'.join(manifest) - spine = [' '*8+''%quoteattr(x) for x in self.spine] - spine = '\n'.join(spine) - guide = '' - - opf = OPF.format(title=escape(self.conf.html_title), - author=escape(self.conf.epub_author), uid=str(uuid.uuid4()), - date=datetime.now().isoformat(), manifest=manifest, spine=spine, - guide=guide) - open('content.opf', 'wb').write(opf) - self.manifest['content.opf'] = ('application/oebps-package+xml', 'opf') - - def create_titlepage(self): - self.cover_image_url = None - if self.conf.epub_cover: - img = '_static/'+os.path.basename(self.conf.epub_cover) - shutil.copyfile(self.conf.epub_cover, os.path.join(self.html_outdir, - *img.split('/'))) - self.cover_image_url = img - tp = SVG_TEMPLATE%img.split('/')[-1] - open(os.path.join(self.html_outdir, '_static', 'titlepage.html'), - 'wb').write(tp) - - def generate_manifest(self): - self.manifest = {} - id = 1 - for dirpath, dirnames, filenames in os.walk('.'): - for fname in filenames: - if fname == '.buildinfo': - continue - fpath = os.path.abspath(os.path.join(dirpath, fname)) - url = os.path.relpath(fpath).replace(os.sep, '/') - self.manifest[url] = mimetypes.guess_type(url, False)[0] - if self.manifest[url] is None: - self.warn('Unknown mimetype for: ' + url) - self.manifest[url] = 'application/octet-stream' - if self.manifest[url] == 'text/html': - self.manifest[url] = 'application/xhtml+xml' - if self.cover_image_url and url.endswith(self.cover_image_url): - id_ = 'cover' - else: - id_ = 'id'+str(id) - id += 1 - self.manifest[url] = (self.manifest[url], id_) - - def isdocnode(self, node): - if not isinstance(node, nodes.list_item): - return False - if len(node.children) != 2: - return False - if not isinstance(node.children[0], addnodes.compact_paragraph): - return False - if not isinstance(node.children[0][0], nodes.reference): - return False - if not isinstance(node.children[1], nodes.bullet_list): - return False - return True - - def generate_toc(self): - tocdoc = self.env.get_and_resolve_doctree(self.config.master_doc, self, - prune_toctrees=False) - istoctree = lambda node: ( - isinstance(node, addnodes.compact_paragraph) - and node.has_key('toctree')) - toc = TOC() - for node in tocdoc.traverse(istoctree): - self.extend_toc(toc, node) - self._parts = [] - self._po = 0 - self._po_map = {} - self.spine_map = {} - self.spine = [] - self.render_toc(toc) - navpoints = '\n'.join(self._parts).strip() - ncx = NCX.format(uid=str(uuid.uuid4()), depth=toc.depth(), - navpoints=navpoints) - open('toc.ncx', 'wb').write(ncx) - self.manifest['toc.ncx'] = ('application/x-dtbncx+xml', 'ncx') - self.spine.insert(0, self.manifest[self.conf.master_doc+'.html'][1]) - if self.conf.epub_cover: - self.spine.insert(0, self.manifest['_static/titlepage.html'][1]) - - def add_to_spine(self, href): - href = urldefrag(href)[0] - if href not in self.spine_map: - for url in self.manifest: - if url == href: - self.spine_map[href]= self.manifest[url][1] - self.spine.append(self.spine_map[href]) - - def render_toc(self, toc, level=2): - for child in toc: - if child.title and child.href: - href = child.href - self.add_to_spine(href) - title = escape(child.title) - if isinstance(title, unicode): - title = title.encode('utf-8') - if child.href in self._po_map: - po = self._po_map[child.href] - else: - self._po += 1 - po = self._po - self._parts.append(' '*(level*4)+ - ''%(uuid.uuid4(), - po)) - self._parts.append(' '*((level+1)*4)+ - '%s'%title) - self._parts.append(' '*((level+1)*4)+ - ''%quoteattr(href)) - self.render_toc(child, level+1) - self._parts.append(' '*(level*4)+'') - - - - - def extend_toc(self, toc, node): - if self.isdocnode(node): - refnode = node.children[0][0] - parent = toc.create_child(refnode.astext(), refnode['refuri']) - for subnode in node.children[1]: - self.extend_toc(parent, subnode) - elif isinstance(node, (nodes.list_item, nodes.bullet_list, - addnodes.compact_paragraph)): - for subnode in node: - self.extend_toc(toc, subnode) - elif isinstance(node, nodes.reference): - parent = toc.create_child(node.astext(), node['refuri']) - + raw = open(opf, 'rb').read() + raw = raw.replace('', + ('\n' + '%s\n') % + (href.replace('/', '_'), time.strftime('%Y-%m-%d'))) + raw = raw.replace('', + ('\n').\ + format('epub_titlepage.html')) + open(opf, 'wb').write(raw) + def build_epub(self, outdir, *args, **kwargs): + if self.config.epub_cover: + self.add_cover(outdir, self.config.epub_cover) + EpubBuilder.build_epub(self, outdir, *args, **kwargs) diff --git a/src/calibre/manual/resources/calibre.css b/src/calibre/manual/resources/calibre.css deleted file mode 100644 index f815378df4..0000000000 --- a/src/calibre/manual/resources/calibre.css +++ /dev/null @@ -1,5 +0,0 @@ - -@import url("default.css"); - -table.docutils td, table.docutils th { padding: 1em; border-bottom: 0; } - diff --git a/src/calibre/manual/resources/logo.png b/src/calibre/manual/resources/logo.png index 42b9568956496f00a3b707126d35529af745adae..158bc9d1b5113db517136636ec2d58dcb29e446a 100644 GIT binary patch literal 10566 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 R-~ST;6l7GSt0j$p{vRq1lnnp? literal 9261 zcmV+|B+}c7P)ht(u001La zNkl@GuWaisn)vK+$tGm^!)RI~&l8itIVWR~skO4Coo6SfV%fO6h zFg|m}hJ(j=w=uTIAdlIHO#{Zp0*OrmAp{aqqovjAy}P=*_Vv}jq14^;|Zc=B7Ham~m6*uCN^zX9B;wSMQ*kA24_GY`6pwhtXFm&u>txCqDV9%Wt^xN;RI`i#YvpqRFq&oO*~-)q$f~8}=u>Q+Sd$R+2;8b`}qcZ#`|}r%pD%XRTea zaq|U7p7^H!g|9t+$_hO&Am>JQ*A~Qm4<1YooQa3x%MeKt&7a-zuHXB^e5rXa-F^R!*3@Hs`8x}|_s;3?uRb@k`pK^^ zcOE&^Rn2BkpHpk`f-6V3@$%y6%Hs0N_Mfo;`+@elX1#TQ2tbF4eRy(m?xtI=Qnl|M z5%K<$jmLm!Zf@?fnw+m5p6X0D+R^8Z9XWE|YhQXv_0*z~^`(W2U;PW$z4FS-_TJWL zG+w#5(wYD^-u&Vh-95f(2Z;jiFZ=73L*^k7lPtLRtd~IKQ%O&Ro z1Ly7@;peV@F^!JA<^?}@{Tn`X$6x*IJAU)Ft*?98)x*DZ!_FO7?%>ANm6cbWnCT7! zjUBtsd-1DpJa_PG4<5Bop9v?zDD<9xzS|IW=D71)^@TXGGlM04(ao2I6&cj{qtCW` zfA+ujpFHs7zRI+&6aAL`}WPsEB0>P zarO8NFTeHor~l#upLy``zAtUPn^CTyM7|)+qTe)Dg zmggTITy4e1HY<6t>ldFlQ2hFSj}QLy(ZiA9{v*@L+{XOEc{>N@xa*MSI(|ZU4@%9<}#B9eqIwMt<`Z+jq>g2Kb#j_8sa) z$=$$=26E52;{3PVa^n@Wj{Or7!3Y0&mIt1gnaT_gG9Q-NXOjIt%6z?XVz&Fmzx;=# zH@t7tmz38!MN&V;V+U8b>$?praa?z4NnU-;H55j7@P=27bKa%b?!Ds!@56MaP<~Os zVgc}x9@bhME&v9V_BFD=sp+N#y4ialfAn~A!?hPzf*{a2Uq#K(7U;$rVE&1tb6E^31DCs(l`coX6 z&hdfIA8KU=_BFy~#z$EvLx^@vJ7cx*f#E0Lp zquB1UC+u_xyx5HlLTmPMtT=$N7GK1;U@MM#KN`7QX#`v`(egP)FOJ;62 z;01*%Z@zY?nwxxr<)#g_=LMdh$HXnN{%in}l@8|M!9%>|wJ%mLyJ%Yd?Z@tQ-#OTu z1iaC~{7cRoQG*X0X^rahcaHq*=3z;1S^ z5!iFXMQUrsJVo3L7}>Ow;n;~YhdXql99|NM&0BN$#emofe5px9fK&wvIW03?306G2xF7@(o}O^Om(0N z%vG{@ZxTo6^WQufKl{>;dU@AG`USR+#$)BY*!XDETJrp#yL3c~+&HRG!_O7C>w!c3_m4cjnt`TY ze%+1@e|Y;PB*t>&^eW?{9+d)Iao#5NvAd6r4VCr4w(*VBMmO^83!~xt_MJ9ch61(H za6I?Q3;3Jr@`%>{$ff7z_~=&__{_a0>HyE(J2qGt*uk?dD6nnATy0`@RcHN+rlmM+vB+dY*Ga#0- z|3~XIpamp}<^JFK)Z)g^?PFlDJgWxFIq~f$7W%@~0pibp^Mrl!=yKUPCr(VSCg*IZ zc*VRXik!2~byhnj{%;>S6={9in_uo2VDf`sombyH7!9s8ddcN`h5|v458X2pKYD1s z4{Y?kBlV?Q|Ktf@N}(3&omM8y%fQYTUOtr1)z0NTpO|%FIPZKL zEM%Hh&jxK0sLeNv96Xihi{CyIuQq%0SwEV2u>Jd!445P{G|ArAcfTML-u$`<6=!{z z&J3#q;uH6t+e$vu6-p|^R>z?UkpJE-T{fSIpX#I5{lHD_%y`$fU)l8@6vwj5Dx+lUY z3A1+k{p+oBV^1%&oe6tw?F9-?o}I*XvYXe(eVM(mKT?p!=Dz5dbq4i~Xs$CRmyMjC zJ=4!C`fc^Wk20h81VJ!V8>;072CK-Npl7=*x0b0-HEG`_ob=qMmU+7ZGl~Ga*8tbkGb_*feI2XAeG-!oX7*A!s*3V`dnH%&+yif0D z_Ts*O_dyWZz0o?~ZUb9_T=2Z-TywR*W5*8iex6R;VKJUzx_6phdxcPVbdJO_V?iH) zFf+EZ4v2R4{`D@`%4}ExTKd!y!9rb7t#lZ!ST>B+@|$;EwtMfhp1phHmK{hxM-s&} z+ilKF&vWeLDNY_gNoV17+chSR;Osjy!;2Y1`g86+0FCVBK53>_%&zw--Pe}BzXyXO z1L6fQyOxq)#&jL+uu0gi6L%WqjX=g#I!6{E6+E@h#rk>@Wq>bd@9Q(MKbc#}TxDJt zYMZ?$Pc_-T$ud&vamhIayv>`r;(6Dx`=ZP7awVKl^b*1TnGUT3OUyhyL3#Q}vHrC` zxU{!CF_f_(ml@k1k-oxrGsD!n(cc8zm3!OQ;N$N*T)&K2z!2YBRootv%< z$a@W*dqKdbA3sRD)utCl;2DH)80(np8jeN*!KN+LijMXJ#cpp+1e=+`eX;EObG%M4 zJ3v8e^IjWx_itOx13bpQg4^V|d zzVzF_{ht^gFH@|1iF@u5>RrG4?#gRl`$`^s=w00Y=Ev=mN1u}Fss?4hM65%)vd)ki zq9lqGolj+RvATFT8pw?43wK}P-8w*)*SSbv8~S0^=w{C%isO9gktxpIQlOe|@Ve&* zy!#9P$l=;^D2!L}J&(vZjxQT(d4)H3kkv;%Pg!=oI=qauWPNVAT&h$`#e&w#S5heF z941LzFASqryVvdX!c}0p)9qH5RwmfKbBxNs3`4_M?e#yemazBSKtVOE68}?b+zsL5U)16q5A32B5?K{m)R~0ya zbHp|0N8EhqFrU8X(*!rX9r9JAa18i@pfkttgMZ4$?|3h={UZIIcf9`Qd!EYij(5DB zJ$v_1EBWZ80XD>$n6R_T;?!X#4t|d(9^cQSM`IqHh*(&BnkZSJRPGV>oP6@h2eIN} zN~KlC$332!w#1>u3WqN>QiBl*D2vQH3L`m4Cu04OslLT({4@_eyn*1_TTwbd3P(75oIS-^{Q0j?+IT*rZ1EhcGKoVpb0zy#ijYN>C^wg(Qj$4uzCZ zt!iebyR57<0ms-T4;e#eCc$8E&f%dkF2Op3#ZcLtCtu5t0E7HsdQAqv7T_}A1;C54 z&+@+jL297`ba@z=tdN8){Lvk}|2xyHcIv$C<=Yt=S?2avmUuNAdHl>ijxRom2x`3W zy>I9I;6AzwWoDOPVR41!(+|@)wm>;J4&giqg^k;|q=(Fn;6w%GIfM{&+7^LC3Wx6t zmKH7bddkj$nt-08I~ifD#R`iS8s{7ai^Gx|3MdZeEA53&Av0_@a1pS*Qm&j+Emd}w zf>Nz0^Pa0G;@HZBTXoI119xOBaez$$sEpvb2BNo$h?bGV+xXO>1y26m6mPj@GZ$?Q z83|7C(kpjU9NCSRuhN+LCiUs>qjTf@@2?%CT6DN@5!1Ao?motDL#-I3J5YE$(c7=~)>&`Yf;-?V~GW}z)?mxygmkv@Y`uLtE zu^KHjLiXoLeGy(e!q8$81rD@Muhr`YYZR3MK{@ZyolA&&23xRr!b2cQY=V^*k%KK4 zZj@J6uDz>2V&~W;i4zObBtgNy;0m5wKim}VNv-zbhq)TVrH5ZT?T^b}; zNKjD|ui1rg1{ZY^`7(CP72Msd^VJVcaBi`|wh_fpC18Geo~KT%FfmnU+n7(KpeYwT zjBzB^o>iG2Ha!ENYcPqxSkPX2=gf3OVmyjv!KU#X$EMqajRadYpaPWBDF7I(4Y~Y= zy}Yq?I~VW14A12ta#&+9ZHsNiM3X(-l%-fIv0ZPc;N^F8;_f!!P{x2*BLpfa5(JXK zbnzQqL^sAY7jV56I1r^_Y;^<2daImVoWo8mVq1pJatA4)lvmU$emVv0C6tu|l^W$7 zI45u}i$fV2z0eRPV5}nu(tBrTW0F|mYs=t>COlyXS7OY#1>ulRlDHU)MQKHYRSw+u zIHOA=3|%&cw=DoIFtagcGNSib2Nzk?uwpzIXM1Vu#se#d&jk)=07wl2;raOG99ksg znjxx_N{wqT6xM zVQ7gX2|}P!FEO(-v5X=qgh&_}^RN$FdJ8co&f>q*M7Dzw1`U`>%;AZHoOkkE{L_lu zwjxeJd?F-15n`rdWJw`%g2CJ%=MSE%53C#>Uza>doer@?dO7^c5W!G{Y$PDSN)7E5 zT-3qFJ&ckV=}^`nq)ML?P|QgJU!s&m$~4!o)`3h%T-bE@?Yi_N3N1+tQaSuU0>txk zvDfK3mCHGXMl{3_E}K*cwL?1(Yb-H_z&T_dP7hAHxkskOP@_gN6eCL#vzTC45=2gd z6yO9okz-SSBfw79W{FaZjHgm-JgfG78sX)jSVJif z>*XL{f%Y(d+#u(=__l`^by+(01UUqoM|?I8<*1f?0xw4NQWxk?!TNJAo00+qB(cR> z0RnEiWn@I*DNW~8Ow=)$qD6ZWVFWP-D=k_H7;y4vc*6B;j{+rR z;t)BBPy&}YoJ~&*j0U5O=7!6Yt?AKB2vY{&3^CJFG>nU#lpOc<`>fP zV5KT4m6U5PBqD4W%%H_75LS`c7^4kBI)aKPkHp8_vRS64h9GfSxl=%#j=nPvXQAYk z*iaaiz=goK04dX+8C(Ve*5X`}c2sIakVob!$l@q^a1+7sRtjV1P#W2SP#%RqGF#Dag(6f3Jd(O0T1_)T z3@E2aOwwOQr#zTv+RllocorutE=idu`Wj$u3P1um5wNFvE~LBPywnAp1Do~IN^l9n zI)oslj+3A~gpepdK;;F#GWY_d&}apPK&AAQ5@Q{S&9D^Na9f8otks4f342y2Nt!JL z0ptr>0QKdi1Y;znlA~7hnK+FL7h_>ef-@HFJbI>wQR&2}GFGBf>~gv{iOCz3aaiMU z8mzQn1TL`%BftpGubzvq{PD;{1t6BG3-q1Tvc_G5I6HlIAOub-r1nu>fc65MkZ1`~ zrlof4(%7FEOJW_yK#~cfOL0gw0BoFCYmCDo2m(Q&s1(p>G-H<}0?)JL^1|YXI%i^% z#A1y_IS*?b##pQ~6l*#1n*xsXj?<-wwFYM_PNzADbrxsLStg9-Mk#ptF<=a^DF8Mz z9(&e!ti|;ja`lwvJbxy)eOUrEYX;oJ|+Y8>F{v=D5ZNGWbC z#)^~${WA;2>zYW6bxE&hkwT&rcuFXs(`=fk(Ev}w;GhIB;j%Gt$6#%O6apnQiA!?< z=|Oozkrf(DCDSA#AxR9@S&RmQ!5EXeh6QC6JImVuHUqv~OVR8PQ@wxf#~dyl%~UCD zIL0G|9ZWIK&fWeda31KRpDGMylIyM)^*-#kW4s3o#00`&MLLjAB2SQes6F>1$ z>nw4%XAn{%mFw38Y?83jY^L?XeBR>uA|YfYT24qzDk@qk92SGYVhPF~I;WTlXK0uP z)+Hoyf|U-d9LAYc7m}2nLw=2Il`Umpl{*OBnsnVtTXs!O>Of4E39%XJdtuENif!6H5gJC=qFP?9-E8f0GB^A z7XUn+xt}muP35C>KT8(A`F0CQtafdA8J(s_n2mUX(-tQ(4anR83%jd3DSG)`&&&lxDuUt%ec-2g(gNKm z%;LI$d|pDjmcP2Z9Gh+zayb|u&q=_BbEX|N(r_jKDb(5Wn8i^$rwZU^qd7XRP2$q| zgOh1WZk#u-eU;H1HN4r5at z=(C`r%T)bJ`I%_|u3yhvQ^I}}_}7m(icFeINC;hV` zCW1dU^8ZNuk~qnV1j3QaOO%#AkaRjWYBVhfL8U4Hp@<2?xfo*&4vP|Ay4>qhwGS#D zmEof8(v4=~ITDv(;?!l3;7C(pG7V5xaenQ*pYBIaKh}N$T8cFdaO?7be+@Y>actAt zp@VWo;d{~pqBwTlh50n?&E)_@0ciH7le3{r$+YH`xD*htD5-9)NI*=-GsHw#o2q_V zA`@wK$fPq~X9OE^qt6fkmzGq7kSPdf3H4LCK^(^xXH)Qe52QT%gQk^*g>*;Tz@Q*s zNVTCimn3nR7E`s*2qDr*5eDJG;5iiu^p1xoO-L^-$49BbXEiDt0D)jbVGNxrDkv$k z(Ok=8&XRd;fPa9)QfsG_5F(opSQFxs5b+~j;D>}UiNXY96SQ(@4N{){amMWIOh}Rh zluJ-5>Rh)Iuf`1%cG_Ltsx;_0fpG?31^6mJNkw5O5J2;EG~=(@MzO3an2rHyQF)Eb zNmS9pD|?WGF*SxxjHMAKs7B0u)3DqzZgQ@ zrqZdjOYrg|>2o6ep^5TKNS#M$jS&4UJj9(&i$>!pLqj?8`8-O={+j;G!hFkhIzp5y zgA9)brG=i!3ZO{jc7CiL4ME&5cPPqw=C+#t%YqU<)Ly4UG`9U zh4dvt3oMR;ETAopW;dbRh|pS52^1qG&HBw1HY;@Id`Zz)_`+fl3{)zv=-G7SEhw*m z$_*lIhyqz(`&z&XX*&fC-ltQC0PAh}!tAw+*Qb-vXyi7|m!9jLNtQ#&|#pq|(k zJ+G0g4&{fb<_0M10VL_X00*U!0>MB~=+=AjX*r+^^>wb^GxxDNF#`f%-T*{5ryfI3;M7HjR5E_?2 zzFMF%TJjg_;oL-WW%GzjPpQ&fyZn;6Ozc@-?g+#mO?gSP^$^T9jlhN@y+78l00PiR?Gsjw5ZGjX^m# zo2S(@Xs!Aiof<1ECR$!9`y;~@a`^xtaF#Q&p1U{~R%tJXwC1|hPq*kS^+-a4Rz6aQ zBCs%P=3T4U6dl*&%;F@EFYM>(rNhj37NUjDQrM0=ZR3(9omY-(X9;_1l9g_Yq}Rck z2xpR%`$8a;LMRXI2dI3Bpio5`NikpG^peNKgH2YN3oO;Uw7M~Ik}^aJfv+W%qR+@c zz{a66y-@UXrQXVl5%2$-ldP=tjFL(<8lgv8uAU2f)W34~a_I}7U*y13OOWmbnr)vB z+4qsdocaEYU7c#pgw1e?bT42Js0hI&fSc~lbU%LdGlhfm$K3Jy$;ocqnHo zS_bN3aN};Lw6^WWLd!(qk~PVyvnF)TC0SZ0gpgVY=?SHZO8dh)R~)Eq*rk2fWB$ax zdaGv+TW6P9+hX6%)Ldtjt^if7)L=2EFKYMV*~Hin0&fSt13U4N4gFr$6-_9QY70QZw_*6#nFp}!sXJkn0T&N);S9#{|A|> zM#g%PnI!-K03~!qSaf4@Wnpw>Eo5PIWdJfTFgPtRI4v+VR539+FflqbG%YYUIxsMD zGJ9MA001R)MObugZ)9m^c`amNbY%cCFfceRFf}bQI8-q>Ix;ajFfc7JH##sd#!5$+ P00000NkvXXu0mjf2#Lp* From 3c4f91204b703e14a6e1f8fe18a96a9ec9ec5d25 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 25 Jul 2010 14:53:23 -0600 Subject: [PATCH 05/18] SONY driver: Don't abort in presence of corrupted thumbnails --- src/calibre/devices/prs505/sony_cache.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/calibre/devices/prs505/sony_cache.py b/src/calibre/devices/prs505/sony_cache.py index 3ac35df9b2..46ccf1f3d2 100644 --- a/src/calibre/devices/prs505/sony_cache.py +++ b/src/calibre/devices/prs505/sony_cache.py @@ -328,7 +328,10 @@ class XMLCache(object): 'descendant::*[local-name()="jpeg"]|' 'descendant::*[local-name()="png"]'): if img.text: - raw = b64decode(img.text.strip()) + try: + raw = b64decode(img.text.strip()) + except: + continue book.thumbnail = raw break break From 5f70956b868bd4472afbb9354e6e1dabc7fd2cd2 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 25 Jul 2010 15:43:24 -0600 Subject: [PATCH 06/18] Improved API documentation in User Manual --- src/calibre/customize/__init__.py | 26 +--- src/calibre/customize/conversion.py | 30 ++--- src/calibre/devices/interface.py | 187 ++++++++++++++++----------- src/calibre/devices/usbms/device.py | 24 +++- src/calibre/devices/usbms/driver.py | 15 ++- src/calibre/ebooks/metadata/fetch.py | 16 +++ src/calibre/manual/Makefile | 2 +- src/calibre/manual/custom.py | 58 +-------- src/calibre/manual/news_recipe.rst | 143 +------------------- src/calibre/manual/plugins.rst | 183 ++++++++++++++------------ src/calibre/web/feeds/news.py | 17 +-- 11 files changed, 290 insertions(+), 411 deletions(-) diff --git a/src/calibre/customize/__init__.py b/src/calibre/customize/__init__.py index 9a018231ef..1348da5e5a 100644 --- a/src/calibre/customize/__init__.py +++ b/src/calibre/customize/__init__.py @@ -262,31 +262,21 @@ class CatalogPlugin(Plugin): type = _('Catalog generator') - #: CLI parser options specific to this plugin, declared as namedtuple Option + #: CLI parser options specific to this plugin, declared as namedtuple Option:: #: - #: from collections import namedtuple - #: Option = namedtuple('Option', 'option, default, dest, help') - #: cli_options = [Option('--catalog-title', + #: from collections import namedtuple + #: Option = namedtuple('Option', 'option, default, dest, help') + #: cli_options = [Option('--catalog-title', #: default = 'My Catalog', #: dest = 'catalog_title', #: help = (_('Title of generated catalog. \nDefault:') + " '" + #: '%default' + "'"))] - #: cli_options parsed in library.cli:catalog_option_parser() - + #: cli_options parsed in library.cli:catalog_option_parser() cli_options = [] def search_sort_db(self, db, opts): - ''' - # Don't add Catalogs to the generated Catalogs - cat = _('Catalog') - if opts.search_text: - opts.search_text += ' not tag:'+cat - else: - opts.search_text = 'not tag:'+cat - ''' - db.search(opts.search_text) if opts.sort_by: @@ -349,8 +339,7 @@ class CatalogPlugin(Plugin): It should generate the catalog in the format specified in file_types, returning the absolute path to the generated catalog file. If an error is encountered - it should raise an Exception and return None. The default - implementation simply returns None. + it should raise an Exception. The generated catalog file should be created with the :meth:`temporary_file` method. @@ -358,9 +347,6 @@ class CatalogPlugin(Plugin): :param path_to_output: Absolute path to the generated catalog file. :param opts: A dictionary of keyword arguments :param db: A LibraryDatabase2 object - - :return: None - ''' # Default implementation does nothing raise NotImplementedError('CatalogPlugin.generate_catalog() default ' diff --git a/src/calibre/customize/conversion.py b/src/calibre/customize/conversion.py index 6fd3fb9932..e98f34273f 100644 --- a/src/calibre/customize/conversion.py +++ b/src/calibre/customize/conversion.py @@ -28,7 +28,7 @@ class ConversionOption(object): def validate_parameters(self): ''' - Validate the parameters passed to :method:`__init__`. + Validate the parameters passed to :meth:`__init__`. ''' if re.match(r'[a-zA-Z_]([a-zA-Z0-9_])*', self.name) is None: raise ValueError(self.name + ' is not a valid Python identifier') @@ -96,7 +96,7 @@ class InputFormatPlugin(Plugin): InputFormatPlugins are responsible for converting a document into HTML+OPF+CSS+etc. The results of the conversion *must* be encoded in UTF-8. - The main action happens in :method:`convert`. + The main action happens in :meth:`convert`. ''' type = _('Conversion Input') @@ -109,7 +109,7 @@ class InputFormatPlugin(Plugin): #: If True, this input plugin generates a collection of images, #: one per HTML file. You can obtain access to the images via - #: convenience method, :method:`get_image_collection`. + #: convenience method, :meth:`get_image_collection`. is_image_collection = False #: If set to True, the input plugin will perform special processing @@ -117,7 +117,7 @@ class InputFormatPlugin(Plugin): for_viewer = False #: Options shared by all Input format plugins. Do not override - #: in sub-classes. Use :member:`options` instead. Every option must be an + #: in sub-classes. Use :attr:`options` instead. Every option must be an #: instance of :class:`OptionRecommendation`. common_options = set([ OptionRecommendation(name='input_encoding', @@ -173,7 +173,6 @@ class InputFormatPlugin(Plugin): returns. :param stream: A file like object that contains the input file. - :param options: Options to customize the conversion process. Guaranteed to have attributes corresponding to all the options declared by this plugin. In @@ -182,14 +181,11 @@ class InputFormatPlugin(Plugin): mean be more verbose. Another useful attribute is ``input_profile`` that is an instance of :class:`calibre.customize.profiles.InputProfile`. - :param file_ext: The extension (without the .) of the input file. It is guaranteed to be one of the `file_types` supported by this plugin. - :param log: A :class:`calibre.utils.logging.Log` object. All output should use this object. - :param accelarators: A dictionary of various information that the input plugin can get easily that would speed up the subsequent stages of the conversion. @@ -235,7 +231,7 @@ class OutputFormatPlugin(Plugin): (OPF+HTML) into an output ebook. The OEB document can be assumed to be encoded in UTF-8. - The main action happens in :method:`convert`. + The main action happens in :meth:`convert`. ''' type = _('Conversion Output') @@ -247,7 +243,7 @@ class OutputFormatPlugin(Plugin): file_type = None #: Options shared by all Input format plugins. Do not override - #: in sub-classes. Use :member:`options` instead. Every option must be an + #: in sub-classes. Use :attr:`options` instead. Every option must be an #: instance of :class:`OptionRecommendation`. common_options = set([ OptionRecommendation(name='pretty_print', @@ -277,17 +273,15 @@ class OutputFormatPlugin(Plugin): :class:`calibre.ebooks.oeb.OEBBook` to the file specified by output. :param output: Either a file like object or a string. If it is a string - it is the path to a directory that may or may not exist. The output - plugin should write its output into that directory. If it is a file like - object, the output plugin should write its output into the file. - + it is the path to a directory that may or may not exist. The output + plugin should write its output into that directory. If it is a file like + object, the output plugin should write its output into the file. :param input_plugin: The input plugin that was used at the beginning of - the conversion pipeline. - + the conversion pipeline. :param opts: Conversion options. Guaranteed to have attributes - corresponding to the OptionRecommendations of this plugin. - + corresponding to the OptionRecommendations of this plugin. :param log: The logger. Print debug/info messages etc. using this. + ''' raise NotImplementedError diff --git a/src/calibre/devices/interface.py b/src/calibre/devices/interface.py index c417c501f4..d40231d950 100644 --- a/src/calibre/devices/interface.py +++ b/src/calibre/devices/interface.py @@ -1,10 +1,5 @@ __license__ = 'GPL v3' __copyright__ = '2008, Kovid Goyal ' -""" -Define the minimum interface that a device backend must satisfy to be used in -the GUI. A device backend must subclass the L{Device} class. See prs500.py for -a backend that implement the Device interface for the SONY PRS500 Reader. -""" import os from collections import namedtuple @@ -15,32 +10,38 @@ class DevicePlugin(Plugin): """ Defines the interface that should be implemented by backends that communicate with an ebook reader. - - The C{end_session} variables are used for USB session management. Sometimes - the front-end needs to call several methods one after another, in which case - the USB session should not be closed after each method call. """ type = _('Device Interface') - # Ordered list of supported formats + #: Ordered list of supported formats FORMATS = ["lrf", "rtf", "pdf", "txt"] + #: VENDOR_ID can be either an integer, a list of integers or a dictionary - #: If it is a dictionary, it must be a dictionary of dictionaries, of the form - #: { - #: integer_vendor_id : { product_id : [list of BCDs], ... }, - #: ... - #: } + #: If it is a dictionary, it must be a dictionary of dictionaries, + #: of the form:: + #: + #: { + #: integer_vendor_id : { product_id : [list of BCDs], ... }, + #: ... + #: } + #: VENDOR_ID = 0x0000 + #: An integer or a list of integers PRODUCT_ID = 0x0000 - # BCD can be either None to not distinguish between devices based on BCD, or - # it can be a list of the BCD numbers of all devices supported by this driver. + #: BCD can be either None to not distinguish between devices based on BCD, or + #: it can be a list of the BCD numbers of all devices supported by this driver. BCD = None - THUMBNAIL_HEIGHT = 68 # Height for thumbnails on device - # Whether the metadata on books can be set via the GUI. + + #: Height for thumbnails on the device + THUMBNAIL_HEIGHT = 68 + + #: Whether the metadata on books can be set via the GUI. CAN_SET_METADATA = True + #: Path separator for paths to books on device path_sep = os.sep + #: Icon for this device icon = I('reader.svg') @@ -121,6 +122,7 @@ class DevicePlugin(Plugin): Return True, device_info if a device handled by this plugin is currently connected. :param devices_on_system: List of devices currently connected + ''' if iswindows: return self.is_usb_connected_windows(devices_on_system, @@ -157,13 +159,14 @@ class DevicePlugin(Plugin): def reset(self, key='-1', log_packets=False, report_progress=None, detected_device=None) : """ - :key: The key to unlock the device - :log_packets: If true the packet stream to/from the device is logged - :report_progress: Function that is called with a % progress + :param key: The key to unlock the device + :param log_packets: If true the packet stream to/from the device is logged + :param report_progress: Function that is called with a % progress (number between 0 and 100) for various tasks If it is called with -1 that means that the task does not have any progress information - :detected_device: Device information from the device scanner + :param detected_device: Device information from the device scanner + """ raise NotImplementedError() @@ -174,19 +177,21 @@ class DevicePlugin(Plugin): is only called after the vendor, product ids and the bcd have matched, so it can do some relatively time intensive checks. The default implementation returns True. This method is called only on windows. See also - :method:`can_handle`. + :meth:`can_handle`. :param device_info: On windows a device ID string. On Unix a tuple of - ``(vendor_id, product_id, bcd)``. + ``(vendor_id, product_id, bcd)``. + ''' return True def can_handle(self, device_info, debug=False): ''' - Unix version of :method:`can_handle_windows` + Unix version of :meth:`can_handle_windows` :param device_info: Is a tupe of (vid, pid, bcd, manufacturer, product, - serial number) + serial number) + ''' return True @@ -198,7 +203,8 @@ class DevicePlugin(Plugin): For example: For devices that present themselves as USB Mass storage devices, this method would be responsible for mounting the device or if the device has been automounted, for finding out where it has been - mounted. The base class within USBMS device.py has a implementation of + mounted. The method :meth:`calibre.devices.usbms.device.Device.open` has + an implementation of this function that should serve as a good example for USB Mass storage devices. ''' @@ -219,17 +225,20 @@ class DevicePlugin(Plugin): def set_progress_reporter(self, report_progress): ''' - @param report_progress: Function that is called with a % progress + :param report_progress: Function that is called with a % progress (number between 0 and 100) for various tasks If it is called with -1 that means that the task does not have any progress information + ''' raise NotImplementedError() def get_device_information(self, end_session=True): """ Ask device for device information. See L{DeviceInfoQuery}. - @return: (device name, device version, software version on device, mime type) + + :return: (device name, device version, software version on device, mime type) + """ raise NotImplementedError() @@ -252,8 +261,9 @@ class DevicePlugin(Plugin): 2. Memory Card A 3. Memory Card B - @return: A 3 element list with total space in bytes of (1, 2, 3). If a - particular device doesn't have any of these locations it should return 0. + :return: A 3 element list with total space in bytes of (1, 2, 3). If a + particular device doesn't have any of these locations it should return 0. + """ raise NotImplementedError() @@ -264,19 +274,23 @@ class DevicePlugin(Plugin): 2. Card A 3. Card B - @return: A 3 element list with free space in bytes of (1, 2, 3). If a - particular device doesn't have any of these locations it should return -1. + :return: A 3 element list with free space in bytes of (1, 2, 3). If a + particular device doesn't have any of these locations it should return -1. + """ raise NotImplementedError() def books(self, oncard=None, end_session=True): """ Return a list of ebooks on the device. - @param oncard: If 'carda' or 'cardb' return a list of ebooks on the + + :param oncard: If 'carda' or 'cardb' return a list of ebooks on the specific storage card, otherwise return list of ebooks in main memory of device. If a card is specified and no books are on the card return empty list. - @return: A BookList. + + :return: A BookList. + """ raise NotImplementedError() @@ -285,25 +299,27 @@ class DevicePlugin(Plugin): ''' Upload a list of books to the device. If a file already exists on the device, it should be replaced. - This method should raise a L{FreeSpaceError} if there is not enough + This method should raise a :class:`FreeSpaceError` if there is not enough free space on the device. The text of the FreeSpaceError must contain the - word "card" if C{on_card} is not None otherwise it must contain the word "memory". - :files: A list of paths and/or file-like objects. If they are paths and - the paths point to temporary files, they may have an additional - attribute, original_file_path pointing to the originals. They may have - another optional attribute, deleted_after_upload which if True means - that the file pointed to by original_file_path will be deleted after - being uploaded to the device. - :names: A list of file names that the books should have - once uploaded to the device. len(names) == len(files) + word "card" if ``on_card`` is not None otherwise it must contain the word "memory". + + :param files: A list of paths and/or file-like objects. If they are paths and + the paths point to temporary files, they may have an additional + attribute, original_file_path pointing to the originals. They may have + another optional attribute, deleted_after_upload which if True means + that the file pointed to by original_file_path will be deleted after + being uploaded to the device. + :param names: A list of file names that the books should have + once uploaded to the device. len(names) == len(files) + :param metadata: If not None, it is a list of :class:`MetaInformation` objects. + The idea is to use the metadata to determine where on the device to + put the book. len(metadata) == len(files). Apart from the regular + cover (path to cover), there may also be a thumbnail attribute, which should + be used in preference. The thumbnail attribute is of the form + (width, height, cover_data as jpeg). + :return: A list of 3-element tuples. The list is meant to be passed - to L{add_books_to_metadata}. - :metadata: If not None, it is a list of :class:`MetaInformation` objects. - The idea is to use the metadata to determine where on the device to - put the book. len(metadata) == len(files). Apart from the regular - cover (path to cover), there may also be a thumbnail attribute, which should - be used in preference. The thumbnail attribute is of the form - (width, height, cover_data as jpeg). + to :meth:`add_books_to_metadata`. ''' raise NotImplementedError() @@ -312,12 +328,15 @@ class DevicePlugin(Plugin): ''' Add locations to the booklists. This function must not communicate with the device. - @param locations: Result of a call to L{upload_books} - @param metadata: List of MetaInformation objects, same as for - :method:`upload_books`. - @param booklists: A tuple containing the result of calls to - (L{books}(oncard=None), L{books}(oncard='carda'), - L{books}(oncard='cardb')). + + :param locations: Result of a call to L{upload_books} + :param metadata: List of :class:`MetaInformation` objects, same as for + :meth:`upload_books`. + :param booklists: A tuple containing the result of calls to + (:meth:`books(oncard=None)`, + :meth:`books(oncard='carda')`, + :meth`books(oncard='cardb')`). + ''' raise NotImplementedError @@ -332,26 +351,35 @@ class DevicePlugin(Plugin): ''' Remove books from the metadata list. This function must not communicate with the device. - @param paths: paths to books on the device. - @param booklists: A tuple containing the result of calls to - (L{books}(oncard=None), L{books}(oncard='carda'), - L{books}(oncard='cardb')). + + :param paths: paths to books on the device. + :param booklists: A tuple containing the result of calls to + (:meth:`books(oncard=None)`, + :meth:`books(oncard='carda')`, + :meth`books(oncard='cardb')`). + ''' raise NotImplementedError() def sync_booklists(self, booklists, end_session=True): ''' Update metadata on device. - @param booklists: A tuple containing the result of calls to - (L{books}(oncard=None), L{books}(oncard='carda'), - L{books}(oncard='cardb')). + + :param booklists: A tuple containing the result of calls to + (:meth:`books(oncard=None)`, + :meth:`books(oncard='carda')`, + :meth`books(oncard='cardb')`). + ''' raise NotImplementedError() def get_file(self, path, outfile, end_session=True): ''' - Read the file at C{path} on the device and write it to outfile. - @param outfile: file object like C{sys.stdout} or the result of an C{open} call + Read the file at ``path`` on the device and write it to outfile. + + :param outfile: file object like ``sys.stdout`` or the result of an + :func:`open` call. + ''' raise NotImplementedError() @@ -365,8 +393,8 @@ class DevicePlugin(Plugin): @classmethod def save_settings(cls, settings_widget): ''' - Should save settings to disk. Takes the widget created in config_widget - and saves all settings to disk. + Should save settings to disk. Takes the widget created in + :meth:`config_widget` and saves all settings to disk. ''' raise NotImplementedError() @@ -381,16 +409,18 @@ class DevicePlugin(Plugin): class BookList(list): ''' - A list of books. Each Book object must have the fields: - 1. title - 2. authors - 3. size (file size of the book) - 4. datetime (a UTC time tuple) - 5. path (path on the device to the book) - 6. thumbnail (can be None) thumbnail is either a str/bytes object with the + A list of books. Each Book object must have the fields + + #. title + #. authors + #. size (file size of the book) + #. datetime (a UTC time tuple) + #. path (path on the device to the book) + #. thumbnail (can be None) thumbnail is either a str/bytes object with the image data or it should have an attribute image_path that stores an absolute (platform native) path to the image - 7. tags (a list of strings, can be empty). + #. tags (a list of strings, can be empty). + ''' __getslice__ = None @@ -427,6 +457,7 @@ class BookList(list): created from series, in which case series_index is used. :param collection_attributes: A list of attributes of the Book object + ''' raise NotImplementedError() diff --git a/src/calibre/devices/usbms/device.py b/src/calibre/devices/usbms/device.py index c07b7fd761..b954911242 100644 --- a/src/calibre/devices/usbms/device.py +++ b/src/calibre/devices/usbms/device.py @@ -47,8 +47,8 @@ class Device(DeviceConfig, DevicePlugin): ''' This class provides logic common to all drivers for devices that export themselves - as USB Mass Storage devices. If you are writing such a driver, inherit from this - class. + as USB Mass Storage devices. Provides implementations for mounting/ejecting + of USBMS devices on all platforms. ''' VENDOR_ID = 0x0 @@ -57,9 +57,19 @@ class Device(DeviceConfig, DevicePlugin): VENDOR_NAME = None - # These can be None, string, list of strings or compiled regex + #: String identifying the main memory of the device in the windows PnP id + #: strings + #: This can be None, string, list of strings or compiled regex WINDOWS_MAIN_MEM = None + + #: String identifying the first card of the device in the windows PnP id + #: strings + #: This can be None, string, list of strings or compiled regex WINDOWS_CARD_A_MEM = None + + #: String identifying the second card of the device in the windows PnP id + #: strings + #: This can be None, string, list of strings or compiled regex WINDOWS_CARD_B_MEM = None # The following are used by the check_ioreg_line method and can be either: @@ -68,9 +78,9 @@ class Device(DeviceConfig, DevicePlugin): OSX_CARD_A_MEM = None OSX_CARD_B_MEM = None - # Used by the new driver detection to disambiguate main memory from - # storage cards. Should be a regular expression that matches the - # main memory mount point assigned by OS X + #: Used by the new driver detection to disambiguate main memory from + #: storage cards. Should be a regular expression that matches the + #: main memory mount point assigned by OS X OSX_MAIN_MEM_VOL_PAT = None OSX_EJECT_COMMAND = ['diskutil', 'eject'] @@ -780,7 +790,7 @@ class Device(DeviceConfig, DevicePlugin): def filename_callback(self, default, mi): ''' Callback to allow drivers to change the default file name - set by :method:`create_upload_path`. + set by :meth:`create_upload_path`. ''' return default diff --git a/src/calibre/devices/usbms/driver.py b/src/calibre/devices/usbms/driver.py index 73a329be58..0d28f06f49 100644 --- a/src/calibre/devices/usbms/driver.py +++ b/src/calibre/devices/usbms/driver.py @@ -33,6 +33,10 @@ def debug_print(*args): # CLI must come before Device as it implements the CLI functions that # are inherited from the device interface in Device. class USBMS(CLI, Device): + ''' + The base class for all USBMS devices. Implements the logic for + sending/getting/updating metadata/caching metadata/etc. + ''' description = _('Communicate with an eBook reader.') author = _('John Schember') @@ -195,10 +199,13 @@ class USBMS(CLI, Device): def upload_cover(self, path, filename, metadata): ''' - :path: the full path were the associated book is located. - :filename: the name of the book file without the extension. - :metadata: metadata belonging to the book. Use metadata.thumbnail - for cover + Upload book cover to the device. Default implementation does nothing. + + :param path: the full path were the associated book is located. + :param filename: the name of the book file without the extension. + :param metadata: metadata belonging to the book. Use metadata.thumbnail + for cover + ''' pass diff --git a/src/calibre/ebooks/metadata/fetch.py b/src/calibre/ebooks/metadata/fetch.py index 0613f64bfb..ee90abf679 100644 --- a/src/calibre/ebooks/metadata/fetch.py +++ b/src/calibre/ebooks/metadata/fetch.py @@ -15,6 +15,22 @@ from calibre.ebooks.metadata.library_thing import check_for_cover metadata_config = None class MetadataSource(Plugin): # {{{ + ''' + Represents a source to query for metadata. Subclasses must implement + at least the fetch method. + + When :meth:`fetch` is called, the `self` object will have the following + useful attributes (each of which may be None):: + + title, book_author, publisher, isbn, log, verbose and extra + + Use these attributes to construct the search query. extra is reserved for + future use. + + The fetch method must store the results in `self.results` as a list of + :class:`MetaInformation` objects. If there is an error, it should be stored + in `self.exception` and `self.tb` (for the traceback). + ''' author = 'Kovid Goyal' diff --git a/src/calibre/manual/Makefile b/src/calibre/manual/Makefile index f991cb8777..dc72b40f3f 100644 --- a/src/calibre/manual/Makefile +++ b/src/calibre/manual/Makefile @@ -25,7 +25,7 @@ clean: html: mkdir -p .build/html .build/doctrees - $(SPHINXBUILD) -b custom $(ALLSPHINXOPTS) .build/html + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) .build/html @echo @echo "Build finished. The HTML pages are in .build/html." diff --git a/src/calibre/manual/custom.py b/src/calibre/manual/custom.py index b50853f6d5..eb0a65ac33 100644 --- a/src/calibre/manual/custom.py +++ b/src/calibre/manual/custom.py @@ -3,17 +3,13 @@ __license__ = 'GPL v3' __copyright__ = '2008, Kovid Goyal ' -import sys, os, inspect, re, textwrap +import sys, os, re, textwrap sys.path.insert(0, os.path.abspath('../../')) sys.extensions_location = '../plugins' sys.resources_location = '../../../resources' -from sphinx.util import rpartition from sphinx.util.console import bold -from sphinx.ext.autodoc import prepare_docstring -from docutils.statemachine import ViewList -from docutils import nodes sys.path.append(os.path.abspath('../../../')) from calibre.linux import entry_points @@ -244,61 +240,9 @@ def cli_docs(app): raw += '\n'+'\n'.join(lines) update_cli_doc(os.path.join('cli', cmd+'.rst'), raw, info) -def auto_member(dirname, arguments, options, content, lineno, - content_offset, block_text, state, state_machine): - name = arguments[0] - env = state.document.settings.env - - mod_cls, obj = rpartition(name, '.') - if not mod_cls and hasattr(env, 'autodoc_current_class'): - mod_cls = env.autodoc_current_class - if not mod_cls: - mod_cls = env.currclass - mod, cls = rpartition(mod_cls, '.') - if not mod and hasattr(env, 'autodoc_current_module'): - mod = env.autodoc_current_module - if not mod: - mod = env.currmodule - - module = __import__(mod, None, None, ['foo']) - cls = getattr(module, cls) - lines = inspect.getsourcelines(cls)[0] - - comment_lines = [] - for i, line in enumerate(lines): - if re.search(r'%s\s*=\s*\S+'%obj, line) and not line.strip().startswith('#:'): - for j in range(i-1, 0, -1): - raw = lines[j].strip() - if not raw.startswith('#:'): - break - comment_lines.append(raw[2:]) - break - comment_lines.reverse() - docstring = '\n'.join(comment_lines) - - if module is not None and docstring is not None: - docstring = docstring.decode('utf-8') - - result = ViewList() - result.append('.. attribute:: %s.%s'%(cls.__name__, obj), '') - result.append('', '') - - docstring = prepare_docstring(docstring) - for i, line in enumerate(docstring): - result.append(' ' + line, '' % name, i) - - result.append('', '') - result.append(' **Default**: ``%s``'%repr(getattr(cls, obj, None)), '') - result.append('', '') - node = nodes.paragraph() - state.nested_parse(result, content_offset, node) - - return list(node) - def setup(app): app.add_config_value('epub_cover', None, False) app.add_builder(EPUBHelpBuilder) - app.add_directive('automember', auto_member, 1, (1, 0, 1)) app.connect('doctree-read', substitute) app.connect('builder-inited', cli_docs) app.connect('build-finished', finished) diff --git a/src/calibre/manual/news_recipe.rst b/src/calibre/manual/news_recipe.rst index 7e5045ea47..767c47b41b 100644 --- a/src/calibre/manual/news_recipe.rst +++ b/src/calibre/manual/news_recipe.rst @@ -6,145 +6,12 @@ API Documentation for recipes =============================== .. module:: calibre.web.feeds.news - :synopsis: Defines various abstract base classes that can be subclassed to create powerful news fetching recipes. + :synopsis: The API for writing recipes is defined by the :class:`BasicNewsRecipe` -Defines various abstract base classes that can be subclassed to create powerful news fetching recipes. The useful -subclasses are: +The API for writing recipes is defined by the :class:`BasicNewsRecipe` -.. contents:: - :depth: 1 - :local: - -BasicNewsRecipe ------------------ - -.. class:: BasicNewsRecipe - - Abstract base class that contains a number of members and methods to customize the fetching of contents in your recipes. All - recipes must inherit from this class or a subclass of it. - - The members and methods are organized as follows: - -.. contents:: - :depth: 1 - :local: - - - -Customizing e-book download -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. automember:: BasicNewsRecipe.title - -.. automember:: BasicNewsRecipe.description - -.. automember:: BasicNewsRecipe.__author__ - -.. automember:: BasicNewsRecipe.max_articles_per_feed - -.. automember:: BasicNewsRecipe.oldest_article - -.. automember:: BasicNewsRecipe.recursions - -.. automember:: BasicNewsRecipe.delay - -.. automember:: BasicNewsRecipe.simultaneous_downloads - -.. automember:: BasicNewsRecipe.timeout - -.. automember:: BasicNewsRecipe.timefmt - -.. automember:: BasicNewsRecipe.conversion_options - -.. automember:: BasicNewsRecipe.feeds - -.. automember:: BasicNewsRecipe.no_stylesheets - -.. automember:: BasicNewsRecipe.encoding - -.. automethod:: BasicNewsRecipe.get_browser - -.. automethod:: BasicNewsRecipe.get_cover_url - -.. automethod:: BasicNewsRecipe.get_feeds - -.. automethod:: BasicNewsRecipe.parse_index - - - -Customizing feed parsing -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. automember:: BasicNewsRecipe.summary_length - -.. automember:: BasicNewsRecipe.use_embedded_content - -.. automethod:: BasicNewsRecipe.get_article_url - -.. automethod:: BasicNewsRecipe.print_version - -.. automethod:: BasicNewsRecipe.parse_feeds - - -Pre/post processing of downloaded HTML -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. automember:: BasicNewsRecipe.extra_css - -.. automember:: BasicNewsRecipe.match_regexps - -.. automember:: BasicNewsRecipe.filter_regexps - -.. automember:: BasicNewsRecipe.remove_tags - -.. automember:: BasicNewsRecipe.remove_tags_after - -.. automember:: BasicNewsRecipe.remove_tags_before - -.. automember:: BasicNewsRecipe.remove_attributes - -.. automember:: BasicNewsRecipe.keep_only_tags - -.. automember:: BasicNewsRecipe.preprocess_regexps - -.. automember:: BasicNewsRecipe.template_css - -.. automember:: BasicNewsRecipe.remove_javascript - -.. automethod:: BasicNewsRecipe.skip_ad_pages - -.. automethod:: BasicNewsRecipe.preprocess_html - -.. automethod:: BasicNewsRecipe.postprocess_html - -.. automethod:: BasicNewsRecipe.populate_article_metadata - - -Convenience methods -~~~~~~~~~~~~~~~~~~~~~~~ - -.. automethod:: BasicNewsRecipe.cleanup - -.. automethod:: BasicNewsRecipe.index_to_soup - -.. automethod:: BasicNewsRecipe.sort_index_by - -.. automethod:: BasicNewsRecipe.tag_to_string - - -Miscellaneous -~~~~~~~~~~~~~~~~~~ - -.. automember:: BasicNewsRecipe.requires_version - - -CustomIndexRecipe ---------------------- - -.. class:: CustomIndexRecipe - - This class is useful for getting content from websites that don't follow the "multiple articles in several feeds" content model. - -.. automethod:: CustomIndexRecipe.custom_index +.. autoclass:: BasicNewsRecipe + :members: + :member-order: groupwise diff --git a/src/calibre/manual/plugins.rst b/src/calibre/manual/plugins.rst index ffc038beef..4a4d5c72f5 100644 --- a/src/calibre/manual/plugins.rst +++ b/src/calibre/manual/plugins.rst @@ -5,7 +5,7 @@ API Documentation for plugins =============================== -.. module:: calibre.customize.__init__ +.. module:: calibre.customize :synopsis: Defines various abstract base classes that can be subclassed to create plugins. Defines various abstract base classes that can be subclassed to create powerful plugins. The useful @@ -20,113 +20,136 @@ classes are: Plugin ----------------- -.. class:: Plugin - - Abstract base class that contains a number of members and methods to create your plugin. All - plugins must inherit from this class or a subclass of it. - - The members and methods are: - -.. automember:: Plugin.name - -.. automember:: Plugin.author - -.. automember:: Plugin.description - -.. automember:: Plugin.version - -.. automember:: Plugin.supported_platforms - -.. automember:: Plugin.priority - -.. automember:: Plugin.minimum_calibre_version - -.. automember:: Plugin.can_be_disabled - -.. automethod:: Plugin.initialize - -.. automethod:: Plugin.customization_help - -.. automethod:: Plugin.temporary_file +.. autoclass:: Plugin + :members: + :member-order: bysource .. _pluginsFTPlugin: FileTypePlugin ----------------- -.. class:: Plugin - - Abstract base class that contains a number of members and methods to create your file type plugin. All file type - plugins must inherit from this class or a subclass of it. - - The members and methods are: - -.. automember:: FileTypePlugin.file_types - -.. automember:: FileTypePlugin.on_import - -.. automember:: FileTypePlugin.on_preprocess - -.. automember:: FileTypePlugin.on_postprocess - -.. automethod:: FileTypePlugin.run +.. autoclass:: FileTypePlugin + :show-inheritance: + :members: + :member-order: bysource .. _pluginsMetadataPlugin: Metadata plugins ------------------- -.. class:: MetadataReaderPlugin - - Abstract base class that contains a number of members and methods to create your metadata reader plugin. All metadata - reader plugins must inherit from this class or a subclass of it. - - The members and methods are: - -.. automember:: MetadataReaderPlugin.file_types - -.. automethod:: MetadataReaderPlugin.get_metadata +.. autoclass:: MetadataReaderPlugin + :show-inheritance: + :members: + :member-order: bysource -.. class:: MetadataWriterPlugin - - Abstract base class that contains a number of members and methods to create your metadata writer plugin. All metadata - writer plugins must inherit from this class or a subclass of it. - - The members and methods are: - -.. automember:: MetadataWriterPlugin.file_types - -.. automethod:: MetadataWriterPlugin.set_metadata - +.. autoclass:: MetadataWriterPlugin + :show-inheritance: + :members: + :member-order: bysource .. _pluginsMetadataSource: +Catalog plugins +---------------- + +.. autoclass:: CatalogPlugin + :show-inheritance: + :members: + :member-order: bysource + + Metadata download plugins -------------------------- -.. class:: calibre.ebooks.metadata.fetch.MetadataSource +.. module:: calibre.ebooks.metadata.fetch - Represents a source to query for metadata. Subclasses must implement - at least the fetch method. +.. autoclass:: MetadataSource + :show-inheritance: + :members: + :member-order: bysource - When :meth:`fetch` is called, the `self` object will have the following - useful attributes (each of which may be None):: +Conversion plugins +-------------------- - title, book_author, publisher, isbn, log, verbose and extra +.. module:: calibre.customize.conversion - Use these attributes to construct the search query. extra is reserved for - future use. +.. autoclass:: InputFormatPlugin + :show-inheritance: + :members: + :member-order: bysource - The fetch method must store the results in `self.results` as a list of - :class:`MetaInformation` objects. If there is an error, it should be stored - in `self.exception` and `self.tb` (for the traceback). +.. autoclass:: OutputFormatPlugin + :show-inheritance: + :members: + :member-order: bysource -.. automember:: calibre.ebooks.metadata.fetch.MetadataSource.metadata_type +Device Drivers +----------------- -.. automember:: calibre.ebooks.metadata.fetch.MetadataSource.string_customization_help +.. module:: calibre.devices.interface -.. automethod:: calibre.ebooks.metadata.fetch.MetadataSource.fetch +The base class for all device drivers is :class:`DevicePlugin`. However, if your device exposes itself as a USBMS drive to the operating system, you should use the USBMS class instead as it implements all the logic needed to support these kinds of devices. + +.. autoclass:: DevicePlugin + :show-inheritance: + :members: + :member-order: bysource + +.. autoclass:: BookList + :show-inheritance: + :members: + :member-order: bysource +USB Mass Storage based devices +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The base class for such devices is :class:`calibre.devices.usbms.driver.USBMS`. This class in turn inherits some of its functionality from its bases, documented below. A typical basic USBMS based driver looks like this: + +.. code-block:: python + + from calibre.devices.usbms.driver import USBMS + + class PDNOVEL(USBMS): + name = 'Pandigital Novel device interface' + gui_name = 'PD Novel' + description = _('Communicate with the Pandigital Novel') + author = 'Kovid Goyal' + supported_platforms = ['windows', 'linux', 'osx'] + FORMATS = ['epub', 'pdf'] + + VENDOR_ID = [0x18d1] + PRODUCT_ID = [0xb004] + BCD = [0x224] + + VENDOR_NAME = 'ANDROID' + WINDOWS_MAIN_MEM = WINDOWS_CARD_A_MEM = '__UMS_COMPOSITE' + THUMBNAIL_HEIGHT = 144 + + EBOOK_DIR_MAIN = 'eBooks' + SUPPORTS_SUB_DIRS = False + + def upload_cover(self, path, filename, metadata): + coverdata = getattr(metadata, 'thumbnail', None) + if coverdata and coverdata[2]: + with open('%s.jpg' % os.path.join(path, filename), 'wb') as coverfile: + coverfile.write(coverdata[2]) + +.. autoclass:: calibre.devices.usbms.device.Device + :show-inheritance: + :members: + :member-order: bysource + +.. autoclass:: calibre.devices.usbms.cli.CLI + :members: + :member-order: bysource + +.. autoclass:: calibre.devices.usbms.driver.USBMS + :show-inheritance: + :members: + :member-order: bysource + diff --git a/src/calibre/web/feeds/news.py b/src/calibre/web/feeds/news.py index 63351bf66d..269f710879 100644 --- a/src/calibre/web/feeds/news.py +++ b/src/calibre/web/feeds/news.py @@ -37,7 +37,10 @@ class DownloadDenied(ValueError): class BasicNewsRecipe(Recipe): ''' - Abstract base class that contains logic needed in all feed fetchers. + Base class that contains logic needed in all recipes. By overriding + progressively more of the functionality in this class, you can make + progressively more customized/powerful recipes. For a tutorial introduction + to creating recipes, see :doc:`news`. ''' #: The title to use for the ebook @@ -127,7 +130,7 @@ class BasicNewsRecipe(Recipe): #: embedded content. use_embedded_content = None - #: Set to True and implement :method:`get_obfuscated_article` to handle + #: Set to True and implement :meth:`get_obfuscated_article` to handle #: websites that try to make it difficult to scrape content. articles_are_obfuscated = False @@ -147,7 +150,7 @@ class BasicNewsRecipe(Recipe): #: If True empty feeds are removed from the output. #: This option has no effect if parse_index is overriden in #: the sub class. It is meant only for recipes that return a list - #: of feeds using `feeds` or :method:`get_feeds`. + #: of feeds using `feeds` or :meth:`get_feeds`. remove_empty_feeds = False #: List of regular expressions that determines which links to follow @@ -538,8 +541,7 @@ class BasicNewsRecipe(Recipe): HTML fetching engine, so it can contain links to pages/images on the web. This method is typically useful for sites that try to make it difficult to - access article content automatically. See for example the - :module:`calibre.web.recipes.iht` recipe. + access article content automatically. ''' raise NotImplementedError @@ -700,8 +702,7 @@ class BasicNewsRecipe(Recipe): Download and pre-process all articles from the feeds in this recipe. This method should be called only once on a particular Recipe instance. Calling it more than once will lead to undefined behavior. - @return: Path to index.html - @rtype: string + :return: Path to index.html ''' try: res = self.build_index() @@ -1359,7 +1360,7 @@ class BasicNewsRecipe(Recipe): ''' If your recipe when converted to EPUB has problems with images when viewed in Adobe Digital Editions, call this method from within - :method:`postprocess_html`. + :meth:`postprocess_html`. ''' for item in soup.findAll('img'): for attrib in ['height','width','border','align','style']: From c5a5751dda2ed826b0d1b7625e733a3754dae6f5 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 25 Jul 2010 16:42:44 -0600 Subject: [PATCH 07/18] News download: Make the navbars on the section index pages more useful --- src/calibre/web/feeds/__init__.py | 2 +- src/calibre/web/feeds/templates.py | 35 +++++++++++++++++++++++++----- 2 files changed, 30 insertions(+), 7 deletions(-) diff --git a/src/calibre/web/feeds/__init__.py b/src/calibre/web/feeds/__init__.py index c34334ee09..a70cf8b664 100644 --- a/src/calibre/web/feeds/__init__.py +++ b/src/calibre/web/feeds/__init__.py @@ -115,7 +115,7 @@ class Feed(object): max_articles_per_feed=100): entries = feed.entries feed = feed.feed - self.title = feed.get('title', _('Unknown feed')) if not title else title + self.title = feed.get('title', _('Unknown section')) if not title else title self.description = feed.get('description', '') image = feed.get('image', {}) self.image_url = image.get('href', None) diff --git a/src/calibre/web/feeds/templates.py b/src/calibre/web/feeds/templates.py index 6d6d381040..eb31b541e8 100644 --- a/src/calibre/web/feeds/templates.py +++ b/src/calibre/web/feeds/templates.py @@ -103,6 +103,32 @@ class IndexTemplate(Template): class FeedTemplate(Template): + def get_navbar(self, f, feeds, top=True): + if len(feeds) < 2: + return DIV() + navbar = DIV('| ', CLASS('calibre_navbar', 'calibre_rescale_70', + style='text-align:center')) + if not top: + hr = HR() + navbar.append(hr) + navbar.text = None + hr.tail = '| ' + + if f+1 < len(feeds): + link = A('Next section', href='../feed_%d/index.html'%(f+1)) + link.tail = ' | ' + navbar.append(link) + link = A('Main menu', href="../index.html") + link.tail = ' | ' + navbar.append(link) + if f > 0: + link = A('Previous section', href='../feed_%d/index.html'%(f-1)) + link.tail = ' |' + navbar.append(link) + if top: + navbar.append(HR()) + return navbar + def _generate(self, f, feeds, cutoff, extra_css=None, style=None): feed = feeds[f] head = HEAD(TITLE(feed.title)) @@ -111,6 +137,8 @@ class FeedTemplate(Template): if extra_css: head.append(STYLE(extra_css, type='text/css')) body = BODY(style='page-break-before:always') + body.append(self.get_navbar(f, feeds)) + div = DIV( H2(feed.title, CLASS('calibre_feed_title', 'calibre_rescale_160')), @@ -144,12 +172,7 @@ class FeedTemplate(Template): CLASS('article_description', 'calibre_rescale_70'))) ul.append(li) div.append(ul) - navbar = DIV('| ', CLASS('calibre_navbar', 'calibre_rescale_70')) - link = A('Up one level', href="../index.html") - link.tail = ' |' - navbar.append(link) - div.append(navbar) - + div.append(self.get_navbar(f, feeds, top=False)) self.root = HTML(head, body) class NavBarTemplate(Template): From 489fa23f4c64984ef75f72043cac7d92f8311813 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 25 Jul 2010 17:32:50 -0600 Subject: [PATCH 08/18] ... --- src/calibre/linux.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/calibre/linux.py b/src/calibre/linux.py index 33e7c004f0..344221ca17 100644 --- a/src/calibre/linux.py +++ b/src/calibre/linux.py @@ -171,7 +171,7 @@ class PostInstall: self.task_failed('Creating uninstaller failed') - def setup_completion(self): + def setup_completion(self): # {{{ try: self.info('Setting up bash completion...') from calibre.ebooks.metadata.cli import option_parser as metaop, filetypes as meta_filetypes @@ -287,8 +287,9 @@ class PostInstall: if self.opts.fatal_errors: raise self.task_failed('Setting up completion failed') + # }}} - def install_man_pages(self): + def install_man_pages(self): # {{{ try: from calibre.utils.help2man import create_man_page if isfreebsd: @@ -318,6 +319,7 @@ class PostInstall: if self.opts.fatal_errors: raise self.task_failed('Installing MAN pages failed') + # }}} def setup_desktop_integration(self): try: From d1a773f2eeeaba40b2fcb768e2407fc34f9e4e11 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 25 Jul 2010 17:53:05 -0600 Subject: [PATCH 09/18] Linux installer: Create a 128x128 calibre icon as apparently GNOME can't handle large icons --- src/calibre/linux.py | 105 +++++++++++++++++++++---------------------- 1 file changed, 52 insertions(+), 53 deletions(-) diff --git a/src/calibre/linux.py b/src/calibre/linux.py index 344221ca17..456afbb486 100644 --- a/src/calibre/linux.py +++ b/src/calibre/linux.py @@ -3,12 +3,14 @@ __copyright__ = '2008, Kovid Goyal ' ''' Post installation script for linux ''' -import sys, os, shutil, cPickle, textwrap, stat +import sys, os, cPickle, textwrap, stat from subprocess import check_call from calibre import __appname__, prints, guess_type from calibre.constants import islinux, isfreebsd from calibre.customize.ui import all_input_formats +from calibre.ptempfile import TemporaryDirectory +from calibre import CurrentDir entry_points = { @@ -323,65 +325,62 @@ class PostInstall: def setup_desktop_integration(self): try: - from PyQt4.QtCore import QFile - from tempfile import mkdtemp + from PyQt4.Qt import QFile, QImage, Qt self.info('Setting up desktop integration...') - tdir = mkdtemp() - cwd = os.getcwdu() - try: - os.chdir(tdir) - render_svg(QFile(I('mimetypes/lrf.svg')), os.path.join(tdir, 'calibre-lrf.png')) - check_call('xdg-icon-resource install --noupdate --context mimetypes --size 128 calibre-lrf.png application-lrf', shell=True) - self.icon_resources.append(('mimetypes', 'application-lrf', '128')) - check_call('xdg-icon-resource install --noupdate --context mimetypes --size 128 calibre-lrf.png text-lrs', shell=True) - self.icon_resources.append(('mimetypes', 'application-lrs', - '128')) - QFile(I('library.png')).copy(os.path.join(tdir, 'calibre-gui.png')) - check_call('xdg-icon-resource install --noupdate --size 128 calibre-gui.png calibre-gui', shell=True) - self.icon_resources.append(('apps', 'calibre-gui', '128')) - render_svg(QFile(I('viewer.svg')), os.path.join(tdir, 'calibre-viewer.png')) - check_call('xdg-icon-resource install --size 128 calibre-viewer.png calibre-viewer', shell=True) - self.icon_resources.append(('apps', 'calibre-viewer', '128')) + with TemporaryDirectory() as tdir: + with CurrentDir(tdir): + render_svg(QFile(I('mimetypes/lrf.svg')), 'calibre-lrf.png') + check_call('xdg-icon-resource install --noupdate --context mimetypes --size 128 calibre-lrf.png application-lrf', shell=True) + self.icon_resources.append(('mimetypes', 'application-lrf', '128')) + check_call('xdg-icon-resource install --noupdate --context mimetypes --size 128 calibre-lrf.png text-lrs', shell=True) + self.icon_resources.append(('mimetypes', 'application-lrs', + '128')) + p = QImage(I('lt.png')).scaledToHeight(128, + Qt.SmoothTransformation) + p.save('calibre-gui.png') + QFile(I('l.png')).copy('calibre-gui.png') + check_call('xdg-icon-resource install --noupdate --size 128 calibre-gui.png calibre-gui', shell=True) + self.icon_resources.append(('apps', 'calibre-gui', '128')) + render_svg(QFile(I('viewer.svg')), 'calibre-viewer.png') + check_call('xdg-icon-resource install --size 128 calibre-viewer.png calibre-viewer', shell=True) + self.icon_resources.append(('apps', 'calibre-viewer', '128')) - mimetypes = set([]) - for x in all_input_formats(): - mt = guess_type('dummy.'+x)[0] - if mt and 'chemical' not in mt: - mimetypes.add(mt) + mimetypes = set([]) + for x in all_input_formats(): + mt = guess_type('dummy.'+x)[0] + if mt and 'chemical' not in mt: + mimetypes.add(mt) - def write_mimetypes(f): - f.write('MimeType=%s;\n'%';'.join(mimetypes)) + def write_mimetypes(f): + f.write('MimeType=%s;\n'%';'.join(mimetypes)) - f = open('calibre-lrfviewer.desktop', 'wb') - f.write(VIEWER) - f.close() - f = open('calibre-ebook-viewer.desktop', 'wb') - f.write(EVIEWER) - write_mimetypes(f) - f.close() - f = open('calibre-gui.desktop', 'wb') - f.write(GUI) - write_mimetypes(f) - f.close() - des = ('calibre-gui.desktop', 'calibre-lrfviewer.desktop', - 'calibre-ebook-viewer.desktop') - for x in des: - cmd = ['xdg-desktop-menu', 'install', './'+x] - if x != des[-1]: - cmd.insert(2, '--noupdate') - check_call(' '.join(cmd), shell=True) - self.menu_resources.append(x) - f = open('calibre-mimetypes', 'wb') - f.write(MIME) - f.close() - self.mime_resources.append('calibre-mimetypes') - check_call('xdg-mime install ./calibre-mimetypes', shell=True) - finally: - os.chdir(cwd) - shutil.rmtree(tdir) + f = open('calibre-lrfviewer.desktop', 'wb') + f.write(VIEWER) + f.close() + f = open('calibre-ebook-viewer.desktop', 'wb') + f.write(EVIEWER) + write_mimetypes(f) + f.close() + f = open('calibre-gui.desktop', 'wb') + f.write(GUI) + write_mimetypes(f) + f.close() + des = ('calibre-gui.desktop', 'calibre-lrfviewer.desktop', + 'calibre-ebook-viewer.desktop') + for x in des: + cmd = ['xdg-desktop-menu', 'install', './'+x] + if x != des[-1]: + cmd.insert(2, '--noupdate') + check_call(' '.join(cmd), shell=True) + self.menu_resources.append(x) + f = open('calibre-mimetypes', 'wb') + f.write(MIME) + f.close() + self.mime_resources.append('calibre-mimetypes') + check_call('xdg-mime install ./calibre-mimetypes', shell=True) except Exception: if self.opts.fatal_errors: raise From c69780c97e261af3b2eac6bd742089acb115cf12 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 25 Jul 2010 17:53:30 -0600 Subject: [PATCH 10/18] ... --- src/calibre/linux.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/calibre/linux.py b/src/calibre/linux.py index 456afbb486..ad1218f5d9 100644 --- a/src/calibre/linux.py +++ b/src/calibre/linux.py @@ -341,7 +341,6 @@ class PostInstall: p = QImage(I('lt.png')).scaledToHeight(128, Qt.SmoothTransformation) p.save('calibre-gui.png') - QFile(I('l.png')).copy('calibre-gui.png') check_call('xdg-icon-resource install --noupdate --size 128 calibre-gui.png calibre-gui', shell=True) self.icon_resources.append(('apps', 'calibre-gui', '128')) render_svg(QFile(I('viewer.svg')), 'calibre-viewer.png') From 0e93305a1023f747c2660c48d5eed388a3b4cc65 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 25 Jul 2010 18:06:31 -0600 Subject: [PATCH 11/18] Linux installer: Fix rendering of viewer icon --- src/calibre/linux.py | 28 +++++++--------------------- 1 file changed, 7 insertions(+), 21 deletions(-) diff --git a/src/calibre/linux.py b/src/calibre/linux.py index ad1218f5d9..fff7e5d969 100644 --- a/src/calibre/linux.py +++ b/src/calibre/linux.py @@ -325,25 +325,22 @@ class PostInstall: def setup_desktop_integration(self): try: - from PyQt4.Qt import QFile, QImage, Qt self.info('Setting up desktop integration...') with TemporaryDirectory() as tdir: with CurrentDir(tdir): - render_svg(QFile(I('mimetypes/lrf.svg')), 'calibre-lrf.png') + render_img('mimetypes/lrf.svg', 'calibre-lrf.png') check_call('xdg-icon-resource install --noupdate --context mimetypes --size 128 calibre-lrf.png application-lrf', shell=True) self.icon_resources.append(('mimetypes', 'application-lrf', '128')) check_call('xdg-icon-resource install --noupdate --context mimetypes --size 128 calibre-lrf.png text-lrs', shell=True) self.icon_resources.append(('mimetypes', 'application-lrs', '128')) - p = QImage(I('lt.png')).scaledToHeight(128, - Qt.SmoothTransformation) - p.save('calibre-gui.png') + render_img('lt.png', 'calibre-gui.png') check_call('xdg-icon-resource install --noupdate --size 128 calibre-gui.png calibre-gui', shell=True) self.icon_resources.append(('apps', 'calibre-gui', '128')) - render_svg(QFile(I('viewer.svg')), 'calibre-viewer.png') + render_img('viewer.svg', 'calibre-viewer.png') check_call('xdg-icon-resource install --size 128 calibre-viewer.png calibre-viewer', shell=True) self.icon_resources.append(('apps', 'calibre-viewer', '128')) @@ -542,21 +539,10 @@ MIME = '''\ ''' -def render_svg(image, dest, width=128, height=128): - from PyQt4.QtGui import QPainter, QImage - from PyQt4.QtSvg import QSvgRenderer - image = image.readAll() if hasattr(image, 'readAll') else image - svg = QSvgRenderer(image) - painter = QPainter() - image = QImage(width, height, QImage.Format_ARGB32) - painter.begin(image) - painter.setRenderHints(QPainter.Antialiasing|QPainter.TextAntialiasing|QPainter.SmoothPixmapTransform|QPainter.HighQualityAntialiasing) - painter.setCompositionMode(QPainter.CompositionMode_SourceOver) - svg.render(painter) - painter.end() - if dest is None: - return image - image.save(dest) +def render_img(image, dest, width=128, height=128): + from PyQt4.Qt import QImage, Qt + img = QImage(I(image)).scaled(width, height, Qt.IgnoreAspectRatio, Qt.SmoothTransformation) + img.save(dest) def main(): p = option_parser() From 007e06dfa5ad1c7026baf7fe40e3079a66e5b047 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 25 Jul 2010 18:14:39 -0600 Subject: [PATCH 12/18] ... --- src/calibre/linux.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/calibre/linux.py b/src/calibre/linux.py index fff7e5d969..9323581c53 100644 --- a/src/calibre/linux.py +++ b/src/calibre/linux.py @@ -41,6 +41,7 @@ entry_points = { ], } +# Uninstall script {{{ UNINSTALL = '''\ #!{python} euid = {euid} @@ -81,6 +82,8 @@ for f in mr: os.remove(os.path.abspath(__file__)) ''' +# }}} + class PostInstall: def task_failed(self, msg): @@ -323,7 +326,7 @@ class PostInstall: self.task_failed('Installing MAN pages failed') # }}} - def setup_desktop_integration(self): + def setup_desktop_integration(self): # {{{ try: self.info('Setting up desktop integration...') @@ -382,6 +385,8 @@ class PostInstall: raise self.task_failed('Setting up desktop integration failed') + # }}} + def option_parser(): from calibre.utils.config import OptionParser parser = OptionParser() From 3a319d058738304a32373b1bbdbca87e1376edfa Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 25 Jul 2010 19:53:27 -0600 Subject: [PATCH 13/18] Fix #6296 (Unable to see contents of library) --- src/calibre/devices/prs505/sony_cache.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/calibre/devices/prs505/sony_cache.py b/src/calibre/devices/prs505/sony_cache.py index 46ccf1f3d2..a444c9e5e2 100644 --- a/src/calibre/devices/prs505/sony_cache.py +++ b/src/calibre/devices/prs505/sony_cache.py @@ -46,7 +46,11 @@ def strptime(src): return time.strptime(' '.join(src), '%w, %d %m %Y %H:%M:%S %Z') def strftime(epoch, zone=time.localtime): - src = time.strftime("%w, %d %m %Y %H:%M:%S GMT", zone(epoch)).split() + try: + src = time.strftime("%w, %d %m %Y %H:%M:%S GMT", zone(epoch)).split() + except: + src = time.strftime("%w, %d %m %Y %H:%M:%S GMT", zone()).split() + src[0] = INVERSE_DAY_MAP[int(src[0][:-1])]+',' src[2] = INVERSE_MONTH_MAP[int(src[2])] return ' '.join(src) From e2c048e23aaf94e08ae3b1debacfbc61d8539c61 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 25 Jul 2010 19:57:19 -0600 Subject: [PATCH 14/18] Fix #6292 (Updated recipes) --- resources/recipes/rian_spa.recipe | 12 ++++-------- resources/recipes/vijesti.recipe | 2 +- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/resources/recipes/rian_spa.recipe b/resources/recipes/rian_spa.recipe index 5d2115168b..2f6ecbcd98 100644 --- a/resources/recipes/rian_spa.recipe +++ b/resources/recipes/rian_spa.recipe @@ -1,13 +1,13 @@ __license__ = 'GPL v3' -__copyright__ = '2009, Darko Miletic ' +__copyright__ = '2009-2010, Darko Miletic ' ''' sp.rian.ru ''' from calibre.web.feeds.news import BasicNewsRecipe -class Ria_eng(BasicNewsRecipe): +class Ria_esp(BasicNewsRecipe): title = 'Ria Novosti' __author__ = 'Darko Miletic' description = 'Noticias desde Russia en Castellano' @@ -28,14 +28,10 @@ class Ria_eng(BasicNewsRecipe): } - keep_only_tags = [dict(name='div', attrs={'class':'articletxt'})] + keep_only_tags = [dict(name='div', attrs={'class':['mainnewsrubric','titleblock','mainnewstxt']})] remove_tags = [dict(name=['object','link','iframe','base'])] - remove_tags_after = dict(name='div',attrs={'class':'text'}) - feeds = [(u'Noticias', u'http://sp.rian.ru/export/rss2/index.xml')] - - def print_version(self, url): - return url.replace('.html','-print.html') + feeds = [(u'Noticias', u'http://rss.feedsportal.com/c/860/fe.ed/sp.rian.ru/export/rss2/index.xml')] diff --git a/resources/recipes/vijesti.recipe b/resources/recipes/vijesti.recipe index 969b300486..c901755b78 100644 --- a/resources/recipes/vijesti.recipe +++ b/resources/recipes/vijesti.recipe @@ -36,7 +36,7 @@ class Vijesti(BasicNewsRecipe): keep_only_tags = [dict(name='div', attrs={'id':'mainnews'})] - remove_tags = [dict(name=['object','link','embed'])] + remove_tags = [dict(name=['object','link','embed','form'])] feeds = [(u'Sve vijesti', u'http://www.vijesti.me/rss.php' )] From 2a39e43655decfc8ccfea34ede310ef23aa51175 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 26 Jul 2010 09:06:39 -0600 Subject: [PATCH 15/18] Fix #6302 (a change in the index-page broke the "Der Tagesspiegel"-recipe) --- resources/recipes/tagesspiegel.recipe | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/resources/recipes/tagesspiegel.recipe b/resources/recipes/tagesspiegel.recipe index e5d2600ae0..3129820e2c 100644 --- a/resources/recipes/tagesspiegel.recipe +++ b/resources/recipes/tagesspiegel.recipe @@ -10,7 +10,7 @@ from calibre.web.feeds.news import BasicNewsRecipe class TagesspiegelRSS(BasicNewsRecipe): title = u'Der Tagesspiegel' - __author__ = 'ipaschke' + __author__ = 'Ingo Paschke' language = 'de' oldest_article = 7 max_articles_per_feed = 100 @@ -39,25 +39,30 @@ class TagesspiegelRSS(BasicNewsRecipe): dict(name='link'), dict(name='iframe'),dict(name='style'),dict(name='meta'),dict(name='button'), dict(name='div', attrs={'class':["hcf-jump-to-comments","hcf-clear","hcf-magnify hcf-media-control"] }), dict(name='span', attrs={'class':["hcf-mainsearch",] }), - dict(name='ul', attrs={'class':["hcf-tools"] }), + dict(name='ul', attrs={'class':["hcf-tools"]}), + dict(name='ul', attrs={'class': re.compile('hcf-services')}) ] def parse_index(self): soup = self.index_to_soup('http://www.tagesspiegel.de/zeitung/') def feed_title(div): - return ''.join(div.findAll(text=True, recursive=False)).strip() + return ''.join(div.findAll(text=True, recursive=False)).strip() if div is not None else None articles = {} key = None ans = [] + maincol = soup.find('div', attrs={'class':re.compile('hcf-main-col')}) - for div in soup.findAll(True, attrs={'class':['hcf-teaser', 'hcf-header', 'story headline']}): + for div in maincol.findAll(True, attrs={'class':['hcf-teaser', 'hcf-header', 'story headline']}): if div['class'] == 'hcf-header': + try: key = string.capwords(feed_title(div.em.a)) articles[key] = [] ans.append(key) + except: + continue elif div['class'] == 'hcf-teaser' and getattr(div.contents[0],'name','') == 'h2': a = div.find('a', href=True) @@ -84,3 +89,4 @@ class TagesspiegelRSS(BasicNewsRecipe): return ans + From e8cd78239993eec03521ca90cf7d918994891ea2 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 26 Jul 2010 09:08:54 -0600 Subject: [PATCH 16/18] Fix #6303 (enhanced "Die Zeit Nachrichten"-recipe) --- resources/recipes/zeitde.recipe | 101 +++++++++++++++++++------------- 1 file changed, 59 insertions(+), 42 deletions(-) diff --git a/resources/recipes/zeitde.recipe b/resources/recipes/zeitde.recipe index df9c647f10..a86359c068 100644 --- a/resources/recipes/zeitde.recipe +++ b/resources/recipes/zeitde.recipe @@ -6,88 +6,105 @@ Fetch Die Zeit. ''' from calibre.web.feeds.news import BasicNewsRecipe - +from calibre.ebooks.BeautifulSoup import Tag class ZeitDe(BasicNewsRecipe): - title = 'Die Zeit Nachrichten' - description = 'Die Zeit - Online Nachrichten' + title = 'ZEIT Online Reader Edition' + description = 'ZEIT Online' language = 'de' lang = 'de_DE' - __author__ = 'Martin Pitt and Sujata Raman' + __author__ = 'Martin Pitt, Sujata Raman and Ingo Paschke' use_embedded_content = False - max_articles_per_feed = 40 + max_articles_per_feed = 100 remove_empty_feeds = True no_stylesheets = True + no_javascript = True encoding = 'utf-8' - + delay = 0 feeds = [ - ('Politik', 'http://newsfeed.zeit.de/politik/index'), - ('Wirtschaft', 'http://newsfeed.zeit.de/wirtschaft/index'), - ('Meinung', 'http://newsfeed.zeit.de/meinung/index'), - ('Gesellschaft', 'http://newsfeed.zeit.de/gesellschaft/index'), - ('Kultur', 'http://newsfeed.zeit.de/kultur/index'), - ('Wissen', 'http://newsfeed.zeit.de/wissen/index'), + ('Seite 1', 'http://newsfeed.zeit.de/index'), + ('Politik', 'http://www.zeit.de/solr/select/?q=ressort:%22Politik%22%20type:article&version=2.2&start=0&rows=50&sort=date-first-released%20desc&indent=on&wt=xslt&tr=solr2rss.xsl'), + ('Wirtschaft', 'http://www.zeit.de/solr/select/?q=ressort:%22Wirtschaft%22%20type:article&version=2.2&start=0&rows=50&sort=date-first-released%20desc&indent=on&wt=xslt&tr=solr2rss.xsl'), + ('Meinung', 'http://www.zeit.de/solr/select/?q=ressort:%22Meinung%22%20type:article&version=2.2&start=0&rows=50&sort=date-first-released%20desc&indent=on&wt=xslt&tr=solr2rss.xsl'), + ('Gesellschaft', 'http://www.zeit.de/solr/select/?q=ressort:%22Gesellschaft%22%20type:article&version=2.2&start=0&rows=50&sort=date-first-released%20desc&indent=on&wt=xslt&tr=solr2rss.xsl'), + ('Kultur', 'http://www.zeit.de/solr/select/?q=ressort:%22Kultur%22%20type:article&version=2.2&start=0&rows=50&sort=date-first-released%20desc&indent=on&wt=xslt&tr=solr2rss.xsl'), + ('Wissen', 'http://www.zeit.de/solr/select/?q=ressort:%22Wissen%22%20type:article&version=2.2&start=0&rows=50&sort=date-first-released%20desc&indent=on&wt=xslt&tr=solr2rss.xsl'), + ('Digital', 'http://www.zeit.de/solr/select/?q=ressort:%22Digital%22%20type:article&version=2.2&start=0&rows=50&sort=date-first-released%20desc&indent=on&wt=xslt&tr=solr2rss.xsl'), + ('Studium', 'http://www.zeit.de/solr/select/?q=ressort:%22Studium%22%20type:article&version=2.2&start=0&rows=50&sort=date-first-released%20desc&indent=on&wt=xslt&tr=solr2rss.xsl'), + ('Karriere', 'http://www.zeit.de/solr/select/?q=ressort:%22Karriere%22%20type:article&version=2.2&start=0&rows=50&sort=date-first-released%20desc&indent=on&wt=xslt&tr=solr2rss.xsl'), + ('Lebensart', 'http://www.zeit.de/solr/select/?q=ressort:%22Lebensart%22%20type:article&version=2.2&start=0&rows=50&sort=date-first-released%20desc&indent=on&wt=xslt&tr=solr2rss.xsl'), + ('Reisen', 'http://www.zeit.de/solr/select/?q=ressort:%22Reisen%22%20type:article&version=2.2&start=0&rows=50&sort=date-first-released%20desc&indent=on&wt=xslt&tr=solr2rss.xsl'), + ('Auto', 'http://www.zeit.de/solr/select/?q=ressort:%22Auto%22%20type:article&version=2.2&start=0&rows=50&sort=date-first-released%20desc&indent=on&wt=xslt&tr=solr2rss.xsl'), + ('Sport', 'http://www.zeit.de/solr/select/?q=ressort:%22Sport%22%20type:article&version=2.2&start=0&rows=50&sort=date-first-released%20desc&indent=on&wt=xslt&tr=solr2rss.xsl'), ] extra_css = ''' .supertitle{color:#990000; font-family:Arial,Helvetica,sans-serif;font-size:xx-small;} - .excerpt{font-family:Georgia,Palatino,Palatino Linotype,FreeSerif,serif;font-size:large;} - .title{font-family:Arial,Helvetica,sans-serif;font-size:large} + .excerpt{font-family:Georgia,Palatino,Palatino Linotype,FreeSerif,serif;font-size:small;} + .title{font-family:Arial,Helvetica,sans-serif;font-size:large;clear:right;} .caption{color:#666666; font-family:Arial,Helvetica,sans-serif;font-size:xx-small;} .copyright{color:#666666; font-family:Arial,Helvetica,sans-serif;font-size:xx-small;} .article{font-family:Georgia,Palatino,Palatino Linotype,FreeSerif,serif;font-size:x-small} + .quote{font-family:Georgia,Palatino,Palatino Linotype,FreeSerif,serif;font-size:x-small} + .quote .cite{font-family:Georgia,Palatino,Palatino Linotype,FreeSerif,serif;font-size:xx-small} .headline iconportrait_inline{font-family:Arial,Helvetica,sans-serif;font-size:x-small} + .inline{float:left;margin-top:0;margin-right:15px;position:relative;width:180px; } + img.inline{float:none} + .intertitle{font-family:Georgia,Palatino,Palatino Linotype,FreeSerif,serif;font-size:x-small;font-weight:700} + .ebinfobox{font-family:Georgia,Palatino,Palatino Linotype,FreeSerif,serif;font-size:xx-small;list-style-type:none;float:right;margin-top:0;border-left-style:solid;border-left-width:1px;padding-left:10px;} + .infobox {border-style: solid; border-width: 1px;padding:8px;} + .infobox dt {font-weight:700;} ''' #filter_regexps = [r'ad.de.doubleclick.net/'] keep_only_tags = [ dict(name='div', attrs={'class':["article"]}) , + dict(name='ul', attrs={'class':["tools"]}) , ] remove_tags = [ - dict(name='link'), dict(name='iframe'),dict(name='style'), - dict(name='div', attrs={'class':["pagination block","pagenav","inline link"] }), - dict(name='div', attrs={'id':["place_5","place_4"]}) + dict(name='link'), dict(name='iframe'),dict(name='style'),dict(name='meta'), + dict(name='div', attrs={'class':["pagination block","pagenav","inline link", "copyright"] }), + dict(name='p', attrs={'class':["ressortbacklink", "copyright"] }), + dict(name='div', attrs={'id':["place_5","place_4","comments"]}) ] - + remove_attributes = ['style', 'font'] def get_article_url(self, article): + ans = article.get('link',None) + ans += "?page=all" - ans = article.get('guid',None) - - try: - self.log('Looking for full story link in', ans) - soup = self.index_to_soup(ans) - x = soup.find(text="Auf einer Seite lesen") - - if x is not None: - - a = x.parent - if a and a.has_key('href'): - ans = a['href'] - self.log('Found full story link', ans) - except: - pass - - if 'video' in ans or 'quiz' in ans : - + if 'video' in ans or 'quiz' or 'blog.zeit.de/' in ans : ans = None return ans - + def get_cover_url(self): + try: + inhalt = self.index_to_soup('http://www.zeit.de/inhalt') + return inhalt.find('div', attrs={'class':'singlearchive clearfix'}).img['src'].replace('icon_','') + except: + return 'http://images.zeit.de/bilder/titelseiten_zeit/1946/001_001.jpg' def preprocess_html(self, soup): soup.html['xml:lang'] = self.lang soup.html['lang'] = self.lang mtag = '' soup.head.insert(0,mtag) - + title = soup.find('h2', attrs={'class':'title'}) + if title is None: + print "no title" + return soup + info = Tag(soup,'ul',[('class','ebinfobox')]) + tools = soup.find('ul', attrs={'class':'tools'}) + author = tools.find('li','author first') + for tag in ['author first', 'date', 'date first', 'author', 'source']: + line = tools.find('li', tag) + if line: + info.insert(0,line) + title.parent.insert(0,info) + tools.extract() return soup - - #def print_version(self,url): - # return url.replace('http://www.zeit.de/', 'http://images.zeit.de/text/').replace('?from=rss', '') - From 24342c9099f3677051c61cd9d92873ee3d35c870 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 26 Jul 2010 10:01:20 -0600 Subject: [PATCH 17/18] Update Toms Hardware (DE) and Welt Online --- resources/recipes/tomshardware_de.recipe | 4 +++- resources/recipes/welt.recipe | 11 ++++++----- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/resources/recipes/tomshardware_de.recipe b/resources/recipes/tomshardware_de.recipe index aad3d60b17..287ea6ee12 100644 --- a/resources/recipes/tomshardware_de.recipe +++ b/resources/recipes/tomshardware_de.recipe @@ -50,12 +50,14 @@ class cdnet(BasicNewsRecipe): dict(name='div', attrs={'class':'greyBoxR clearfix'}), dict(name='div', attrs={'class':'greyBoxL clearfix'}), dict(name='div', attrs={'class':'greyBox clearfix'}), + dict(name='div', attrs={'class':'labelized'}), dict(id='')] #remove_tags_before = [dict(id='header-news-title')] - remove_tags_after = [dict(name='div', attrs={'class':'btmGreyTables'})] + remove_tags_after = [dict(name='div', attrs={'class':'labelized'})] #remove_tags_after = [dict(name='div', attrs={'class':'intelliTXT'})] feeds = [ ('tomshardware', 'http://www.tomshardware.com/de/feeds/rss2/tom-s-hardware-de,12-1.xml') ] + diff --git a/resources/recipes/welt.recipe b/resources/recipes/welt.recipe index 89e0d42c09..bd5af30a63 100644 --- a/resources/recipes/welt.recipe +++ b/resources/recipes/welt.recipe @@ -22,7 +22,7 @@ class weltDe(BasicNewsRecipe): remove_stylesheets = True remove_javascript = True encoding = 'utf-8' - html2epub_options = 'linearize_tables = True\nbase_font_size2=10' + html2epub_options = 'base_font_size=10' BasicNewsRecipe.summary_length = 100 @@ -83,10 +83,9 @@ class weltDe(BasicNewsRecipe): dict(name='div', attrs={'class':'articleOptions clear'}), dict(name='div', attrs={'class':'noPrint galleryIndex'}), dict(name='div', attrs={'class':'inlineBox inlineTagCloud'}), + dict(name='div', attrs={'class':'clear module imageGalleryBig bgColor1'}), dict(name='div', attrs={'class':'clear module writeComment bgColor1'}), dict(name='div', attrs={'class':'clear module textGallery bgColor1'}), - dict(name='div', attrs={'class':'clear module socialMedia bgColor1'}), - dict(name='div', attrs={'class':'clear module continuativeLinks'}), dict(name='div', attrs={'class':'moreArtH3'}), dict(name='div', attrs={'class':'jqmWindow'}), dict(name='div', attrs={'class':'clear gap4'}), @@ -99,7 +98,7 @@ class weltDe(BasicNewsRecipe): dict(name='div', attrs={'class':'headLineH3'}), dict(name='div', attrs={'class':'print'}), dict(name='div', attrs={'class':'clear menu'}), - dict(name='div', attrs={'class':'clear galleryContent'}), + dict(name='div', attrs={'class':'themenalarm'}), dict(name='p', attrs={'class':'jump'}), dict(name='a', attrs={'class':'commentLink'}), dict(name='h2', attrs={'class':'jumpHeading'}), @@ -110,7 +109,7 @@ class weltDe(BasicNewsRecipe): dict(name='table', attrs={'class':'textGallery'}), dict(name='li', attrs={'class':'active'})] - remove_tags_after = [dict(name='div', attrs={'class':'clear departmentLine'})] + remove_tags_after = [dict(name='div', attrs={'class':'themenalarm'})] extra_css = ''' h2{font-family:Arial,Helvetica,sans-serif; font-size: x-small; color: #003399;} @@ -122,6 +121,7 @@ class weltDe(BasicNewsRecipe): .photo {font-family:Arial,Helvetica,sans-serif; font-size: x-small; color: #666666;} ''' feeds = [ ('Politik', 'http://welt.de/politik/?service=Rss'), + ('Deutsche Dinge', 'http://www.welt.de/deutsche-dinge/?service=Rss'), ('Wirtschaft', 'http://welt.de/wirtschaft/?service=Rss'), ('Finanzen', 'http://welt.de/finanzen/?service=Rss'), ('Sport', 'http://welt.de/sport/?service=Rss'), @@ -137,3 +137,4 @@ class weltDe(BasicNewsRecipe): def print_version(self, url): return url.replace ('.html', '.html?print=true') + From dce70ef463eac468a943ccac1b3a618323e59e69 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 26 Jul 2010 10:06:40 -0600 Subject: [PATCH 18/18] Fix #6300 (TypeError: an integer is required) --- src/calibre/gui2/throbber.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/calibre/gui2/throbber.py b/src/calibre/gui2/throbber.py index 99c899c9f3..e27438b60d 100644 --- a/src/calibre/gui2/throbber.py +++ b/src/calibre/gui2/throbber.py @@ -26,7 +26,10 @@ class ThrobbingButton(QToolButton): def set_normal_icon_size(self, w, h): self.normal_icon_size = QSize(w, h) self.setIconSize(self.normal_icon_size) - self.setMinimumSize(self.sizeHint()) + try: + self.setMinimumSize(self.sizeHint()) + except: + self.setMinimumSize(QSize(w+5, h+5)) def animation_finished(self): self.setIconSize(self.normal_icon_size)