0.7.22 update

This commit is contained in:
GRiker 2010-10-03 16:11:32 -07:00
commit b57222a01c
58 changed files with 45676 additions and 31403 deletions

View File

@ -4,6 +4,52 @@
# for important features/bug fixes. # for important features/bug fixes.
# Also, each release can have new and improved recipes. # Also, each release can have new and improved recipes.
- version: 0.7.22
date: 2010-10-03
new features:
- title: "Drag and drop books from your calibre library"
type: major
description: >
"You can now drag and drop books from your calibre library. You can drag them to the desktop or to a file explorer, to copy them to your computer. You can drag them to the
device icon in calibre to send them to the device. You can also drag and drop books from the device view in calibre to the calibre library icon or the operating
system to copy them from the device."
- title: "There were many minor bug fixes for various bugs caused by the major changes in 0.7.21. So if you have updated to 0.7.21, it is highly recommended you update to 0.7.22"
- title: "Driver for the VelocityMicro ebook reader device"
- title: "Add a tweak to control how articles in titles are processed during sorting"
- title: "Add a new format type 'device_db' to plugboards to control the metadata displayed in book lists on SONY devices."
bug fixes:
- title: "Fix ISBN not being read from filenames in 0.7.21"
tickets: [7054]
- title: "Fix instant Search for text not found causes unhandled exception when conversion jobs are running"
tickets: [7043]
- title: "Fix removing a publisher causes an error in 0.7.21"
tickets: [7046]
- title: "MOBI Output: Fix some images being distorted in 0.7.21"
tickets: [7049]
- title: "Fix regression that broke bulk conversion of books without covers in 0.7.21"
- title: "Fix regression that broke add and set_metadata commands in calibredb in 0.7.21"
- title: "Workaround for Qt bug in file open dialogs in linux that causes multiple file selection to ignore files with two or more spaces in the file name"
- title: "Conversion pipeline: Fix regression in 0.7.21 that broke conversion of LIT/EPUB documents that specified no title in their OPF files"
- title: "Fix regression that broke iPad driver in 0.7.21"
improved recipes:
- Washington Post
- version: 0.7.21 - version: 0.7.21
date: 2010-10-01 date: 2010-10-01

View File

@ -83,6 +83,16 @@ title_series_sorting = 'library_order'
# strictly_alphabetic, it would remain "The Client". # strictly_alphabetic, it would remain "The Client".
save_template_title_series_sorting = 'library_order' save_template_title_series_sorting = 'library_order'
# Set the list of words that are to be considered 'articles' when computing the
# title sort strings. The list is a regular expression, with the articles
# separated by 'or' bars. Comparisons are case insensitive, and that cannot be
# changed. Changes to this tweak won't have an effect until the book is modified
# in some way. If you enter an invalid pattern, it is silently ignored.
# To disable use the expression: '^$'
# Default: '^(A|The|An)\s+'
title_sort_articles=r'^(A|The|An)\s+'
# Specify a folder that calibre should connect to at startup using # Specify a folder that calibre should connect to at startup using
# connect_to_folder. This must be a full path to the folder. If the folder does # connect_to_folder. This must be a full path to the folder. If the folder does
# not exist when calibre starts, it is ignored. If there are '\' characters in # not exist when calibre starts, it is ignored. If there are '\' characters in

View File

