diff --git a/Changelog.yaml b/Changelog.yaml index 79ef8f249c..e46d937411 100644 --- a/Changelog.yaml +++ b/Changelog.yaml @@ -5,7 +5,7 @@ # Also, each release can have new and improved recipes. - version: 0.7.36 - date: 2010-01-01 + date: 2011-01-01 new features: - title: "Tag browser: Add subcategories and search" diff --git a/resources/catalog/template.xhtml b/resources/catalog/template.xhtml index 409086d343..4df2a11c84 100644 --- a/resources/catalog/template.xhtml +++ b/resources/catalog/template.xhtml @@ -14,10 +14,10 @@ - + - + @@ -32,7 +32,7 @@ - +
 
 
{publisher}{note_source}: {note_content}
 

diff --git a/resources/default_tweaks.py b/resources/default_tweaks.py index 9726ed3b09..4ae0278133 100644 --- a/resources/default_tweaks.py +++ b/resources/default_tweaks.py @@ -55,16 +55,9 @@ author_sort_copy_method = 'invert' # categories_use_field_for_author_name = 'author_sort' categories_use_field_for_author_name = 'author' -# Control how the tags pane displays categories containing many items. If the -# number of items is larger than categories_collapse_more_than, a sub-category -# will be added. If sorting by name, then the subcategories can be organized by -# first letter (categories_collapse_model = 'first letter') or into equal-sized -# groups (categories_collapse_model = 'partition'). If sorting by average rating -# or by popularity, then 'partition' is always used. The addition of -# subcategories can be disabled by setting categories_collapse_more_than = 0. -# When using partition, the format of the subcategory label is controlled by a -# template: categories_collapsed_name_template if sorting by name, -# categories_collapsed_rating_template if sorting by average rating, and +# When partitioning the tags browser, the format of the subcategory label is +# controlled by a template: categories_collapsed_name_template if sorting by +# name, categories_collapsed_rating_template if sorting by average rating, and # categories_collapsed_popularity_template if sorting by popularity. There are # two variables available to the template: first and last. The variable 'first' # is the initial item in the subcategory, and the variable 'last' is the final @@ -76,11 +69,10 @@ categories_use_field_for_author_name = 'author' # avg_rating: the averate rating of all the books referencing this item # sort: the sort value. For authors, this is the author_sort for that author # category: the category (e.g., authors, series) that the item is in. -categories_collapse_more_than = 50 -categories_collapsed_name_template = '{first.name:shorten(4,'',0)} - {last.name::shorten(4,'',0)}' +categories_collapsed_name_template = '{first.sort:shorten(4,'',0)} - {last.sort:shorten(4,'',0)}' categories_collapsed_rating_template = '{first.avg_rating:4.2f:ifempty(0)} - {last.avg_rating:4.2f:ifempty(0)}' categories_collapsed_popularity_template = '{first.count:d} - {last.count:d}' -categories_collapse_model = 'first letter' + # Set whether boolean custom columns are two- or three-valued. # Two-values for true booleans diff --git a/resources/recipes/la_republica.recipe b/resources/recipes/la_republica.recipe index c74f2d7b05..14dc0d0aef 100644 --- a/resources/recipes/la_republica.recipe +++ b/resources/recipes/la_republica.recipe @@ -28,6 +28,8 @@ class LaRepubblica(BasicNewsRecipe): recursion = 10 remove_javascript = True + no_stylesheets = True + def get_article_url(self, article): link = article.get('id', article.get('guid', None)) if link is None: diff --git a/src/calibre/devices/scanner.py b/src/calibre/devices/scanner.py index 380dcb7440..241d503c14 100644 --- a/src/calibre/devices/scanner.py +++ b/src/calibre/devices/scanner.py @@ -30,6 +30,12 @@ class Drive(str): typ.order = order return typ +def drivecmp(a, b): + ans = cmp(getattr(a, 'order', 0), getattr(b, 'order', 0)) + if ans == 0: + ans = cmp(a, b) + return ans + class WinPNPScanner(object): @@ -57,7 +63,13 @@ class WinPNPScanner(object): order = 0 match = re.search(r'REV_.*?&(\d+)#', pnp_id) if match is None: - match = re.search(r'REV_.*?&(\d+)', pnp_id) + # Windows XP + # On the Nook Color this is the last digit + # + # USBSTOR\DISK&VEN_B&N&PROD_EBOOK_DISK&REV_0100\7&13EAFDB8&0&2004760017462009&1 + # USBSTOR\DISK&VEN_B&N&PROD_EBOOK_DISK&REV_0100\7&13EAFDB8&0&2004760017462009&0 + # + match = re.search(r'REV_.*&(\d+)', pnp_id) if match is not None: order = int(match.group(1)) return order diff --git a/src/calibre/devices/usbms/device.py b/src/calibre/devices/usbms/device.py index 2c095d6f7b..4711a8eec4 100644 --- a/src/calibre/devices/usbms/device.py +++ b/src/calibre/devices/usbms/device.py @@ -11,7 +11,7 @@ intended to be subclassed with the relevant parts implemented for a particular device. This class handles device detection. ''' -import os, subprocess, time, re, sys, glob, operator +import os, subprocess, time, re, sys, glob from itertools import repeat from calibre.devices.interface import DevicePlugin @@ -225,7 +225,7 @@ class Device(DeviceConfig, DevicePlugin): return False def open_windows(self): - from calibre.devices.scanner import win_pnp_drives + from calibre.devices.scanner import win_pnp_drives, drivecmp time.sleep(5) drives = {} @@ -263,7 +263,7 @@ class Device(DeviceConfig, DevicePlugin): if self.WINDOWS_MAIN_MEM in (self.WINDOWS_CARD_A_MEM, self.WINDOWS_CARD_B_MEM) or \ self.WINDOWS_CARD_A_MEM == self.WINDOWS_CARD_B_MEM: - letters = sorted(drives.values(), key=operator.attrgetter('order')) + letters = sorted(drives.values(), cmp=drivecmp) drives = {} for which, letter in zip(['main', 'carda', 'cardb'], letters): drives[which] = letter diff --git a/src/calibre/ebooks/pdf/writer.py b/src/calibre/ebooks/pdf/writer.py index 8938dd66c1..b0884417f6 100644 --- a/src/calibre/ebooks/pdf/writer.py +++ b/src/calibre/ebooks/pdf/writer.py @@ -175,7 +175,7 @@ class PDFWriter(QObject): # {{{ if self.cover_data is None: return item_path = os.path.join(self.tmp_path, 'cover.pdf') - printer = self.get_pdf_printer() + printer = get_pdf_printer(self.opts) printer.setOutputFileName(item_path) self.combine_queue.insert(0, item_path) p = QPixmap() diff --git a/src/calibre/gui2/__init__.py b/src/calibre/gui2/__init__.py index 1c99d9d9d5..df6ac45e5b 100644 --- a/src/calibre/gui2/__init__.py +++ b/src/calibre/gui2/__init__.py @@ -53,6 +53,8 @@ gprefs.defaults['toolbar_icon_size'] = 'medium' gprefs.defaults['toolbar_text'] = 'auto' gprefs.defaults['show_child_bar'] = False gprefs.defaults['font'] = None +gprefs.defaults['tags_browser_partition_method'] = 'first letter' +gprefs.defaults['tags_browser_collapse_at'] = 100 # }}} diff --git a/src/calibre/gui2/convert/gui_conversion.py b/src/calibre/gui2/convert/gui_conversion.py index 116f09e429..d1e1270924 100644 --- a/src/calibre/gui2/convert/gui_conversion.py +++ b/src/calibre/gui2/convert/gui_conversion.py @@ -30,7 +30,7 @@ def gui_catalog(fmt, title, dbspec, ids, out_file_name, sync, fmt_options, conne from calibre.library import db from calibre.utils.config import prefs prefs.refresh() - db = db() + db = db(read_only=True) db.catalog_plugin_on_device_temp_mapping = dbspec # Create a minimal OptionParser that we can append to diff --git a/src/calibre/gui2/dialogs/book_info.py b/src/calibre/gui2/dialogs/book_info.py index eac8461299..4da897920c 100644 --- a/src/calibre/gui2/dialogs/book_info.py +++ b/src/calibre/gui2/dialogs/book_info.py @@ -23,7 +23,7 @@ class BookInfo(QDialog, Ui_BookInfo): self.cover_pixmap = None self.comments.sizeHint = self.comments_size_hint self.comments.page().setLinkDelegationPolicy(self.comments.page().DelegateAllLinks) - self.comments.linkClicked(self.link_clicked) + self.comments.linkClicked.connect(self.link_clicked) self.view_func = view_func diff --git a/src/calibre/gui2/library/views.py b/src/calibre/gui2/library/views.py index 322199a4f9..c1dd5b3766 100644 --- a/src/calibre/gui2/library/views.py +++ b/src/calibre/gui2/library/views.py @@ -386,7 +386,12 @@ class BooksView(QTableView): # {{{ old_state = self.get_default_state() if tweaks['sort_columns_at_startup'] is not None: - old_state['sort_history'] = tweaks['sort_columns_at_startup'] + sh = [] + for c,d in tweaks['sort_columns_at_startup']: + if not isinstance(d, bool): + d = True if d == 0 else False + sh.append((c, d)) + old_state['sort_history'] = sh self.apply_state(old_state) diff --git a/src/calibre/gui2/main.py b/src/calibre/gui2/main.py index 371c4df701..c8b5cb001e 100644 --- a/src/calibre/gui2/main.py +++ b/src/calibre/gui2/main.py @@ -303,7 +303,7 @@ def run_gui(opts, args, actions, listener, app, gui_debug=None): runner.main.system_tray_icon.hide() except: pass - if runner.main.gui_debug is not None: + if getattr(runner.main, 'gui_debug', None) is not None: e = sys.executable if getattr(sys, 'frozen', False) else sys.argv[0] import subprocess creationflags = 0 diff --git a/src/calibre/gui2/preferences/look_feel.py b/src/calibre/gui2/preferences/look_feel.py index de1116c231..263d19325d 100644 --- a/src/calibre/gui2/preferences/look_feel.py +++ b/src/calibre/gui2/preferences/look_feel.py @@ -57,6 +57,11 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form): (_('Never'), 'never')] r('toolbar_text', gprefs, choices=choices) + choices = [(_('Disabled'), 'disabled'), (_('By first letter'), 'first letter'), + (_('Partitioned'), 'partition')] + r('tags_browser_partition_method', gprefs, choices=choices) + r('tags_browser_collapse_at', gprefs) + self.current_font = None self.change_font_button.clicked.connect(self.change_font) @@ -113,6 +118,7 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form): def refresh_gui(self, gui): gui.search.search_as_you_type(config['search_as_you_type']) self.update_font_display() + gui.tags_view.reread_collapse_parameters() if __name__ == '__main__': app = QApplication([]) diff --git a/src/calibre/gui2/preferences/look_feel.ui b/src/calibre/gui2/preferences/look_feel.ui index 8e57f8c17e..2223167068 100644 --- a/src/calibre/gui2/preferences/look_feel.ui +++ b/src/calibre/gui2/preferences/look_feel.ui @@ -7,7 +7,7 @@ 0 0 670 - 385 + 392 @@ -142,6 +142,65 @@ + + + + + Tags browser category partitioning method: + + + opt_tags_browser_partition_method + + + + + + + Choose how tag browser subcategories are displayed when +there are more items than the limit. Select by first +letter to see an A, B, C list. Choose partitioned to +have a list of fixed-sized groups. Set to disabled +if you never want subcategories + + + + + + + Collapse when more items than: + + + opt_tags_browser_collapse_at + + + + + + + If a Tag Browser category has more than this number of items, it is divided +up into sub-categories. If the partition method is set to disable, this value is ignored. + + + 10000 + + + + + + + Qt::Horizontal + + + + 20 + 5 + + + + + + + &Toolbar @@ -183,7 +242,7 @@ - + @@ -204,14 +263,14 @@ - + Change &font (needs restart) - + Qt::Vertical diff --git a/src/calibre/gui2/tag_view.py b/src/calibre/gui2/tag_view.py index f04902283e..90d7ce698a 100644 --- a/src/calibre/gui2/tag_view.py +++ b/src/calibre/gui2/tag_view.py @@ -17,7 +17,7 @@ from PyQt4.Qt import Qt, QTreeView, QApplication, pyqtSignal, QFont, QSize, \ QShortcut, QKeySequence, SIGNAL from calibre.ebooks.metadata import title_sort -from calibre.gui2 import config, NONE +from calibre.gui2 import config, NONE, gprefs from calibre.library.field_metadata import TagsIcons, category_icon_map from calibre.utils.config import tweaks from calibre.utils.icu import sort_key, upper, lower, strcmp @@ -94,6 +94,10 @@ class TagsView(QTreeView): # {{{ self.setDropIndicatorShown(True) self.setAutoExpandDelay(500) self.pane_is_visible = False + if gprefs['tags_browser_collapse_at'] == 0: + self.collapse_model = 'disable' + else: + self.collapse_model = gprefs['tags_browser_partition_method'] def set_pane_is_visible(self, to_what): pv = self.pane_is_visible @@ -101,12 +105,20 @@ class TagsView(QTreeView): # {{{ if to_what and not pv: self.recount() + def reread_collapse_parameters(self): + if gprefs['tags_browser_collapse_at'] == 0: + self.collapse_model = 'disable' + else: + self.collapse_model = gprefs['tags_browser_partition_method'] + self.set_new_model(self._model.get_filter_categories_by()) + def set_database(self, db, tag_match, sort_by): self.hidden_categories = config['tag_browser_hidden_categories'] self._model = TagsModel(db, parent=self, hidden_categories=self.hidden_categories, search_restriction=None, - drag_drop_finished=self.drag_drop_finished) + drag_drop_finished=self.drag_drop_finished, + collapse_model=self.collapse_model) self.pane_is_visible = True # because TagsModel.init did a recount self.sort_by = sort_by self.tag_match = tag_match @@ -194,6 +206,12 @@ class TagsView(QTreeView): # {{{ self.hidden_categories.add(category) elif action == 'show': self.hidden_categories.discard(category) + elif action == 'categorization': + changed = self.collapse_model != category + self.collapse_model = category + if changed: + self.set_new_model(self._model.get_filter_categories_by()) + gprefs['tags_browser_partition_method'] = category elif action == 'defaults': self.hidden_categories.clear() config.set('tag_browser_hidden_categories', self.hidden_categories) @@ -216,6 +234,8 @@ class TagsView(QTreeView): # {{{ item = item.parent if item.type == TagTreeItem.CATEGORY: + while item.parent != self._model.root_item: + item = item.parent category = unicode(item.name.toString()) key = item.category_key # Verify that we are working with a field that we know something about @@ -277,6 +297,23 @@ class TagsView(QTreeView): # {{{ self.context_menu.addAction(_('Show all categories'), partial(self.context_menu_handler, action='defaults')) + m = self.context_menu.addMenu(_('Change sub-categorization scheme')) + da = m.addAction('Disable', + partial(self.context_menu_handler, action='categorization', category='disable')) + fla = m.addAction('By first letter', + partial(self.context_menu_handler, action='categorization', category='first letter')) + pa = m.addAction('Partition', + partial(self.context_menu_handler, action='categorization', category='partition')) + if self.collapse_model == 'disable': + da.setCheckable(True) + da.setChecked(True) + elif self.collapse_model == 'first letter': + fla.setCheckable(True) + fla.setChecked(True) + else: + pa.setCheckable(True) + pa.setChecked(True) + if not self.context_menu.isEmpty(): self.context_menu.popup(self.mapToGlobal(point)) return True @@ -338,7 +375,8 @@ class TagsView(QTreeView): # {{{ hidden_categories=self.hidden_categories, search_restriction=self.search_restriction, drag_drop_finished=self.drag_drop_finished, - filter_categories_by=filter_categories_by) + filter_categories_by=filter_categories_by, + collapse_model=self.collapse_model) self.setModel(self._model) except: # The DB must be gone. Set the model to None and hope that someone @@ -467,7 +505,7 @@ class TagsModel(QAbstractItemModel): # {{{ def __init__(self, db, parent, hidden_categories=None, search_restriction=None, drag_drop_finished=None, - filter_categories_by=None): + filter_categories_by=None, collapse_model='disable'): QAbstractItemModel.__init__(self, parent) # must do this here because 'QPixmap: Must construct a QApplication @@ -488,6 +526,7 @@ class TagsModel(QAbstractItemModel): # {{{ self.search_restriction = search_restriction self.row_map = [] self.filter_categories_by = filter_categories_by + self.collapse_model = collapse_model # get_node_tree cannot return None here, because row_map is empty data = self.get_node_tree(config['sort_tags_by']) @@ -678,16 +717,19 @@ class TagsModel(QAbstractItemModel): # {{{ if data is None: return False row_index = -1 - collapse = tweaks['categories_collapse_more_than'] - collapse_model = tweaks['categories_collapse_model'] - if sort_by == 'name': - collapse_template = tweaks['categories_collapsed_name_template'] - elif sort_by == 'rating': - collapse_model = 'partition' - collapse_template = tweaks['categories_collapsed_rating_template'] - else: - collapse_model = 'partition' - collapse_template = tweaks['categories_collapsed_popularity_template'] + collapse = gprefs['tags_browser_collapse_at'] + collapse_model = self.collapse_model + if collapse == 0: + collapse_model = 'disable' + elif collapse_model != 'disable': + if sort_by == 'name': + collapse_template = tweaks['categories_collapsed_name_template'] + elif sort_by == 'rating': + collapse_model = 'partition' + collapse_template = tweaks['categories_collapsed_rating_template'] + else: + collapse_model = 'partition' + collapse_template = tweaks['categories_collapsed_popularity_template'] collapse_letter = None for i, r in enumerate(self.row_map): @@ -722,7 +764,7 @@ class TagsModel(QAbstractItemModel): # {{{ tag.avg_rating = None tag.state = state_map.get(tag.name, 0) - if collapse > 0 and cat_len > collapse: + if collapse_model != 'disable' and cat_len > collapse: if collapse_model == 'partition': if (idx % collapse) == 0: d = {'first': tag} @@ -737,8 +779,11 @@ class TagsModel(QAbstractItemModel): # {{{ category_icon = category_node.icon, category_key=category_node.category_key) else: - if upper(tag.sort[0]) != collapse_letter: - collapse_letter = upper(tag.name[0]) + ts = tag.sort + if not ts: + ts = ' ' + if upper(ts[0]) != collapse_letter: + collapse_letter = upper(ts[0]) sub_cat = TagTreeItem(parent=category, data = collapse_letter, category_icon = category_node.icon, diff --git a/src/calibre/gui2/wizard/__init__.py b/src/calibre/gui2/wizard/__init__.py index 4f418d34d5..4e5e79bbdf 100644 --- a/src/calibre/gui2/wizard/__init__.py +++ b/src/calibre/gui2/wizard/__init__.py @@ -174,7 +174,7 @@ class CybookOrizon(CybookOpus): class PocketBook360(CybookOpus): manufacturer = 'PocketBook' - name = 'PocketBook 360' + name = 'PocketBook 360 and newer models' id = 'pocketbook360' output_profile = 'cybook_opus' diff --git a/src/calibre/library/__init__.py b/src/calibre/library/__init__.py index d08ca0b44f..2e00db32c4 100644 --- a/src/calibre/library/__init__.py +++ b/src/calibre/library/__init__.py @@ -2,10 +2,11 @@ __license__ = 'GPL v3' __copyright__ = '2008, Kovid Goyal ' ''' Code to manage ebook library''' -def db(path=None): +def db(path=None, read_only=False): from calibre.library.database2 import LibraryDatabase2 from calibre.utils.config import prefs - return LibraryDatabase2(path if path else prefs['library_path']) + return LibraryDatabase2(path if path else prefs['library_path'], + read_only=read_only) def generate_test_db(library_path, # {{{ diff --git a/src/calibre/library/catalog.py b/src/calibre/library/catalog.py index 5f771a8a6d..dd1a9e2e7d 100644 --- a/src/calibre/library/catalog.py +++ b/src/calibre/library/catalog.py @@ -4,14 +4,13 @@ __license__ = 'GPL v3' __copyright__ = '2010, Greg Riker' import codecs, datetime, htmlentitydefs, os, re, shutil, time, zlib -from contextlib import closing from collections import namedtuple from copy import deepcopy from xml.sax.saxutils import escape from lxml import etree from calibre import prints, prepare_string_for_xml, strftime -from calibre.constants import preferred_encoding +from calibre.constants import preferred_encoding, DEBUG from calibre.customize import CatalogPlugin from calibre.customize.conversion import OptionRecommendation, DummyReporter from calibre.ebooks.BeautifulSoup import BeautifulSoup, BeautifulStoneSoup, Tag, NavigableString @@ -33,7 +32,7 @@ FIELDS = ['all', 'author_sort', 'authors', 'comments', TEMPLATE_ALLOWED_FIELDS = [ 'author_sort', 'authors', 'id', 'isbn', 'pubdate', 'publisher', 'series_index', 'series', 'tags', 'timestamp', 'title', 'uuid' ] -class CSV_XML(CatalogPlugin): +class CSV_XML(CatalogPlugin): # {{{ 'CSV/XML catalog generator' Option = namedtuple('Option', 'option, default, dest, action, help') @@ -209,8 +208,9 @@ class CSV_XML(CatalogPlugin): with open(path_to_output, 'w') as f: f.write(etree.tostring(root, encoding='utf-8', xml_declaration=True, pretty_print=True)) +# }}} -class BIBTEX(CatalogPlugin): +class BIBTEX(CatalogPlugin): # {{{ 'BIBTEX catalog generator' Option = namedtuple('Option', 'option, default, dest, action, help') @@ -535,6 +535,7 @@ class BIBTEX(CatalogPlugin): bibtexc, citation_bibtex)) outfile.close() +# }}} class EPUB_MOBI(CatalogPlugin): 'ePub catalog generator' @@ -991,12 +992,10 @@ class EPUB_MOBI(CatalogPlugin): if not os.path.exists(self.__archive_path): self.opts.log.info(' creating thumbnail archive, thumb_width: %1.2f"' % float(self.opts.thumb_width)) - zfw = ZipFile(self.__archive_path, mode='w') - zfw.writestr("Catalog Thumbs Archive",'') - #zfw.comment = "thumb_width: %1.2f" % float(self.opts.thumb_width) - zfw.close() + with ZipFile(self.__archive_path, mode='w') as zfw: + zfw.writestr("Catalog Thumbs Archive",'') else: - with closing(ZipFile(self.__archive_path, mode='r')) as zfr: + with ZipFile(self.__archive_path, mode='r') as zfr: try: cached_thumb_width = zfr.read('thumb_width') except: @@ -1006,8 +1005,7 @@ class EPUB_MOBI(CatalogPlugin): self.opts.log.warning(" invalidating cache at '%s'" % self.__archive_path) self.opts.log.warning(' thumb_width changed: %1.2f" => %1.2f"' % (float(cached_thumb_width),float(self.opts.thumb_width))) - os.remove(self.__archive_path) - with closing(ZipFile(self.__archive_path, mode='w')) as zfw: + with ZipFile(self.__archive_path, mode='w') as zfw: zfw.writestr("Catalog Thumbs Archive",'') else: self.opts.log.info(' existing thumb cache at %s, cached_thumb_width: %1.2f"' % @@ -1917,34 +1915,35 @@ class EPUB_MOBI(CatalogPlugin): aTag['name'] = anchor_name.replace(" ","") body.insert(btc, aTag) btc += 1 - ''' - # We don't need this because the kindle inserts section titles - #

