Sync to trunk.

This commit is contained in:
John Schember 2011-01-02 17:23:19 -05:00
commit 46bc5c3f56
20 changed files with 258 additions and 100 deletions

View File

@ -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"

View File

@ -14,10 +14,10 @@
<table width="100%" border="0">
<tr>
<td class="thumbnail" rowspan="7"></td>
<td>&#160;</td>
<td></td>
</tr>
<tr>
<td>&#160;</td>
<td></td>
</tr>
<tr>
<td class="publisher">{publisher}</td>
@ -32,7 +32,7 @@
<td class="notes">{note_source}: {note_content}</td>
</tr>
<tr>
<td>&#160;</td>
<td></td>
</tr>
</table>
<hr class="description_divider" />

View File

@ -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

View File

@ -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:

View File

@ -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

View File

@ -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

View File

@ -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()

View File

@ -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
# }}}

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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([])

View File

@ -7,7 +7,7 @@
<x>0</x>
<y>0</y>
<width>670</width>
<height>385</height>
<height>392</height>
</rect>
</property>
<property name="windowTitle">
@ -142,6 +142,65 @@
</widget>
</item>
<item row="7" column="0" colspan="2">
<layout class="QHBoxLayout">
<item>
<widget class="QLabel" name="label_6">
<property name="text">
<string>Tags browser category partitioning method:</string>
</property>
<property name="buddy">
<cstring>opt_tags_browser_partition_method</cstring>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="opt_tags_browser_partition_method">
<property name="toolTip">
<string>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</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_6">
<property name="text">
<string>Collapse when more items than:</string>
</property>
<property name="buddy">
<cstring>opt_tags_browser_collapse_at</cstring>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="opt_tags_browser_collapse_at">
<property name="toolTip">
<string>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.</string>
</property>
<property name="maximum">
<number>10000</number>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>5</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item row="15" column="0" colspan="2">
<widget class="QGroupBox" name="groupBox_2">
<property name="title">
<string>&amp;Toolbar</string>
@ -183,7 +242,7 @@
</layout>
</widget>
</item>
<item row="8" column="0">
<item row="16" column="0">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="label_2">
@ -204,14 +263,14 @@
</item>
</layout>
</item>
<item row="8" column="1">
<item row="16" column="1">
<widget class="QPushButton" name="change_font_button">
<property name="text">
<string>Change &amp;font (needs restart)</string>
</property>
</widget>
</item>
<item row="9" column="0" colspan="2">
<item row="17" column="0" colspan="2">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>

View File

@ -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,

View File

@ -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'

View File

@ -2,10 +2,11 @@ __license__ = 'GPL v3'
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
''' 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, # {{{

View File

@ -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
#<h2><a name="byalphaauthor" id="byalphaauthor"></a>By Author</h2>
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
'''
# <p class="letter_index">
# <p class="author_index">
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 <h2> tag with book_count at the head
#<h2><a name="byalphaauthor" id="byalphaauthor"></a>By Author</h2>
@ -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 <br/>' % (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 <br/>' % (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:

View File

@ -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()

View File

@ -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."""