@ -42,7 +42,7 @@ class RMF24_opinie(BasicNewsRecipe):
# thanks to Kovid Goyal # thanks to Kovid Goyal
def get_article_url(self, article): def get_article_url(self, article):
link = article.get('link') link = article.get('link')
if 'audio' not in link: if '/audio,aId' not in link:
return link return link
preprocess_regexps = [ preprocess_regexps = [

View File

@ -2,7 +2,7 @@ __license__ = 'GPL v3'
__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net' __copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
__docformat__ = 'restructuredtext en' __docformat__ = 'restructuredtext en'
__appname__ = 'calibre' __appname__ = 'calibre'
__version__ = '0.7.21' __version__ = '0.7.22'
__author__ = "Kovid Goyal <kovid@kovidgoyal.net>" __author__ = "Kovid Goyal <kovid@kovidgoyal.net>"
import re import re

View File

@ -460,7 +460,8 @@ from calibre.devices.hanvon.driver import N516, EB511, ALEX, AZBOOKA, THEBOOK
from calibre.devices.edge.driver import EDGE from calibre.devices.edge.driver import EDGE
from calibre.devices.teclast.driver import TECLAST_K3, NEWSMY, IPAPYRUS, SOVOS from calibre.devices.teclast.driver import TECLAST_K3, NEWSMY, IPAPYRUS, SOVOS
from calibre.devices.sne.driver import SNE from calibre.devices.sne.driver import SNE
from calibre.devices.misc import PALMPRE, AVANT, SWEEX, PDNOVEL, KOGAN, GEMEI from calibre.devices.misc import PALMPRE, AVANT, SWEEX, PDNOVEL, KOGAN, \
GEMEI, VELOCITYMICRO
from calibre.devices.folder_device.driver import FOLDER_DEVICE_FOR_CONFIG from calibre.devices.folder_device.driver import FOLDER_DEVICE_FOR_CONFIG
from calibre.devices.kobo.driver import KOBO from calibre.devices.kobo.driver import KOBO
@ -572,6 +573,7 @@ plugins += [
PDNOVEL, PDNOVEL,
SPECTRA, SPECTRA,
GEMEI, GEMEI,
VELOCITYMICRO,
ITUNES, ITUNES,
] ]
plugins += [x for x in list(locals().values()) if isinstance(x, type) and \ plugins += [x for x in list(locals().values()) if isinstance(x, type) and \

View File

@ -108,6 +108,24 @@ class PDNOVEL(USBMS):
with open('%s.jpg' % os.path.join(path, filename), 'wb') as coverfile: with open('%s.jpg' % os.path.join(path, filename), 'wb') as coverfile:
coverfile.write(coverdata[2]) coverfile.write(coverdata[2])
class VELOCITYMICRO(USBMS):
name = 'VelocityMicro device interface'
gui_name = 'VelocityMicro'
description = _('Communicate with the VelocityMicro')
author = 'Kovid Goyal'
supported_platforms = ['windows', 'linux', 'osx']
FORMATS = ['epub', 'pdb', 'txt', 'html', 'pdf']
VENDOR_ID = [0x18d1]
PRODUCT_ID = [0xb015]
BCD = [0x224]
VENDOR_NAME = 'ANDROID'
WINDOWS_MAIN_MEM = WINDOWS_CARD_A_MEM = '__UMS_COMPOSITE'
EBOOK_DIR_MAIN = 'eBooks'
SUPPORTS_SUB_DIRS = False
class GEMEI(USBMS): class GEMEI(USBMS):
name = 'Gemei Device Interface' name = 'Gemei Device Interface'
gui_name = 'GM2000' gui_name = 'GM2000'

View File

@ -63,6 +63,8 @@ class PRS505(USBMS):
'series, tags, authors' 'series, tags, authors'
EXTRA_CUSTOMIZATION_DEFAULT = ', '.join(['series', 'tags']) EXTRA_CUSTOMIZATION_DEFAULT = ', '.join(['series', 'tags'])
plugboard = None
def windows_filter_pnp_id(self, pnp_id): def windows_filter_pnp_id(self, pnp_id):
return '_LAUNCHER' in pnp_id return '_LAUNCHER' in pnp_id
@ -150,7 +152,7 @@ class PRS505(USBMS):
else: else:
collections = [] collections = []
debug_print('PRS505: collection fields:', collections) debug_print('PRS505: collection fields:', collections)
c.update(blists, collections) c.update(blists, collections, self.plugboard)
c.write() c.write()
USBMS.sync_booklists(self, booklists, end_session=end_session) USBMS.sync_booklists(self, booklists, end_session=end_session)
@ -163,3 +165,9 @@ class PRS505(USBMS):
c.write() c.write()
debug_print('PRS505: finished rebuild_collections') debug_print('PRS505: finished rebuild_collections')
def use_plugboard_ext(self):
return 'device_db'
def set_plugboard(self, pb):
debug_print('PRS505: use plugboard', pb)
self.plugboard = pb

View File

@ -325,12 +325,6 @@ class XMLCache(object):
for book in bl: for book in bl:
record = lpath_map.get(book.lpath, None) record = lpath_map.get(book.lpath, None)
if record is not None: if record is not None:
title = record.get('title', None)
if title is not None and title != book.title:
debug_print('Renaming title', book.title, 'to', title)
book.title = title
# Don't set the author, because the reader strips all but
# the first author.
for thumbnail in record.xpath( for thumbnail in record.xpath(
'descendant::*[local-name()="thumbnail"]'): 'descendant::*[local-name()="thumbnail"]'):
for img in thumbnail.xpath( for img in thumbnail.xpath(
@ -350,7 +344,7 @@ class XMLCache(object):
# }}} # }}}
# Update XML from JSON {{{ # Update XML from JSON {{{
def update(self, booklists, collections_attributes): def update(self, booklists, collections_attributes, plugboard):
debug_print('Starting update', collections_attributes) debug_print('Starting update', collections_attributes)
use_tz_var = False use_tz_var = False
for i, booklist in booklists.items(): for i, booklist in booklists.items():
@ -365,8 +359,13 @@ class XMLCache(object):
record = lpath_map.get(book.lpath, None) record = lpath_map.get(book.lpath, None)
if record is None: if record is None:
record = self.create_text_record(root, i, book.lpath) record = self.create_text_record(root, i, book.lpath)
if plugboard is not None:
newmi = book.deepcopy()
newmi.template_to_attribute(book, plugboard)
else:
newmi = book
(gtz_count, ltz_count, use_tz_var) = \ (gtz_count, ltz_count, use_tz_var) = \
self.update_text_record(record, book, path, i, self.update_text_record(record, newmi, path, i,
gtz_count, ltz_count, use_tz_var) gtz_count, ltz_count, use_tz_var)
# Ensure the collections in the XML database are recorded for # Ensure the collections in the XML database are recorded for
# this book # this book

View File

@ -707,7 +707,7 @@ OptionRecommendation(name='timestamp',
if mi.cover.startswith('http:') or mi.cover.startswith('https:'): if mi.cover.startswith('http:') or mi.cover.startswith('https:'):
mi.cover = self.download_cover(mi.cover) mi.cover = self.download_cover(mi.cover)
ext = mi.cover.rpartition('.')[-1].lower().strip() ext = mi.cover.rpartition('.')[-1].lower().strip()
if ext not in ('png', 'jpg', 'jpeg'): if ext not in ('png', 'jpg', 'jpeg', 'gif'):
ext = 'jpg' ext = 'jpg'
mi.cover_data = (ext, open(mi.cover, 'rb').read()) mi.cover_data = (ext, open(mi.cover, 'rb').read())
mi.cover = None mi.cover = None

View File

@ -44,7 +44,15 @@ def author_to_author_sort(author):
def authors_to_sort_string(authors): def authors_to_sort_string(authors):
return ' & '.join(map(author_to_author_sort, authors)) return ' & '.join(map(author_to_author_sort, authors))
_title_pat = re.compile('^(A|The|An)\s+', re.IGNORECASE) try:
_title_pat = re.compile(tweaks.get('title_sort_articles',
r'^(A|The|An)\s+'), re.IGNORECASE)
except:
print 'Error in title sort pattern'
import traceback
traceback.print_exc()
_title_pat = re.compile('^(A|The|An)\s+', re.IGNORECASE)
_ignore_starts = u'\'"'+u''.join(unichr(x) for x in range(0x2018, 0x201e)+[0x2032, 0x2033]) _ignore_starts = u'\'"'+u''.join(unichr(x) for x in range(0x2018, 0x201e)+[0x2032, 0x2033])
def title_sort(title): def title_sort(title):

View File

@ -114,7 +114,8 @@ SC_COPYABLE_FIELDS = SOCIAL_METADATA_FIELDS.union(
PUBLICATION_METADATA_FIELDS).union( PUBLICATION_METADATA_FIELDS).union(
BOOK_STRUCTURE_FIELDS).union( BOOK_STRUCTURE_FIELDS).union(
DEVICE_METADATA_FIELDS).union( DEVICE_METADATA_FIELDS).union(
CALIBRE_METADATA_FIELDS) - \ CALIBRE_METADATA_FIELDS).union(
TOP_LEVEL_CLASSIFIERS) - \
SC_FIELDS_NOT_COPIED.union( SC_FIELDS_NOT_COPIED.union(
SC_FIELDS_COPY_NOT_NULL) SC_FIELDS_COPY_NOT_NULL)

View File

@ -8,6 +8,7 @@ __docformat__ = 'restructuredtext en'
import copy, traceback import copy, traceback
from calibre import prints from calibre import prints
from calibre.constants import DEBUG
from calibre.ebooks.metadata.book import SC_COPYABLE_FIELDS from calibre.ebooks.metadata.book import SC_COPYABLE_FIELDS
from calibre.ebooks.metadata.book import SC_FIELDS_COPY_NOT_NULL from calibre.ebooks.metadata.book import SC_FIELDS_COPY_NOT_NULL
from calibre.ebooks.metadata.book import STANDARD_METADATA_FIELDS from calibre.ebooks.metadata.book import STANDARD_METADATA_FIELDS
@ -50,6 +51,8 @@ class SafeFormat(TemplateFormatter):
return '' return ''
return v return v
except: except:
if DEBUG:
traceback.print_exc()
return key return key
composite_formatter = SafeFormat() composite_formatter = SafeFormat()
@ -320,8 +323,8 @@ class Metadata(object):
else: else:
self.set(dest, val) self.set(dest, val)
except: except:
traceback.print_exc() if DEBUG:
pass traceback.print_exc()
# Old Metadata API {{{ # Old Metadata API {{{
def print_all_attributes(self): def print_all_attributes(self):

View File

@ -108,7 +108,8 @@ def _get_metadata(stream, stream_type, use_libprs_metadata,
base = metadata_from_filename(name, pat=pattern) base = metadata_from_filename(name, pat=pattern)
if force_read_metadata or is_recipe(name) or prefs['read_file_metadata']: if force_read_metadata or is_recipe(name) or prefs['read_file_metadata']:
mi = get_file_type_metadata(stream, stream_type) mi = get_file_type_metadata(stream, stream_type)
if base.title == os.path.splitext(name)[0] and base.authors is None: if base.title == os.path.splitext(name)[0] and \
base.is_null('authors') and base.is_null('isbn'):
# Assume that there was no metadata in the file and the user set pattern # Assume that there was no metadata in the file and the user set pattern
# to match meta info from the file name did not match. # to match meta info from the file name did not match.
# The regex is meant to match the standard format filenames are written # The regex is meant to match the standard format filenames are written

View File

@ -41,24 +41,6 @@ class MOBIOutput(OutputFormatPlugin):
), ),
]) ])
def remove_image_transparencies(self):
from calibre.utils.magick.draw import save_cover_data_to
for item in self.oeb.manifest:
if item.media_type.startswith('image'):
raw = item.data
ext = item.media_type.split('/')[-1].lower()
if ext not in ('png', 'gif') or not raw:
continue
try:
data = save_cover_data_to(raw, 'img.'+ext, return_data=True)
except:
self.log.exception('Failed to remove transparency from',
item.href)
data = None
if data is not None:
item.data = data
item.unload_data_from_memory()
def check_for_periodical(self): def check_for_periodical(self):
if self.oeb.metadata.publication_type and \ if self.oeb.metadata.publication_type and \
unicode(self.oeb.metadata.publication_type[0]).startswith('periodical:'): unicode(self.oeb.metadata.publication_type[0]).startswith('periodical:'):
@ -178,7 +160,6 @@ class MOBIOutput(OutputFormatPlugin):
from calibre.ebooks.oeb.transforms.rasterize import SVGRasterizer, Unavailable from calibre.ebooks.oeb.transforms.rasterize import SVGRasterizer, Unavailable
from calibre.ebooks.oeb.transforms.htmltoc import HTMLTOCAdder from calibre.ebooks.oeb.transforms.htmltoc import HTMLTOCAdder
from calibre.customize.ui import plugin_for_input_format from calibre.customize.ui import plugin_for_input_format
self.remove_image_transparencies()
imagemax = PALM_MAX_IMAGE_SIZE if opts.rescale_images else None imagemax = PALM_MAX_IMAGE_SIZE if opts.rescale_images else None
if not opts.no_inline_toc: if not opts.no_inline_toc:
tocadder = HTMLTOCAdder(title=opts.toc_title) tocadder = HTMLTOCAdder(title=opts.toc_title)