By Author

- h2Tag = Tag(soup, "h2") - aTag = Tag(soup, "a") - anchor_name = friendly_name.lower() - aTag['name'] = anchor_name.replace(" ","") - h2Tag.insert(0,aTag) - h2Tag.insert(1,NavigableString('%s' % friendly_name)) - body.insert(btc,h2Tag) - btc += 1 - ''' #

#

divTag = Tag(soup, "div") dtc = 0 - current_letter = "" - current_author = "" - current_series = None + divOpeningTag = None + dotc = 0 + divRunningTag = None + drtc = 0 # Loop through booksByAuthor book_count = 0 - divRunningTag = None + current_author = '' + current_letter = '' + current_series = None for book in self.booksByAuthor: book_count += 1 if self.letter_or_symbol(book['author_sort'][0].upper()) != current_letter : # Start a new letter with Index letter + if divOpeningTag is not None: + divTag.insert(dtc, divOpeningTag) + dtc += 1 + dotc = 0 + if divRunningTag is not None: + divTag.insert(dtc, divRunningTag) + dtc += 1 + drtc = 0 + divRunningTag = None + current_letter = self.letter_or_symbol(book['author_sort'][0].upper()) author_count = 0 divOpeningTag = Tag(soup, 'div') @@ -1964,16 +1963,16 @@ class EPUB_MOBI(CatalogPlugin): current_author = book['author'] author_count += 1 if author_count == 2: - # Add divOpeningTag to divTag + # Add divOpeningTag to divTag, kill divOpeningTag divTag.insert(dtc, divOpeningTag) dtc += 1 - elif author_count > 2 and divRunningTag is not None: - divTag.insert(dtc, divRunningTag) - dtc += 1 + divOpeningTag = None + dotc = 0 - divRunningTag = Tag(soup, 'div') - divRunningTag['style'] = 'display:inline-block;width:100%' - drtc = 0 + # Create a divRunningTag for the rest of the authors in this letter + divRunningTag = Tag(soup, 'div') + divRunningTag['style'] = 'display:inline-block;width:100%' + drtc = 0 non_series_books = 0 current_series = None @@ -1986,7 +1985,7 @@ class EPUB_MOBI(CatalogPlugin): if author_count == 1: divOpeningTag.insert(dotc, pAuthorTag) dotc += 1 - elif divRunningTag is not None: + else: divRunningTag.insert(drtc,pAuthorTag) drtc += 1 @@ -2059,10 +2058,12 @@ class EPUB_MOBI(CatalogPlugin): if author_count == 1: divOpeningTag.insert(dotc, pBookTag) dotc += 1 - elif divRunningTag is not None: + else: divRunningTag.insert(drtc,pBookTag) drtc += 1 + # Loop ends here + if not self.__generateForKindle: # Insert the

tag with book_count at the head #

By Author

@@ -2080,6 +2081,9 @@ class EPUB_MOBI(CatalogPlugin): if author_count == 1: divTag.insert(dtc, divOpeningTag) dtc += 1 + elif divRunningTag is not None: + divTag.insert(dtc, divRunningTag) + dtc += 1 # Add the divTag to the body body.insert(btc, divTag) @@ -2907,7 +2911,7 @@ class EPUB_MOBI(CatalogPlugin): # Write the thumb_width to the file validating cache contents # Allows detection of aborted catalog builds - with closing(ZipFile(self.__archive_path, mode='a'))as zfw: + with ZipFile(self.__archive_path, mode='a') as zfw: zfw.writestr('thumb_width', self.opts.thumb_width) self.thumbs = thumbs @@ -4389,21 +4393,21 @@ class EPUB_MOBI(CatalogPlugin): ''' # Publisher - publisher = NBSP + publisher = '' if 'publisher' in book: publisher = book['publisher'] # Rating stars = int(book['rating']) / 2 - rating = NBSP + rating = '' if stars: star_string = self.FULL_RATING_SYMBOL * stars empty_stars = self.EMPTY_RATING_SYMBOL * (5 - stars) rating = '%s%s
' % (star_string,empty_stars) # Notes - note_source = NBSP - note_content = NBSP + note_source = '' + note_content = '' if 'notes' in book: note_source = book['notes']['source'] note_content = book['notes']['content'] @@ -4445,7 +4449,7 @@ class EPUB_MOBI(CatalogPlugin): formatsTag = body.find('p',attrs={'class':'formats'}) formatsTag.extract() - if note_content == NBSP: + if note_content == '': tdTag = body.find('td', attrs={'class':'notes'}) tdTag.contents[0].replaceWith(NBSP) @@ -4459,6 +4463,18 @@ class EPUB_MOBI(CatalogPlugin): imgTag['alt'] = "cover thumbnail" tdTag.insert(0,imgTag) + ''' + # Rating + stars = int(book['rating']) / 2 + rating = '' + if stars: + star_string = self.FULL_RATING_SYMBOL * stars + empty_stars = self.EMPTY_RATING_SYMBOL * (5 - stars) + rating = '%s%s
' % (star_string,empty_stars) + ratingTag = body.find('td',attrs={'class':'rating'}) + ratingTag.insert(0,NavigableString(rating)) + ''' + # The Blurb if 'description' in book and book['description'] > '': blurbTag = body.find(attrs={'class':'description'}) @@ -4653,7 +4669,7 @@ class EPUB_MOBI(CatalogPlugin): cover_crc = hex(zlib.crc32(data)) # Test cache for uuid - with closing(ZipFile(self.__archive_path, mode='r')) as zfr: + with ZipFile(self.__archive_path, mode='r') as zfr: try: t_info = zfr.getinfo(title['uuid']) except: @@ -4677,9 +4693,8 @@ class EPUB_MOBI(CatalogPlugin): # Save thumb to archive t_info = ZipInfo(title['uuid'],time.localtime()[0:6]) t_info.comment = cover_crc - zfw = ZipFile(self.__archive_path, mode='a') - zfw.writestr(t_info, thumb_data) - zfw.close() + with ZipFile(self.__archive_path, mode='a') as zfw: + zfw.writestr(t_info, thumb_data) def getFriendlyGenreTag(self, genre): # Find the first instance of friendly_tag matching genre @@ -4822,6 +4837,8 @@ class EPUB_MOBI(CatalogPlugin): addendum = self.__db.get_field(record['id'], self.__merge_comments['field'], index_is_id=True) + if addendum is None: + addendum = '' include_hr = eval(self.__merge_comments['hr']) if self.__merge_comments['position'] == 'before': merged = addendum @@ -5027,8 +5044,11 @@ class EPUB_MOBI(CatalogPlugin): if catalog_source_built: recommendations = [] - recommendations.append(('comments', '\n'.join(line for line in build_log), + if DEBUG: + recommendations.append(('comments', '\n'.join(line for line in build_log), OptionRecommendation.HIGH)) + else: + recommendations.append(('comments', '', OptionRecommendation.HIGH)) dp = getattr(opts, 'debug_pipeline', None) if dp is not None: diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py index cbda615677..611aa1cc89 100644 --- a/src/calibre/library/database2.py +++ b/src/calibre/library/database2.py @@ -102,7 +102,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): if self.user_version == 0: self.initialize_database() # remember to add any filter to the connect method in sqlite.py as well - # so that various code taht connects directly will not complain about + # so that various code that connects directly will not complain about # missing functions self.books_list_filter = self.conn.create_dynamic_filter('books_list_filter') # Store temporary tables in memory @@ -113,7 +113,8 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): def exists_at(cls, path): return path and os.path.exists(os.path.join(path, 'metadata.db')) - def __init__(self, library_path, row_factory=False, default_prefs=None): + def __init__(self, library_path, row_factory=False, default_prefs=None, + read_only=False): self.field_metadata = FieldMetadata() self.dirtied_queue = Queue() if not os.path.exists(library_path): @@ -127,6 +128,14 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): if isinstance(self.dbpath, unicode) and not iswindows: self.dbpath = self.dbpath.encode(filesystem_encoding) + if read_only and os.path.exists(self.dbpath): + # Work on only a copy of metadata.db to ensure that + # metadata.db is not changed + pt = PersistentTemporaryFile('_metadata_ro.db') + pt.close() + shutil.copyfile(self.dbpath, pt.name) + self.dbpath = pt.name + apply_default_prefs = not os.path.exists(self.dbpath) self.connect() @@ -2769,7 +2778,6 @@ books_series_link feeds os.remove(dest) raise else: - os.remove(self.dbpath) shutil.copyfile(dest, self.dbpath) self.connect() self.initialize_dynamic() diff --git a/src/calibre/utils/zipfile.py b/src/calibre/utils/zipfile.py index 7f6e97f7d2..ff290abd25 100644 --- a/src/calibre/utils/zipfile.py +++ b/src/calibre/utils/zipfile.py @@ -1258,6 +1258,12 @@ class ZipFile: """Call the "close()" method in case the user forgot.""" self.close() + def __enter__(self): + return self + + def __exit__(self, typ, value, traceback): + self.close() + def close(self): """Close the file, and for mode "w" and "a" write the ending records."""