mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Sync to trunk.
This commit is contained in:
commit
46bc5c3f56
@ -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"
|
||||
|
@ -14,10 +14,10 @@
|
||||
<table width="100%" border="0">
|
||||
<tr>
|
||||
<td class="thumbnail" rowspan="7"></td>
|
||||
<td> </td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td> </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> </td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</table>
|
||||
<hr class="description_divider" />
|
||||
|
@ -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
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
|
||||
# }}}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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
|
||||
|
@ -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([])
|
||||
|
@ -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>&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 &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>
|
||||
|
@ -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,
|
||||
|
@ -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'
|
||||
|
||||
|
@ -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, # {{{
|
||||
|
@ -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:
|
||||
|
@ -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()
|
||||
|
@ -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."""
|
||||
|
Loading…
x
Reference in New Issue
Block a user