View File

@ -15,7 +15,6 @@ from struct import pack
import time import time
from urlparse import urldefrag from urlparse import urldefrag
from PIL import Image
from cStringIO import StringIO from cStringIO import StringIO
from calibre.ebooks.mobi.langcodes import iana2mobi from calibre.ebooks.mobi.langcodes import iana2mobi
from calibre.ebooks.mobi.mobiml import MBP_NS from calibre.ebooks.mobi.mobiml import MBP_NS
@ -28,6 +27,7 @@ from calibre.ebooks.oeb.base import namespace
from calibre.ebooks.oeb.base import prefixname from calibre.ebooks.oeb.base import prefixname
from calibre.ebooks.oeb.base import urlnormalize from calibre.ebooks.oeb.base import urlnormalize
from calibre.ebooks.compression.palmdoc import compress_doc from calibre.ebooks.compression.palmdoc import compress_doc
from calibre.utils.magick.draw import Image, save_cover_data_to, thumbnail
INDEXING = True INDEXING = True
FCIS_FLIS = True FCIS_FLIS = True
@ -111,46 +111,18 @@ def align_block(raw, multiple=4, pad='\0'):
return raw + pad*(multiple - extra) return raw + pad*(multiple - extra)
def rescale_image(data, maxsizeb, dimen=None): def rescale_image(data, maxsizeb, dimen=None):
image = Image.open(StringIO(data))
format = image.format
changed = False
if image.format not in ('JPEG', 'GIF'):
width, height = image.size
area = width * height
if area <= 40000:
format = 'GIF'
else:
image = image.convert('RGBA')
format = 'JPEG'
changed = True
if dimen is not None: if dimen is not None:
image.thumbnail(dimen, Image.ANTIALIAS) return thumbnail(data, width=dimen, height=dimen)[-1]
changed = True # Replace transparent pixels with white pixels and convert to JPEG
if changed: data = save_cover_data_to(data, 'img.jpg', return_data=True)
data = StringIO() scale = 0.9
image.save(data, format) while len(data) >= maxsizeb and scale >= 0.05:
data = data.getvalue() img = Image()
if len(data) <= maxsizeb: img.load(data)
return data w, h = img.size
image = image.convert('RGBA') img.size = (int(scale*w), int(scale*h))
for quality in xrange(95, -1, -1): data = img.export('jpg')
data = StringIO() scale -= 0.05
image.save(data, 'JPEG', quality=quality)
data = data.getvalue()
if len(data) <= maxsizeb:
return data
width, height = image.size
for scale in xrange(99, 0, -1):
scale = scale / 100.
data = StringIO()
scaled = image.copy()
size = (int(width * scale), (height * scale))
scaled.thumbnail(size, Image.ANTIALIAS)
scaled.save(data, 'JPEG', quality=0)
data = data.getvalue()
if len(data) <= maxsizeb:
return data
# Well, we tried?
return data return data
class Serializer(object): class Serializer(object):

