From 4ea961ba6298a905bc50136e8054117d77a18575 Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Mon, 18 Apr 2011 08:34:22 +0100 Subject: [PATCH] From Greg --- src/calibre/customize/builtins.py | 5 +- src/calibre/devices/apple/driver.py | 51 +++++++------ .../devices/content_server/__init__.py | 10 +++ src/calibre/devices/content_server/driver.py | 74 +++++++++++++++++++ src/calibre/gui2/actions/catalog.py | 2 +- src/calibre/gui2/device.py | 2 +- src/calibre/gui2/dialogs/tweak_epub.py | 11 ++- src/calibre/library/server/content.py | 27 ++++++- 8 files changed, 154 insertions(+), 28 deletions(-) create mode 100644 src/calibre/devices/content_server/__init__.py create mode 100644 src/calibre/devices/content_server/driver.py diff --git a/src/calibre/customize/builtins.py b/src/calibre/customize/builtins.py index d3b0b8409d..458bfec3fd 100644 --- a/src/calibre/customize/builtins.py +++ b/src/calibre/customize/builtins.py @@ -582,6 +582,7 @@ from calibre.ebooks.snb.output import SNBOutput from calibre.customize.profiles import input_profiles, output_profiles from calibre.devices.apple.driver import ITUNES +from calibre.devices.content_server.driver import CONTENT_SERVER_FOR_CONFIG from calibre.devices.hanlin.driver import HANLINV3, HANLINV5, BOOX, SPECTRA from calibre.devices.blackberry.driver import BLACKBERRY from calibre.devices.cybook.driver import CYBOOK, ORIZON @@ -753,7 +754,9 @@ plugins += [ EEEREADER, NEXTBOOK, ITUNES, -] + CONTENT_SERVER_FOR_CONFIG + ] + plugins += [x for x in list(locals().values()) if isinstance(x, type) and \ x.__name__.endswith('MetadataReader')] plugins += [x for x in list(locals().values()) if isinstance(x, type) and \ diff --git a/src/calibre/devices/apple/driver.py b/src/calibre/devices/apple/driver.py index 2cc478603a..d7811f0a22 100644 --- a/src/calibre/devices/apple/driver.py +++ b/src/calibre/devices/apple/driver.py @@ -201,8 +201,9 @@ class ITUNES(DriverBase): # 0x1294 iPhone 3GS # 0x1297 iPhone 4 # 0x129a iPad + # 0x12a2 iPad2 VENDOR_ID = [0x05ac] - PRODUCT_ID = [0x1292,0x1293,0x1294,0x1297,0x1299,0x129a] + PRODUCT_ID = [0x1292,0x1293,0x1294,0x1297,0x1299,0x129a,0x12a2] BCD = [0x01] # Plugboard ID @@ -421,7 +422,7 @@ class ITUNES(DriverBase): cached_books[this_book.path] = { 'title':book.name(), - 'author':[book.artist()], + 'author':book.artist().split(' & '), 'lib_book':library_books[this_book.path] if this_book.path in library_books else None, 'dev_book':book, 'uuid': book.composer() @@ -459,7 +460,7 @@ class ITUNES(DriverBase): cached_books[this_book.path] = { 'title':book.Name, - 'author':book.Artist, + 'author':book.artist().split(' & '), 'lib_book':library_books[this_book.path] if this_book.path in library_books else None, 'uuid': book.Composer, 'format': 'pdf' if book.KindAsString.startswith('PDF') else 'epub' @@ -1021,7 +1022,9 @@ class ITUNES(DriverBase): if isosx: for (i,file) in enumerate(files): format = file.rpartition('.')[2].lower() - path = self.path_template % (metadata[i].title, metadata[i].author[0],format) + path = self.path_template % (metadata[i].title, + authors_to_string(metadata[i].authors), + format) self._remove_existing_copy(path, metadata[i]) fpath = self._get_fpath(file, metadata[i], format, update_md=True) db_added, lb_added = self._add_new_copy(fpath, metadata[i]) @@ -1034,9 +1037,11 @@ class ITUNES(DriverBase): if DEBUG: self.log.info("ITUNES.upload_books()") self.log.info(" adding '%s' by '%s' uuid:%s to self.cached_books" % - ( metadata[i].title, metadata[i].author, metadata[i].uuid)) + (metadata[i].title, + authors_to_string(metadata[i].authors), + metadata[i].uuid)) self.cached_books[this_book.path] = { - 'author': metadata[i].author, + 'author': authors_to_string(metadata[i].authors), 'dev_book': db_added, 'format': format, 'lib_book': lb_added, @@ -1055,7 +1060,9 @@ class ITUNES(DriverBase): for (i,file) in enumerate(files): format = file.rpartition('.')[2].lower() - path = self.path_template % (metadata[i].title, metadata[i].author[0],format) + path = self.path_template % (metadata[i].title, + authors_to_string(metadata[i].authors), + format) self._remove_existing_copy(path, metadata[i]) fpath = self._get_fpath(file, metadata[i],format, update_md=True) db_added, lb_added = self._add_new_copy(fpath, metadata[i]) @@ -1075,9 +1082,11 @@ class ITUNES(DriverBase): if DEBUG: self.log.info("ITUNES.upload_books()") self.log.info(" adding '%s' by '%s' uuid:%s to self.cached_books" % - ( metadata[i].title, metadata[i].author, metadata[i].uuid)) + (metadata[i].title, + authors_to_string(metadata[i].authors), + metadata[i].uuid)) self.cached_books[this_book.path] = { - 'author': metadata[i].author[0], + 'author': authors_to_string(metadata[i].authors), 'dev_book': db_added, 'format': format, 'lib_book': lb_added, @@ -1190,7 +1199,7 @@ class ITUNES(DriverBase): base_fn = base_fn.rpartition('.')[0] db_added = self._find_device_book( { 'title': base_fn if format == 'pdf' else metadata.title, - 'author': metadata.authors[0], + 'author': authors_to_string(metadata.authors), 'uuid': metadata.uuid, 'format': format}) return db_added @@ -1255,7 +1264,7 @@ class ITUNES(DriverBase): base_fn = base_fn.rpartition('.')[0] added = self._find_library_book( { 'title': base_fn if format == 'pdf' else metadata.title, - 'author': metadata.author[0], + 'author': authors_to_string(metadata.authors), 'uuid': metadata.uuid, 'format': format}) return added @@ -1314,7 +1323,7 @@ class ITUNES(DriverBase): with open(metadata.cover,'r+b') as cd: cover_data = cd.read() except: - self.problem_titles.append("'%s' by %s" % (metadata.title, metadata.author[0])) + self.problem_titles.append("'%s' by %s" % (metadata.title, authors_to_string(metadata.authors))) self.log.error(" error scaling '%s' for '%s'" % (metadata.cover,metadata.title)) import traceback @@ -1389,7 +1398,7 @@ class ITUNES(DriverBase): thumb_path = path.rpartition('.')[0] + '.jpg' zfw.writestr(thumb_path, thumb) except: - self.problem_titles.append("'%s' by %s" % (metadata.title, metadata.author[0])) + self.problem_titles.append("'%s' by %s" % (metadata.title, authors_to_string(metadata.authors))) self.log.error(" error converting '%s' to thumb for '%s'" % (metadata.cover,metadata.title)) finally: try: @@ -1407,7 +1416,7 @@ class ITUNES(DriverBase): if DEBUG: self.log.info(" ITUNES._create_new_book()") - this_book = Book(metadata.title, authors_to_string(metadata.author)) + this_book = Book(metadata.title, authors_to_string(metadata.authors)) this_book.datetime = time.gmtime() this_book.db_id = None this_book.device_collections = [] @@ -2451,7 +2460,7 @@ class ITUNES(DriverBase): for book in self.cached_books: if self.cached_books[book]['uuid'] == metadata.uuid or \ (self.cached_books[book]['title'] == metadata.title and \ - self.cached_books[book]['author'] == metadata.authors[0]): + self.cached_books[book]['author'] == authors_to_string(metadata.authors)): self.update_list.append(self.cached_books[book]) self._remove_from_device(self.cached_books[book]) if DEBUG: @@ -2470,7 +2479,7 @@ class ITUNES(DriverBase): for book in self.cached_books: if self.cached_books[book]['uuid'] == metadata.uuid or \ (self.cached_books[book]['title'] == metadata.title and \ - self.cached_books[book]['author'] == metadata.authors[0]): + self.cached_books[book]['author'] == authors_to_string(metadata.authors)): self.update_list.append(self.cached_books[book]) self._remove_from_iTunes(self.cached_books[book]) if DEBUG: @@ -2939,13 +2948,13 @@ class ITUNES(DriverBase): def _xform_metadata_via_plugboard(self, book, format): ''' Transform book metadata from plugboard templates ''' if DEBUG: - self.log.info(" ITUNES._xform_metadata_via_plugboard()") + self.log.info(" ITUNES._xform_metadata_via_plugboard()") if self.plugboard_func: pb = self.plugboard_func(self.DEVICE_PLUGBOARD_NAME, format, self.plugboards) newmi = book.deepcopy_metadata() newmi.template_to_attribute(book, pb) - if DEBUG: + if pb is not None and DEBUG: self.log.info(" transforming %s using %s:" % (format, pb)) self.log.info(" title: %s %s" % (book.title, ">>> %s" % newmi.title if book.title != newmi.title else '')) @@ -3062,7 +3071,7 @@ class ITUNES_ASYNC(ITUNES): cached_books[this_book.path] = { 'title':library_books[book].name(), - 'author':[library_books[book].artist()], + 'author':library_books[book].artist().split(' & '), 'lib_book':library_books[book], 'dev_book':None, 'uuid': library_books[book].composer(), @@ -3102,7 +3111,7 @@ class ITUNES_ASYNC(ITUNES): cached_books[this_book.path] = { 'title':library_books[book].Name, - 'author':library_books[book].Artist, + 'author':library_books[book].Artist.split(' & '), 'lib_book':library_books[book], 'uuid': library_books[book].Composer, 'format': format @@ -3288,7 +3297,7 @@ class Book(Metadata): See ebooks.metadata.book.base ''' def __init__(self,title,author): - Metadata.__init__(self, title, authors=[author]) + Metadata.__init__(self, title, authors=author.split(' & ')) @property def title_sorter(self): diff --git a/src/calibre/devices/content_server/__init__.py b/src/calibre/devices/content_server/__init__.py new file mode 100644 index 0000000000..3d1a86922e --- /dev/null +++ b/src/calibre/devices/content_server/__init__.py @@ -0,0 +1,10 @@ +#!/usr/bin/env python +# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai +from __future__ import with_statement + +__license__ = 'GPL v3' +__copyright__ = '2009, Kovid Goyal ' +__docformat__ = 'restructuredtext en' + + + diff --git a/src/calibre/devices/content_server/driver.py b/src/calibre/devices/content_server/driver.py new file mode 100644 index 0000000000..84b14f8e62 --- /dev/null +++ b/src/calibre/devices/content_server/driver.py @@ -0,0 +1,74 @@ +''' +Created on 17 Apr 2011 + +@author: GRiker, modeled on charles's Folder Device + +''' + +from calibre.constants import DEBUG +from calibre.devices.interface import DevicePlugin +from calibre.devices.usbms.deviceconfig import DeviceConfig +from calibre.devices.usbms.driver import USBMS, BookList + +class DriverBase(DeviceConfig, DevicePlugin): + # Reduce to just the formats eligible for plugboard xforms + # These formats are shown in the customization dialog + FORMATS = ['epub', 'mobi'] + USER_CAN_ADD_NEW_FORMATS = False + + # Hide the standard customization widgets + SUPPORTS_SUB_DIRS = False + MUST_READ_METADATA = True + SUPPORTS_USE_AUTHOR_SORT = False + + +# This class is added to the standard device plugin chain, so that it can +# be configured. It has invalid vendor_id etc, so it will never match a +# device. The 'real' CONTENT_SERVER will use the config from it. +class CONTENT_SERVER_FOR_CONFIG(USBMS): + name = 'Content Server Interface' + gui_name = 'Content Server' + description = _('Enables metadata plugboards to be used with Content Server.') + author = 'GRiker' + supported_platforms = ['windows', 'osx', 'linux'] + + VENDOR_ID = [0xffff] + PRODUCT_ID = [0xffff] + BCD = [0xffff] + DEVICE_PLUGBOARD_NAME = 'CONTENT_SERVER' + + def config_widget(cls): + ''' + Configure a minimal QWidget + Better to simply disable the config_widget altogether + ''' + cw = DriverBase.config_widget() + # Turn off the Save template + cw.opt_save_template.setVisible(False) + cw.label.setVisible(False) + # Hide the up/down arrows + cw.column_up.setVisible(False) + cw.column_down.setVisible(False) + # Retitle + cw.groupBox.setTitle(_("Enable metadata plugboards for the following formats:")) + return cw + +class CONTENT_SERVER(USBMS): + + FORMATS = CONTENT_SERVER_FOR_CONFIG.FORMATS + DEVICE_PLUGBOARD_NAME = 'CONTENT_SERVER' + + def __init__(self): + if DEBUG: + print("CONTENT_SERVER.init()") + pass + + def set_plugboards(self, plugboards, pb_func): + # This method is called with the plugboard that matches the format + # declared in use_plugboard_ext and a device name of CONTENT_SERVER + if DEBUG: + print("CONTENT_SERVER.set_plugboards()") + print(' using plugboard %s' % plugboards) + self.plugboards = plugboards + self.plugboard_func = pb_func + diff --git a/src/calibre/gui2/actions/catalog.py b/src/calibre/gui2/actions/catalog.py index fad6e59294..093985d041 100644 --- a/src/calibre/gui2/actions/catalog.py +++ b/src/calibre/gui2/actions/catalog.py @@ -17,7 +17,7 @@ from calibre.gui2.actions import InterfaceAction class GenerateCatalogAction(InterfaceAction): name = 'Generate Catalog' - action_spec = (_('Create a catalog of the books in your calibre library'), None, None, None) + action_spec = (_('Create a catalog of the books in your calibre library'), 'catalog.png', 'Catalog builder', None) dont_add_to = frozenset(['menubar-device', 'toolbar-device', 'context-menu-device']) def generate_catalog(self): diff --git a/src/calibre/gui2/device.py b/src/calibre/gui2/device.py index 4d4f66eab1..8f21c17eaf 100644 --- a/src/calibre/gui2/device.py +++ b/src/calibre/gui2/device.py @@ -892,7 +892,7 @@ class DeviceMixin(object): # {{{ sub_dest_parts.append('') to = sub_dest_parts[0] fmts = sub_dest_parts[1] - subject = ';'.join(sub_dest_parts[2:]) + subject = ';'.join(sub_dest_parts[2:]) fmts = [x.strip().lower() for x in fmts.split(',')] self.send_by_mail(to, fmts, delete, subject=subject) diff --git a/src/calibre/gui2/dialogs/tweak_epub.py b/src/calibre/gui2/dialogs/tweak_epub.py index db6e93fd7a..a42fb07e40 100755 --- a/src/calibre/gui2/dialogs/tweak_epub.py +++ b/src/calibre/gui2/dialogs/tweak_epub.py @@ -12,6 +12,7 @@ from zipfile import ZipFile, ZIP_DEFLATED, ZIP_STORED from PyQt4.Qt import QDialog +from calibre.constants import isosx, iswindows from calibre.gui2 import open_local_file from calibre.gui2.dialogs.tweak_epub_ui import Ui_Dialog from calibre.libunzip import extract as zipextract @@ -42,11 +43,19 @@ class TweakEpub(QDialog, Ui_Dialog): self.move(parent_loc.x(),parent_loc.y()) def cleanup(self): + if isosx: + try: + import appscript + self.finder = appscript.app('Finder') + self.finder.Finder_windows[os.path.basename(self._exploded)].close() + except: + # appscript fails to load on 10.4 + pass + # Delete directory containing exploded ePub if self._exploded is not None: shutil.rmtree(self._exploded, ignore_errors=True) - def display_exploded(self): ''' Generic subprocess launch of native file browser diff --git a/src/calibre/library/server/content.py b/src/calibre/library/server/content.py index 0c3edd1627..faa0a61baf 100644 --- a/src/calibre/library/server/content.py +++ b/src/calibre/library/server/content.py @@ -183,16 +183,37 @@ class ContentServer(object): if fmt is None: raise cherrypy.HTTPError(404, 'book: %d does not have format: %s'%(id, format)) if format == 'EPUB': + # Get the original metadata + mi = self.db.get_metadata(id, index_is_id=True) + + # Instantiate the CONTENT_SERVER driver + from calibre.devices.content_server.driver import CONTENT_SERVER + cs = CONTENT_SERVER() + + # Get any EPUB plugboards for the content server + from calibre.gui2.device import find_plugboard, device_name_for_plugboards + plugboards = self.db.prefs.get('plugboards', {}) + + # Transform the metadata via the plugboard + if hasattr(cs, 'set_plugboards') and callable(cs.set_plugboards): + cs.set_plugboards(plugboards, find_plugboard) + cpb = find_plugboard(device_name_for_plugboards(cs), format.lower(), plugboards) + if cpb: + newmi = mi.deepcopy_metadata() + newmi.template_to_attribute(mi, cpb) + else: + newmi = mi + + # Write the updated file from tempfile import TemporaryFile from calibre.ebooks.metadata.meta import set_metadata raw = fmt.read() fmt = TemporaryFile() fmt.write(raw) fmt.seek(0) - set_metadata(fmt, self.db.get_metadata(id, index_is_id=True, - get_cover=True), - 'epub') + set_metadata(fmt, newmi, 'epub') fmt.seek(0) + mt = guess_type('dummy.'+format.lower())[0] if mt is None: mt = 'application/octet-stream'