View File

@ -234,13 +234,14 @@ class AddAction(InterfaceAction):
self.gui.set_books_in_library(booklists=[model.db], reset=True) self.gui.set_books_in_library(booklists=[model.db], reset=True)
self.gui.refresh_ondevice() self.gui.refresh_ondevice()
def add_books_from_device(self, view): def add_books_from_device(self, view, paths=None):
rows = view.selectionModel().selectedRows() if paths is None:
if not rows or len(rows) == 0: rows = view.selectionModel().selectedRows()
d = error_dialog(self.gui, _('Add to library'), _('No book selected')) if not rows or len(rows) == 0:
d.exec_() d = error_dialog(self.gui, _('Add to library'), _('No book selected'))
return d.exec_()
paths = [p for p in view._model.paths(rows) if p is not None] return
paths = [p for p in view.model().paths(rows) if p is not None]
ve = self.gui.device_manager.device.VIRTUAL_BOOK_EXTENSIONS ve = self.gui.device_manager.device.VIRTUAL_BOOK_EXTENSIONS
def ext(x): def ext(x):
ans = os.path.splitext(x)[1] ans = os.path.splitext(x)[1]
@ -261,7 +262,7 @@ class AddAction(InterfaceAction):
return return
from calibre.gui2.add import Adder from calibre.gui2.add import Adder
self.__adder_func = partial(self._add_from_device_adder, on_card=None, self.__adder_func = partial(self._add_from_device_adder, on_card=None,
model=view._model) model=view.model())
self._adder = Adder(self.gui, self.gui.library_view.model().db, self._adder = Adder(self.gui, self.gui.library_view.model().db,
self.Dispatcher(self.__adder_func), spare_server=self.gui.spare_server) self.Dispatcher(self.__adder_func), spare_server=self.gui.spare_server)
self._adder.add(paths) self._adder.add(paths)

View File

@ -181,5 +181,6 @@ class ConvertAction(InterfaceAction):
self.gui.tags_view.recount() self.gui.tags_view.recount()
if self.gui.current_view() is self.gui.library_view: if self.gui.current_view() is self.gui.library_view:
current = self.gui.library_view.currentIndex() current = self.gui.library_view.currentIndex()
self.gui.library_view.model().current_changed(current, QModelIndex()) if current.isValid():
self.gui.library_view.model().current_changed(current, QModelIndex())

View File

@ -21,7 +21,10 @@ from calibre.gui2.convert import Widget
def create_opf_file(db, book_id): def create_opf_file(db, book_id):
mi = db.get_metadata(book_id, index_is_id=True) mi = db.get_metadata(book_id, index_is_id=True)
mi.application_id = uuid.uuid4() mi.application_id = uuid.uuid4()
old_cover = mi.cover
mi.cover = None
raw = metadata_to_opf(mi) raw = metadata_to_opf(mi)
mi.cover = old_cover
opf_file = PersistentTemporaryFile('.opf') opf_file = PersistentTemporaryFile('.opf')
opf_file.write(raw) opf_file.write(raw)
opf_file.close() opf_file.close()

View File

@ -310,7 +310,13 @@ class DeviceManager(Thread): # {{{
self.device.sync_booklists(booklists, end_session=False) self.device.sync_booklists(booklists, end_session=False)
return self.device.card_prefix(end_session=False), self.device.free_space() return self.device.card_prefix(end_session=False), self.device.free_space()
def sync_booklists(self, done, booklists): def sync_booklists(self, done, booklists, plugboards):
if hasattr(self.connected_device, 'use_plugboard_ext') and \
callable(self.connected_device.use_plugboard_ext):
ext = self.connected_device.use_plugboard_ext()
if ext is not None:
self.connected_device.set_plugboard(
self.find_plugboard(ext, plugboards))
return self.create_job(self._sync_booklists, done, args=[booklists], return self.create_job(self._sync_booklists, done, args=[booklists],
description=_('Send metadata to device')) description=_('Send metadata to device'))
@ -319,28 +325,31 @@ class DeviceManager(Thread): # {{{
args=[booklist, on_card], args=[booklist, on_card],
description=_('Send collections to device')) description=_('Send collections to device'))
def find_plugboard(self, ext, plugboards):
dev_name = self.connected_device.__class__.__name__
cpb = None
if ext in plugboards:
cpb = plugboards[ext]
elif plugboard_any_format_value in plugboards:
cpb = plugboards[plugboard_any_format_value]
if cpb is not None:
if dev_name in cpb:
cpb = cpb[dev_name]
elif plugboard_any_device_value in cpb:
cpb = cpb[plugboard_any_device_value]
else:
cpb = None
if DEBUG:
prints('Device using plugboard', ext, dev_name, cpb)
return cpb
def _upload_books(self, files, names, on_card=None, metadata=None, plugboards=None): def _upload_books(self, files, names, on_card=None, metadata=None, plugboards=None):
'''Upload books to device: ''' '''Upload books to device: '''
if metadata and files and len(metadata) == len(files): if metadata and files and len(metadata) == len(files):
for f, mi in zip(files, metadata): for f, mi in zip(files, metadata):
if isinstance(f, unicode): if isinstance(f, unicode):
ext = f.rpartition('.')[-1].lower() ext = f.rpartition('.')[-1].lower()
dev_name = self.connected_device.__class__.__name__ cpb = self.find_plugboard(ext, plugboards)
cpb = None
if ext in plugboards:
cpb = plugboards[ext]
elif plugboard_any_format_value in plugboards:
cpb = plugboards[plugboard_any_format_value]
if cpb is not None:
if dev_name in cpb:
cpb = cpb[dev_name]
elif plugboard_any_device_value in cpb:
cpb = cpb[plugboard_any_device_value]
else:
cpb = None
if DEBUG:
prints('Using plugboard', ext, dev_name, cpb)
if ext: if ext:
try: try:
if DEBUG: if DEBUG:
@ -1247,8 +1256,9 @@ class DeviceMixin(object): # {{{
''' '''
Upload metadata to device. Upload metadata to device.
''' '''
plugboards = self.library_view.model().db.prefs.get('plugboards', {})
self.device_manager.sync_booklists(Dispatcher(self.metadata_synced), self.device_manager.sync_booklists(Dispatcher(self.metadata_synced),
self.booklists()) self.booklists(), plugboards)
def metadata_synced(self, job): def metadata_synced(self, job):
''' '''
@ -1502,8 +1512,10 @@ class DeviceMixin(object): # {{{
if update_metadata: if update_metadata:
if self.device_manager.is_device_connected: if self.device_manager.is_device_connected:
plugboards = self.library_view.model().db.prefs.get('plugboards', {})
self.device_manager.sync_booklists( self.device_manager.sync_booklists(
Dispatcher(self.metadata_synced), booklists) Dispatcher(self.metadata_synced), booklists,
plugboards)
return update_metadata return update_metadata
# }}} # }}}

View File

@ -56,6 +56,7 @@ class LocationManager(QObject): # {{{
self._mem.append(a) self._mem.append(a)
else: else:
ac.setToolTip(tooltip) ac.setToolTip(tooltip)
ac.calibre_name = name
return ac return ac
@ -112,7 +113,6 @@ class LocationManager(QObject): # {{{
ac.setWhatsThis(t) ac.setWhatsThis(t)
ac.setStatusTip(t) ac.setStatusTip(t)
@property @property
def has_device(self): def has_device(self):
return max(self.free) > -1 return max(self.free) > -1
@ -228,6 +228,7 @@ class ToolBar(QToolBar): # {{{
self.added_actions = [] self.added_actions = []
self.build_bar() self.build_bar()
self.preferred_width = self.sizeHint().width() self.preferred_width = self.sizeHint().width()
self.setAcceptDrops(True)
def apply_settings(self): def apply_settings(self):
sz = gprefs['toolbar_icon_size'] sz = gprefs['toolbar_icon_size']
@ -317,6 +318,59 @@ class ToolBar(QToolBar): # {{{
def database_changed(self, db): def database_changed(self, db):
pass pass
#support drag&drop from/to library from/to reader/card
def dragEnterEvent(self, event):
md = event.mimeData()
if md.hasFormat("application/calibre+from_library") or \
md.hasFormat("application/calibre+from_device"):
event.setDropAction(Qt.CopyAction)
event.accept()
else:
event.ignore()
def dragMoveEvent(self, event):
allowed = False
md = event.mimeData()
#Drop is only allowed in the location manager widget's different from the selected one
for ac in self.location_manager.available_actions:
w = self.widgetForAction(ac)
if w is not None:
if ( md.hasFormat("application/calibre+from_library") or \
md.hasFormat("application/calibre+from_device") ) and \
w.geometry().contains(event.pos()) and \
isinstance(w, QToolButton) and not w.isChecked():
allowed = True
break
if allowed:
event.acceptProposedAction()
else:
event.ignore()
def dropEvent(self, event):
data = event.mimeData()
mime = 'application/calibre+from_library'
if data.hasFormat(mime):
ids = list(map(int, str(data.data(mime)).split()))
tgt = None
for ac in self.location_manager.available_actions:
w = self.widgetForAction(ac)
if w is not None and w.geometry().contains(event.pos()):
tgt = ac.calibre_name
if tgt is not None:
if tgt == 'main':
tgt = None
self.gui.sync_to_device(tgt, False, send_ids=ids)
event.accept()
mime = 'application/calibre+from_device'
if data.hasFormat(mime):
paths = [unicode(u.toLocalFile()) for u in data.urls()]
if paths:
self.gui.iactions['Add Books'].add_books_from_device(
self.gui.current_view(), paths=paths)
event.accept()
# }}} # }}}
class MainWindowMixin(object): # {{{ class MainWindowMixin(object): # {{{

View File

@ -153,7 +153,7 @@ class TextDelegate(QStyledItemDelegate): # {{{
complete_items = [i[1] for i in self.auto_complete_function()] complete_items = [i[1] for i in self.auto_complete_function()]
completer = QCompleter(complete_items, self) completer = QCompleter(complete_items, self)
completer.setCaseSensitivity(Qt.CaseInsensitive) completer.setCaseSensitivity(Qt.CaseInsensitive)
completer.setCompletionMode(QCompleter.InlineCompletion) completer.setCompletionMode(QCompleter.PopupCompletion)
editor.setCompleter(completer) editor.setCompleter(completer)
return editor return editor
#}}} #}}}

View File

@ -361,13 +361,14 @@ class BooksModel(QAbstractTableModel): # {{{
self.cover_cache.set_cache(ids) self.cover_cache.set_cache(ids)
def current_changed(self, current, previous, emit_signal=True): def current_changed(self, current, previous, emit_signal=True):
idx = current.row() if current.isValid():
self.set_cache(idx) idx = current.row()
data = self.get_book_display_info(idx) self.set_cache(idx)
if emit_signal: data = self.get_book_display_info(idx)
self.new_bookdisplay_data.emit(data) if emit_signal:
else: self.new_bookdisplay_data.emit(data)
return data else:
return data
def get_book_info(self, index): def get_book_info(self, index):
if isinstance(index, int): if isinstance(index, int):
@ -1081,12 +1082,11 @@ class DeviceBooksModel(BooksModel): # {{{
self.db = db self.db = db
self.map = list(range(0, len(db))) self.map = list(range(0, len(db)))
def current_changed(self, current, previous): def cover(self, row):
data = {} item = self.db[self.map[row]]
item = self.db[self.map[current.row()]]
cdata = item.thumbnail cdata = item.thumbnail
img = QImage()
if cdata is not None: if cdata is not None:
img = QImage()
if hasattr(cdata, 'image_path'): if hasattr(cdata, 'image_path'):
img.load(cdata.image_path) img.load(cdata.image_path)
elif cdata: elif cdata:
@ -1094,9 +1094,16 @@ class DeviceBooksModel(BooksModel): # {{{
img.loadFromData(cdata[-1]) img.loadFromData(cdata[-1])
else: else:
img.loadFromData(cdata) img.loadFromData(cdata)
if img.isNull(): if img.isNull():
img = self.default_image img = self.default_image
data['cover'] = img return img
def current_changed(self, current, previous):
data = {}
item = self.db[self.map[current.row()]]
cover = self.cover(current.row())
if cover is not self.default_image:
data['cover'] = cover
type = _('Unknown') type = _('Unknown')
ext = os.path.splitext(item.path)[1] ext = os.path.splitext(item.path)[1]
if ext: if ext:

View File

@ -9,7 +9,8 @@ import os
from functools import partial from functools import partial
from PyQt4.Qt import QTableView, Qt, QAbstractItemView, QMenu, pyqtSignal, \ from PyQt4.Qt import QTableView, Qt, QAbstractItemView, QMenu, pyqtSignal, \
QModelIndex, QIcon, QItemSelection QModelIndex, QIcon, QItemSelection, QMimeData, QDrag, QApplication, \
QPoint, QPixmap, QUrl, QImage, QPainter, QColor, QRect
from calibre.gui2.library.delegates import RatingDelegate, PubDateDelegate, \ from calibre.gui2.library.delegates import RatingDelegate, PubDateDelegate, \
TextDelegate, DateDelegate, TagsDelegate, CcTextDelegate, \ TextDelegate, DateDelegate, TagsDelegate, CcTextDelegate, \
@ -18,7 +19,8 @@ from calibre.gui2.library.models import BooksModel, DeviceBooksModel
from calibre.utils.config import tweaks, prefs from calibre.utils.config import tweaks, prefs
from calibre.gui2 import error_dialog, gprefs from calibre.gui2 import error_dialog, gprefs
from calibre.gui2.library import DEFAULT_SORT from calibre.gui2.library import DEFAULT_SORT
from calibre.constants import filesystem_encoding
from calibre import force_unicode
class BooksView(QTableView): # {{{ class BooksView(QTableView): # {{{
@ -31,6 +33,7 @@ class BooksView(QTableView): # {{{
self.setDragEnabled(True) self.setDragEnabled(True)
self.setDragDropOverwriteMode(False) self.setDragDropOverwriteMode(False)
self.setDragDropMode(self.DragDrop) self.setDragDropMode(self.DragDrop)
self.drag_start_pos = None
self.setAlternatingRowColors(True) self.setAlternatingRowColors(True)
self.setSelectionBehavior(self.SelectRows) self.setSelectionBehavior(self.SelectRows)
self.setShowGrid(False) self.setShowGrid(False)
@ -422,10 +425,92 @@ class BooksView(QTableView): # {{{
Accept a drop event and return a list of paths that can be read from Accept a drop event and return a list of paths that can be read from
and represent files with extensions. and represent files with extensions.
''' '''
if event.mimeData().hasFormat('text/uri-list'): md = event.mimeData()
urls = [unicode(u.toLocalFile()) for u in event.mimeData().urls()] if md.hasFormat('text/uri-list') and not \
md.hasFormat('application/calibre+from_library'):
urls = [unicode(u.toLocalFile()) for u in md.urls()]
return [u for u in urls if os.path.splitext(u)[1] and os.access(u, os.R_OK)] return [u for u in urls if os.path.splitext(u)[1] and os.access(u, os.R_OK)]
def drag_icon(self, cover, multiple):
cover = cover.scaledToHeight(120, Qt.SmoothTransformation)
if multiple:
base_width = cover.width()
base_height = cover.height()
base = QImage(base_width+21, base_height+21,
QImage.Format_ARGB32_Premultiplied)
base.fill(QColor(255, 255, 255, 0).rgba())
p = QPainter(base)
rect = QRect(20, 0, base_width, base_height)
p.fillRect(rect, QColor('white'))
p.drawRect(rect)
rect.moveLeft(10)
rect.moveTop(10)
p.fillRect(rect, QColor('white'))
p.drawRect(rect)
rect.moveLeft(0)
rect.moveTop(20)
p.fillRect(rect, QColor('white'))
p.save()
p.setCompositionMode(p.CompositionMode_SourceAtop)
p.drawImage(rect.topLeft(), cover)
p.restore()
p.drawRect(rect)
p.end()
cover = base
return QPixmap.fromImage(cover)
def drag_data(self):
m = self.model()
db = m.db
rows = self.selectionModel().selectedRows()
selected = map(m.id, rows)
ids = ' '.join(map(str, selected))
md = QMimeData()
md.setData('application/calibre+from_library', ids)
fmt = prefs['output_format']
def url_for_id(i):
ans = db.format_abspath(i, fmt, index_is_id=True)
if ans is None:
fmts = db.formats(i, index_is_id=True)
if fmts:
fmts = fmts.split(',')
else:
fmts = []
for f in fmts:
ans = db.format_abspath(i, f, index_is_id=True)
if ans is not None:
break
if ans is None:
ans = db.abspath(i, index_is_id=True)
return QUrl.fromLocalFile(ans)
md.setUrls([url_for_id(i) for i in selected])
drag = QDrag(self)
drag.setMimeData(md)
cover = self.drag_icon(m.cover(self.currentIndex().row()),
len(selected) > 1)
drag.setHotSpot(QPoint(cover.width()//3, cover.height()//3))
drag.setPixmap(cover)
return drag
def mousePressEvent(self, event):
if event.button() == Qt.LeftButton:
self.drag_start_pos = event.pos()
return QTableView.mousePressEvent(self, event)
def mouseMoveEvent(self, event):
if not (event.buttons() & Qt.LeftButton) or self.drag_start_pos is None:
return
if (event.pos() - self.drag_start_pos).manhattanLength() \
< QApplication.startDragDistance():
return
index = self.indexAt(event.pos())
if not index.isValid():
return
drag = self.drag_data()
drag.exec_(Qt.CopyAction)
def dragEnterEvent(self, event): def dragEnterEvent(self, event):
if int(event.possibleActions() & Qt.CopyAction) + \ if int(event.possibleActions() & Qt.CopyAction) + \
int(event.possibleActions() & Qt.MoveAction) == 0: int(event.possibleActions() & Qt.MoveAction) == 0:
@ -547,6 +632,21 @@ class DeviceBooksView(BooksView): # {{{
self.setDragDropMode(self.NoDragDrop) self.setDragDropMode(self.NoDragDrop)
self.setAcceptDrops(False) self.setAcceptDrops(False)
def drag_data(self):
m = self.model()
rows = self.selectionModel().selectedRows()
paths = [force_unicode(p, enc=filesystem_encoding) for p in m.paths(rows) if p]
md = QMimeData()
md.setData('application/calibre+from_device', 'dummy')
md.setUrls([QUrl.fromLocalFile(p) for p in paths])
drag = QDrag(self)
drag.setMimeData(md)
cover = self.drag_icon(m.cover(self.currentIndex().row()), len(paths) >
1)
drag.setHotSpot(QPoint(cover.width()//3, cover.height()//3))
drag.setPixmap(cover)
return drag
def contextMenuEvent(self, event): def contextMenuEvent(self, event):
edit_collections = callable(getattr(self._model.db, 'supports_collections', None)) and \ edit_collections = callable(getattr(self._model.db, 'supports_collections', None)) and \
self._model.db.supports_collections() and \ self._model.db.supports_collections() and \

View File

@ -39,6 +39,12 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
ConfigWidgetBase.initialize(self) ConfigWidgetBase.initialize(self)
if self.gui.device_manager.connected_device is not None:
self.device_label.setText(_('Device currently connected: ') +
self.gui.device_manager.connected_device.__class__.__name__)
else:
self.device_label.setText(_('Device currently connected: None'))
self.devices = [''] self.devices = ['']
for device in device_plugins(): for device in device_plugins():
n = device.__class__.__name__ n = device.__class__.__name__
@ -54,6 +60,7 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
for w in metadata_writers(): for w in metadata_writers():
for f in w.file_types: for f in w.file_types:
self.formats.append(f) self.formats.append(f)
self.formats.append('device_db')
self.formats.sort() self.formats.sort()
self.formats.insert(1, plugboard_any_format_value) self.formats.insert(1, plugboard_any_format_value)
self.new_format.addItems(self.formats) self.new_format.addItems(self.formats)

View File

@ -40,7 +40,18 @@ One possible use for a plugboard is to alter the title to contain series informa
</property> </property>
</widget> </widget>
</item> </item>
<item row="2" column="0"> <item row="2" column="0" colspan="2">
<widget class="QLabel" name="device_label">
</widget>
</item>
<item row="3" column="0" colspan="2">
<widget class="Line" name="line">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item row="4" column="0">
<layout class="QGridLayout" name="gridLayout_2"> <layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="1"> <item row="0" column="1">
<widget class="QLabel" name="label_6"> <widget class="QLabel" name="label_6">
@ -123,7 +134,7 @@ One possible use for a plugboard is to alter the title to contain series informa
</item> </item>
</layout> </layout>
</item> </item>
<item row="2" column="1"> <item row="4" column="1">
<layout class="QGridLayout" name="fields_layout"> <layout class="QGridLayout" name="fields_layout">
<item row="0" column="0"> <item row="0" column="0">
<widget class="QLabel" name="label_2"> <widget class="QLabel" name="label_2">

View File

@ -79,6 +79,8 @@ class TagsView(QTreeView): # {{{
self.setHeaderHidden(True) self.setHeaderHidden(True)
self.setItemDelegate(TagDelegate(self)) self.setItemDelegate(TagDelegate(self))
self.made_connections = False self.made_connections = False
self.setAcceptDrops(True)
self.setDropIndicatorShown(True)
def set_database(self, db, tag_match, sort_by): def set_database(self, db, tag_match, sort_by):
self.hidden_categories = config['tag_browser_hidden_categories'] self.hidden_categories = config['tag_browser_hidden_categories']
@ -104,6 +106,49 @@ class TagsView(QTreeView): # {{{
def database_changed(self, event, ids): def database_changed(self, event, ids):
self.refresh_required.emit() self.refresh_required.emit()
def dragEnterEvent(self, event):
md = event.mimeData()
if md.hasFormat("application/calibre+from_library"):
event.setDropAction(Qt.CopyAction)
event.accept()
else:
event.ignore()
def dragMoveEvent(self, event):
allowed = False
idx = self.indexAt(event.pos())
m = self.model()
p = m.parent(idx)
if idx.isValid() and p.isValid():
item = m.data(p, Qt.UserRole)
if item.type == TagTreeItem.CATEGORY and \
item.category_key in \
('tags', 'series', 'authors', 'rating', 'publisher'):
allowed = True
if allowed:
event.acceptProposedAction()
else:
event.ignore()
def dropEvent(self, event):
idx = self.indexAt(event.pos())
m = self.model()
p = m.parent(idx)
if idx.isValid() and p.isValid():
item = m.data(p, Qt.UserRole)
if item.type == TagTreeItem.CATEGORY and \
item.category_key in \
('tags', 'series', 'authors', 'rating', 'publisher'):
child = m.data(idx, Qt.UserRole)
md = event.mimeData()
mime = 'application/calibre+from_library'
ids = list(map(int, str(md.data(mime)).split()))
self.handle_drop(item, child, ids)
event.accept()
def handle_drop(self, parent, child, ids):
print 'Dropped ids:', ids
@property @property
def match_all(self): def match_all(self):
return self.tag_match and self.tag_match.currentIndex() > 0 return self.tag_match and self.tag_match.currentIndex() > 0
@ -326,6 +371,8 @@ class TagTreeItem(object): # {{{
self.children.append(child) self.children.append(child)
def data(self, role): def data(self, role):
if role == Qt.UserRole:
return self
if self.type == self.TAG: if self.type == self.TAG:
return self.tag_data(role) return self.tag_data(role)
if self.type == self.CATEGORY: if self.type == self.CATEGORY:
@ -544,8 +591,14 @@ class TagsModel(QAbstractItemModel): # {{{
def headerData(self, *args): def headerData(self, *args):
return NONE return NONE
def flags(self, *args): def flags(self, index, *args):
return Qt.ItemIsEnabled|Qt.ItemIsSelectable|Qt.ItemIsEditable ans = Qt.ItemIsEnabled|Qt.ItemIsSelectable|Qt.ItemIsEditable
if index.isValid() and self.parent(index).isValid():
ans |= Qt.ItemIsDropEnabled
return ans
def supportedDropActions(self):
return Qt.CopyAction|Qt.MoveAction
def path_for_index(self, index): def path_for_index(self, index):
ans = [] ans = []

View File

@ -1593,7 +1593,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
self.conn.commit() self.conn.commit()
def delete_publisher_using_id(self, old_id): def delete_publisher_using_id(self, old_id):
self.dirty_books_referencing('publisher', id, commit=False) self.dirty_books_referencing('publisher', old_id, commit=False)
self.conn.execute('''DELETE FROM books_publishers_link self.conn.execute('''DELETE FROM books_publishers_link
WHERE publisher=?''', (old_id,)) WHERE publisher=?''', (old_id,))
self.conn.execute('DELETE FROM publishers WHERE id=?', (old_id,)) self.conn.execute('DELETE FROM publishers WHERE id=?', (old_id,))

View File

@ -8,6 +8,7 @@ __docformat__ = 'restructuredtext en'
import os, traceback, cStringIO, re import os, traceback, cStringIO, re
from calibre.constants import DEBUG
from calibre.utils.config import Config, StringConfig, tweaks from calibre.utils.config import Config, StringConfig, tweaks
from calibre.utils.formatter import TemplateFormatter from calibre.utils.formatter import TemplateFormatter
from calibre.utils.filenames import shorten_components_to, supports_long_names, \ from calibre.utils.filenames import shorten_components_to, supports_long_names, \
@ -118,8 +119,8 @@ class SafeFormat(TemplateFormatter):
try: try:
b = self.book.get_user_metadata(key, False) b = self.book.get_user_metadata(key, False)
except: except:
prints('save_to_disk get value exception') if DEBUG:
traceback.print_exc() traceback.print_exc()
b = None b = None
if b is not None and b['datatype'] == 'composite': if b is not None and b['datatype'] == 'composite':
@ -129,13 +130,13 @@ class SafeFormat(TemplateFormatter):
self.composite_values[key] = \ self.composite_values[key] = \
self.vformat(b['display']['composite_template'], [], kwargs) self.vformat(b['display']['composite_template'], [], kwargs)
return self.composite_values[key] return self.composite_values[key]
if kwargs[key]: if key in kwargs:
return self.sanitize(kwargs[key]) return kwargs[key].replace('/', '_').replace('\\', '_')
return '' return ''
except: except:
print('save_to_disk general exception') if DEBUG:
traceback.print_exc() traceback.print_exc()
return '' return key
safe_formatter = SafeFormat() safe_formatter = SafeFormat()
@ -182,8 +183,8 @@ def get_components(template, mi, id, timefmt='%b %Y', length=250,
elif custom_metadata[key]['datatype'] == 'bool': elif custom_metadata[key]['datatype'] == 'bool':
format_args[key] = _('yes') if format_args[key] else _('no') format_args[key] = _('yes') if format_args[key] else _('no')
components = safe_formatter.safe_format(template, format_args, '', mi, components = safe_formatter.safe_format(template, format_args,
sanitize=sanitize_func) 'G_C-EXCEPTION!', mi)
components = [x.strip() for x in components.split('/') if x.strip()] components = [x.strip() for x in components.split('/') if x.strip()]
components = [sanitize_func(x) for x in components if x] components = [sanitize_func(x) for x in components if x]
if not components: if not components:
@ -267,7 +268,8 @@ def save_book_to_disk(id, db, root, opts, length):
cpb = cpb[dev_name] cpb = cpb[dev_name]
else: else:
cpb = None cpb = None
#prints('Using plugboard:', fmt, cpb) if DEBUG:
prints('Save-to-disk using plugboard:', fmt, cpb)
data = db.format(id, fmt, index_is_id=True) data = db.format(id, fmt, index_is_id=True)
if data is None: if data is None:
continue continue
@ -285,7 +287,8 @@ def save_book_to_disk(id, db, root, opts, length):
newmi = mi newmi = mi
set_metadata(stream, newmi, fmt) set_metadata(stream, newmi, fmt)
except: except:
traceback.print_exc() if DEBUG:
traceback.print_exc()
stream.seek(0) stream.seek(0)
data = stream.read() data = stream.read()
fmt_path = base_path+'.'+str(fmt) fmt_path = base_path+'.'+str(fmt)

View File

@ -165,7 +165,7 @@ For tags, the result cut apart whereever |app| finds a comma. For example, if th
The same thing happens for authors, but using a different character for the cut, a `&` (ampersand) instead of a comma. For example, if the template produces the value ``Blogs, Joe&Posts, Susan``, then the book will end up with two authors, ``Blogs, Joe`` and ``Posts, Susan``. If the template produces the value ``Blogs, Joe;Posts, Susan``, then the book will have one author with a rather strange name. The same thing happens for authors, but using a different character for the cut, a `&` (ampersand) instead of a comma. For example, if the template produces the value ``Blogs, Joe&Posts, Susan``, then the book will end up with two authors, ``Blogs, Joe`` and ``Posts, Susan``. If the template produces the value ``Blogs, Joe;Posts, Susan``, then the book will have one author with a rather strange name.
Plugboards affect only the metadata written into the book. They do not affect calibre's metadata or the metadata used in ``save to disk`` and ``send to device`` templates. Plugboards also do not affect what is written into a Sony's database, so cannot be used for altering the metadata shown on a Sony's menu. Plugboards affect the metadata written into the book when it is saved to disk or written to the device. Plugboards do not affect the metadata used by ``save to disk`` and ``send to device`` to create the file names. Instead, file names are constructed using the templates entered on the appropriate preferences window.
Helpful Tips Helpful Tips
------------ ------------

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -4,7 +4,9 @@ Created on 23 Sep 2010
@author: charles @author: charles
''' '''
import re, string import re, string, traceback
from calibre.constants import DEBUG
class TemplateFormatter(string.Formatter): class TemplateFormatter(string.Formatter):
''' '''
@ -19,7 +21,6 @@ class TemplateFormatter(string.Formatter):
string.Formatter.__init__(self) string.Formatter.__init__(self)
self.book = None self.book = None
self.kwargs = None self.kwargs = None
self.sanitize = None
def _lookup(self, val, field_if_set, field_not_set): def _lookup(self, val, field_if_set, field_not_set):
if val: if val:
@ -99,8 +100,8 @@ class TemplateFormatter(string.Formatter):
return fmt, '', '' return fmt, '', ''
return matches.groups() return matches.groups()
except: except:
import traceback if DEBUG:
traceback.print_exc() traceback.print_exc()
return fmt, '', '' return fmt, '', ''
def format_field(self, val, fmt): def format_field(self, val, fmt):
@ -139,14 +140,15 @@ class TemplateFormatter(string.Formatter):
ans = string.Formatter.vformat(self, fmt, args, kwargs) ans = string.Formatter.vformat(self, fmt, args, kwargs)
return self.compress_spaces.sub(' ', ans).strip() return self.compress_spaces.sub(' ', ans).strip()
def safe_format(self, fmt, kwargs, error_value, book, sanitize=None): def safe_format(self, fmt, kwargs, error_value, book):
self.kwargs = kwargs self.kwargs = kwargs
self.book = book self.book = book
self.sanitize = sanitize
self.composite_values = {} self.composite_values = {}
try: try:
ans = self.vformat(fmt, [], kwargs).strip() ans = self.vformat(fmt, [], kwargs).strip()
except: except:
if DEBUG:
traceback.print_exc()
ans = error_value ans = error_value
return ans return ans