From d1c040d5464798e79c865fa8f355eac6601c8d0d Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Thu, 13 May 2010 21:57:54 +0100 Subject: [PATCH] Working JSON metadata along side Sony metadata --- src/calibre/devices/interface.py | 17 ++ src/calibre/devices/prs505/__init__.py | 4 + src/calibre/devices/prs505/books.py | 205 ++++++++---------------- src/calibre/devices/prs505/driver.py | 157 ++---------------- src/calibre/devices/usbms/books.py | 4 +- src/calibre/devices/usbms/driver.py | 37 +++-- src/calibre/ebooks/metadata/__init__.py | 8 +- src/calibre/gui2/device.py | 14 +- src/calibre/gui2/library.py | 15 +- 9 files changed, 142 insertions(+), 319 deletions(-) diff --git a/src/calibre/devices/interface.py b/src/calibre/devices/interface.py index 98421959cc..6247e29e15 100644 --- a/src/calibre/devices/interface.py +++ b/src/calibre/devices/interface.py @@ -387,6 +387,9 @@ class BookList(list): __getslice__ = None __setslice__ = None + def __init__(self, oncard, prefix): + pass + def supports_tags(self): ''' Return True if the the device supports tags (collections) for this book list. ''' raise NotImplementedError() @@ -399,3 +402,17 @@ class BookList(list): ''' raise NotImplementedError() + def add_book(self, book, collections=None): + ''' + Add the book to the booklist. Intent is to maintain any device-internal + metadata + ''' + if book not in self: + self.append(book) + + def remove_book(self, book): + ''' + Remove a book from the booklist. Correct any device metadata at the + same time + ''' + self.remove(book) diff --git a/src/calibre/devices/prs505/__init__.py b/src/calibre/devices/prs505/__init__.py index f832dbb7fc..20f3b8d49b 100644 --- a/src/calibre/devices/prs505/__init__.py +++ b/src/calibre/devices/prs505/__init__.py @@ -1,2 +1,6 @@ __license__ = 'GPL v3' __copyright__ = '2008, Kovid Goyal ' + +MEDIA_XML = 'database/cache/media.xml' + +CACHE_XML = 'Sony Reader/database/cache.xml' diff --git a/src/calibre/devices/prs505/books.py b/src/calibre/devices/prs505/books.py index 66f24b97a0..82bc977bcd 100644 --- a/src/calibre/devices/prs505/books.py +++ b/src/calibre/devices/prs505/books.py @@ -5,13 +5,14 @@ __copyright__ = '2008, Kovid Goyal ' import re, time, functools from uuid import uuid4 as _uuid import xml.dom.minidom as dom -from base64 import b64decode as decode from base64 import b64encode as encode from calibre.devices.interface import BookList as _BookList from calibre.devices import strftime as _strftime -from calibre.devices import strptime +from calibre.devices.usbms.books import Book as _Book +from calibre.devices.prs505 import MEDIA_XML +from calibre.devices.prs505 import CACHE_XML strftime = functools.partial(_strftime, zone=time.gmtime) @@ -50,62 +51,7 @@ class book_metadata_field(object): obj.elem.setAttribute(self.attr, val) -class Book(object): - """ Provides a view onto the XML element that represents a book """ - - title = book_metadata_field("title") - authors = book_metadata_field("author", \ - formatter=lambda x: [x if x and x.strip() else _('Unknown')]) - mime = book_metadata_field("mime") - rpath = book_metadata_field("path") - id = book_metadata_field("id", formatter=int) - sourceid = book_metadata_field("sourceid", formatter=int) - size = book_metadata_field("size", formatter=lambda x : int(float(x))) - # When setting this attribute you must use an epoch - datetime = book_metadata_field("date", formatter=strptime, setter=strftime) - - @dynamic_property - def title_sorter(self): - doc = '''String to sort the title. If absent, title is returned''' - def fget(self): - src = self.elem.getAttribute('titleSorter').strip() - if not src: - src = self.title - return src - def fset(self, val): - self.elem.setAttribute('titleSorter', sortable_title(unicode(val))) - return property(doc=doc, fget=fget, fset=fset) - - @dynamic_property - def thumbnail(self): - doc = \ - """ - The thumbnail. Should be a height 68 image. - Setting is not supported. - """ - def fget(self): - th = self.elem.getElementsByTagName(self.prefix + "thumbnail") - if not len(th): - th = self.elem.getElementsByTagName("cache:thumbnail") - if len(th): - for n in th[0].childNodes: - if n.nodeType == n.ELEMENT_NODE: - th = n - break - rc = "" - for node in th.childNodes: - if node.nodeType == node.TEXT_NODE: - rc += node.data - return decode(rc) - return property(fget=fget, doc=doc) - - @dynamic_property - def path(self): - doc = """ Absolute path to book on device. Setting not supported. """ - def fget(self): - return self.mountpath + self.rpath - return property(fget=fget, doc=doc) - +class Book(_Book): @dynamic_property def db_id(self): doc = '''The database id in the application database that this file corresponds to''' @@ -115,42 +61,26 @@ class Book(object): return int(match.group(1)) return property(fget=fget, doc=doc) - def __init__(self, node, mountpath, tags, prefix=""): - self.elem = node - self.prefix = prefix - self.tags = tags - self.mountpath = mountpath - - def __str__(self): - """ Return a utf-8 encoded string with title author and path information """ - return self.title.encode('utf-8') + " by " + \ - self.authors.encode('utf-8') + " at " + self.path.encode('utf-8') - - class BookList(_BookList): - def __init__(self, xml_file, mountpath, report_progress=None): - _BookList.__init__(self) + def __init__(self, oncard, prefix): + _BookList.__init__(self, oncard, prefix) + if prefix is None: + return + db = CACHE_XML if oncard else MEDIA_XML + xml_file = open(prefix + db, 'rb') xml_file.seek(0) self.document = dom.parse(xml_file) self.root_element = self.document.documentElement - self.mountpath = mountpath + self.mountpath = prefix records = self.root_element.getElementsByTagName('records') - self.tag_order = {} if records: self.prefix = 'xs1:' self.root_element = records[0] else: self.prefix = '' - - nodes = self.root_element.childNodes - for i, book in enumerate(nodes): - if report_progress: - report_progress((i+1) / float(len(nodes)), _('Getting list of books on device...')) - if hasattr(book, 'tagName') and book.tagName.endswith('text'): - tags = [i.getAttribute('title') for i in self.get_playlists(book.getAttribute('id'))] - self.append(Book(book, mountpath, tags, prefix=self.prefix)) + self.tag_order = {} def max_id(self): max = 0 @@ -180,32 +110,32 @@ class BookList(_BookList): return child return None - def add_book(self, mi, name, collections, size, ctime): + def add_book(self, book, collections): + if book in self: + return """ Add a node into the DOM tree, representing a book """ - book = self.book_by_path(name) - if book is not None: - self.remove_book(name) - node = self.document.createElement(self.prefix + "text") - mime = MIME_MAP.get(name.rpartition('.')[-1].lower(), MIME_MAP['epub']) + mime = MIME_MAP.get(book.lpath.rpartition('.')[-1].lower(), MIME_MAP['epub']) cid = self.max_id()+1 + book.sony_id = cid + self.append(book) try: sourceid = str(self[0].sourceid) if len(self) else '1' except: sourceid = '1' attrs = { - "title" : mi.title, - 'titleSorter' : sortable_title(mi.title), - "author" : mi.format_authors() if mi.format_authors() else _('Unknown'), + "title" : book.title, + 'titleSorter' : sortable_title(book.title), + "author" : book.format_authors() if book.format_authors() else _('Unknown'), "page":"0", "part":"0", "scale":"0", \ "sourceid":sourceid, "id":str(cid), "date":"", \ - "mime":mime, "path":name, "size":str(size) + "mime":mime, "path":book.lpath, "size":str(book.size) } for attr in attrs.keys(): node.setAttributeNode(self.document.createAttribute(attr)) node.setAttribute(attr, attrs[attr]) try: - w, h, data = mi.thumbnail + w, h, data = book.thumbnail except: w, h, data = None, None, None @@ -218,14 +148,11 @@ class BookList(_BookList): th.appendChild(jpeg) node.appendChild(th) self.root_element.appendChild(node) - book = Book(node, self.mountpath, [], prefix=self.prefix) - book.datetime = ctime - self.append(book) tags = [] for item in collections: item = item.strip() - mitem = getattr(mi, item, None) + mitem = getattr(book, item, None) titems = [] if mitem: if isinstance(mitem, list): @@ -241,37 +168,34 @@ class BookList(_BookList): tags.extend(titems) if tags: tags = list(set(tags)) - if hasattr(mi, 'tag_order'): - self.tag_order.update(mi.tag_order) - self.set_tags(book, tags) + if hasattr(book, 'tag_order'): + self.tag_order.update(book.tag_order) + self.set_playlists(cid, tags) - def _delete_book(self, node): + def _delete_node(self, node): nid = node.getAttribute('id') self.remove_from_playlists(nid) node.parentNode.removeChild(node) node.unlink() - def delete_book(self, cid): + def delete_node(self, lpath): ''' - Remove DOM node corresponding to book with C{id == cid}. + Remove DOM node corresponding to book with lpath. Also remove book from any collections it is part of. ''' - for book in self: - if str(book.id) == str(cid): - self.remove(book) - self._delete_book(book.elem) - break + for child in self.root_element.childNodes: + if child.nodeType == child.ELEMENT_NODE and child.hasAttribute("id"): + if child.getAttribute('path') == lpath: + self._delete_node(child) + break - def remove_book(self, path): + def remove_book(self, book): ''' Remove DOM node corresponding to book with C{path == path}. Also remove book from any collections it is part of. ''' - for book in self: - if path.endswith(book.rpath): - self.remove(book) - self._delete_book(book.elem) - break + self.remove(book) + self.delete_node(book.lpath) def playlists(self): ans = [] @@ -343,11 +267,6 @@ class BookList(_BookList): pli.parentNode.removeChild(pli) pli.unlink() - def set_tags(self, book, tags): - tags = [t for t in tags if t] - book.tags = tags - self.set_playlists(book.id, tags) - def set_playlists(self, id, collections): self.remove_from_playlists(id) for collection in set(collections): @@ -358,15 +277,6 @@ class BookList(_BookList): item.setAttribute('id', str(id)) coll.appendChild(item) - def get_playlists(self, bookid): - ans = [] - for pl in self.playlists(): - for item in pl.childNodes: - if hasattr(item, 'tagName') and item.tagName.endswith('item'): - if item.getAttribute('id') == str(bookid): - ans.append(pl) - return ans - def next_id(self): return self.document.documentElement.getAttribute('nextID') @@ -378,27 +288,41 @@ class BookList(_BookList): src = self.document.toxml('utf-8') + '\n' stream.write(src.replace("'", ''')) - def book_by_id(self, id): - for book in self: - if str(book.id) == str(id): - return book - def reorder_playlists(self): + sony_id_cache = {} + for child in self.root_element.childNodes: + if child.nodeType == child.ELEMENT_NODE and child.hasAttribute("id"): + sony_id_cache[child.getAttribute('id')] = child.getAttribute('path') + + books_lpath_cache = {} + for book in self: + books_lpath_cache[book.lpath] = book + for title in self.tag_order.keys(): pl = self.playlist_by_title(title) if not pl: continue - db_ids = [i.getAttribute('id') for i in pl.childNodes if hasattr(i, 'getAttribute')] - pl_book_ids = [getattr(self.book_by_id(i), 'db_id', None) for i in db_ids] + # make a list of the ids + sony_ids = [id.getAttribute('id') \ + for id in pl.childNodes if hasattr(id, 'getAttribute')] + # convert IDs in playlist to a list of lpaths + sony_paths = [sony_id_cache[id] for id in sony_ids] + # create list of books containing lpaths + books = [books_lpath_cache.get(p, None) for p in sony_paths] + # create dict of db_id -> sony_id imap = {} - for i, j in zip(pl_book_ids, db_ids): - imap[i] = j - pl_book_ids = [i for i in pl_book_ids if i is not None] - ordered_ids = [i for i in self.tag_order[title] if i in pl_book_ids] + for book, sony_id in zip(books, sony_ids): + if book is not None: + imap[book.application_id] = sony_id + # filter the list, removing books not on device but on playlist + books = [i for i in books if i is not None] + # filter the order specification to the books we have + ordered_ids = [db_id for db_id in self.tag_order[title] if db_id in imap] + # rewrite the playlist in the correct order if len(ordered_ids) < len(pl.childNodes): continue - children = [i for i in pl.childNodes if hasattr(i, 'getAttribute')] + children = [i for i in pl.childNodes if hasattr(i, 'getAttribute')] for child in children: pl.removeChild(child) child.unlink() @@ -439,7 +363,6 @@ def fix_ids(main, carda, cardb): except KeyError: item.parentNode.removeChild(item) item.unlink() - db.reorder_playlists() regen_ids(main) diff --git a/src/calibre/devices/prs505/driver.py b/src/calibre/devices/prs505/driver.py index f4fc4b0d29..3e1ee67faa 100644 --- a/src/calibre/devices/prs505/driver.py +++ b/src/calibre/devices/prs505/driver.py @@ -11,15 +11,14 @@ Device driver for the SONY PRS-505 import os import re -import time -from itertools import cycle -from calibre.devices.usbms.cli import CLI -from calibre.devices.usbms.device import Device +from calibre.devices.usbms.driver import USBMS from calibre.devices.prs505.books import BookList, fix_ids +from calibre.devices.prs505 import MEDIA_XML +from calibre.devices.prs505 import CACHE_XML from calibre import __appname__ -class PRS505(CLI, Device): +class PRS505(USBMS): name = 'PRS-300/505 Device Interface' gui_name = 'SONY Reader' @@ -46,9 +45,6 @@ class PRS505(CLI, Device): MAIN_MEMORY_VOLUME_LABEL = 'Sony Reader Main Memory' STORAGE_CARD_VOLUME_LABEL = 'Sony Reader Storage Card' - MEDIA_XML = 'database/cache/media.xml' - CACHE_XML = 'Sony Reader/database/cache.xml' - CARD_PATH_PREFIX = __appname__ SUPPORTS_SUB_DIRS = True @@ -60,67 +56,18 @@ class PRS505(CLI, Device): 'series, tags, authors' EXTRA_CUSTOMIZATION_DEFAULT = ', '.join(['series', 'tags']) + METADATA_CACHE = "database/cache/metadata.calibre" + + def initialize(self): + USBMS.initialize(self) + self.booklist_class = BookList + def windows_filter_pnp_id(self, pnp_id): return '_LAUNCHER' in pnp_id - def open(self): - self.report_progress = lambda x, y: x - Device.open(self) - - def write_cache(prefix): - try: - cachep = os.path.join(prefix, *(self.CACHE_XML.split('/'))) - if not os.path.exists(cachep): - dname = os.path.dirname(cachep) - if not os.path.exists(dname): - try: - os.makedirs(dname, mode=0777) - except: - time.sleep(5) - os.makedirs(dname, mode=0777) - with open(cachep, 'wb') as f: - f.write(u''' - - - '''.encode('utf8')) - return True - except: - import traceback - traceback.print_exc() - return False - - if self._card_a_prefix is not None: - if not write_cache(self._card_a_prefix): - self._card_a_prefix = None - if self._card_b_prefix is not None: - if not write_cache(self._card_b_prefix): - self._card_b_prefix = None - def get_device_information(self, end_session=True): return (self.gui_name, '', '', '') - def books(self, oncard=None, end_session=True): - if oncard == 'carda' and not self._card_a_prefix: - self.report_progress(1.0, _('Getting list of books on device...')) - return [] - elif oncard == 'cardb' and not self._card_b_prefix: - self.report_progress(1.0, _('Getting list of books on device...')) - return [] - elif oncard and oncard != 'carda' and oncard != 'cardb': - self.report_progress(1.0, _('Getting list of books on device...')) - return [] - - db = self.__class__.CACHE_XML if oncard else self.__class__.MEDIA_XML - prefix = self._card_a_prefix if oncard == 'carda' else self._card_b_prefix if oncard == 'cardb' else self._main_prefix - bl = BookList(open(prefix + db, 'rb'), prefix, self.report_progress) - paths = bl.purge_corrupted_files() - for path in paths: - path = os.path.join(prefix, path) - if os.path.exists(path): - os.unlink(path) - self.report_progress(1.0, _('Getting list of books on device...')) - return bl - def filename_callback(self, fname, mi): if getattr(mi, 'application_id', None) is not None: base = fname.rpartition('.')[0] @@ -129,90 +76,17 @@ class PRS505(CLI, Device): fname = base + suffix + '.' + fname.rpartition('.')[-1] return fname - def upload_books(self, files, names, on_card=None, end_session=True, - metadata=None): - - path = self._sanity_check(on_card, files) - - paths, ctimes, sizes = [], [], [] - names = iter(names) - metadata = iter(metadata) - for i, infile in enumerate(files): - mdata, fname = metadata.next(), names.next() - filepath = self.create_upload_path(path, mdata, fname) - - paths.append(filepath) - self.put_file(infile, paths[-1], replace_file=True) - ctimes.append(os.path.getctime(paths[-1])) - sizes.append(os.stat(paths[-1]).st_size) - - self.report_progress((i+1) / float(len(files)), _('Transferring books to device...')) - - self.report_progress(1.0, _('Transferring books to device...')) - - return zip(paths, sizes, ctimes, cycle([on_card])) - - def add_books_to_metadata(self, locations, metadata, booklists): - if not locations or not metadata: - return - - metadata = iter(metadata) - for location in locations: - info = metadata.next() - path = location[0] - oncard = location[3] - blist = 2 if oncard == 'cardb' else 1 if oncard == 'carda' else 0 - - if self._main_prefix and path.startswith(self._main_prefix): - name = path.replace(self._main_prefix, '') - elif self._card_a_prefix and path.startswith(self._card_a_prefix): - name = path.replace(self._card_a_prefix, '') - elif self._card_b_prefix and path.startswith(self._card_b_prefix): - name = path.replace(self._card_b_prefix, '') - - name = name.replace('\\', '/') - name = name.replace('//', '/') - if name.startswith('/'): - name = name[1:] - - opts = self.settings() - collections = opts.extra_customization.split(',') if opts.extra_customization else [] - booklist = booklists[blist] - if not hasattr(booklist, 'add_book'): - raise ValueError(('Incorrect upload location %s. Did you choose the' - ' correct card A or B, to send books to?')%oncard) - booklist.add_book(info, name, collections, *location[1:-1]) - fix_ids(*booklists) - - def delete_books(self, paths, end_session=True): - for i, path in enumerate(paths): - self.report_progress((i+1) / float(len(paths)), _('Removing books from device...')) - if os.path.exists(path): - os.unlink(path) - try: - os.removedirs(os.path.dirname(path)) - except: - pass - self.report_progress(1.0, _('Removing books from device...')) - - @classmethod - def remove_books_from_metadata(cls, paths, booklists): - for path in paths: - for bl in booklists: - if hasattr(bl, 'remove_book'): - bl.remove_book(path) - fix_ids(*booklists) - def sync_booklists(self, booklists, end_session=True): + print 'in sync_booklists' fix_ids(*booklists) if not os.path.exists(self._main_prefix): os.makedirs(self._main_prefix) - with open(self._main_prefix + self.__class__.MEDIA_XML, 'wb') as f: + with open(self._main_prefix + MEDIA_XML, 'wb') as f: booklists[0].write(f) def write_card_prefix(prefix, listid): if prefix is not None and hasattr(booklists[listid], 'write'): - tgt = os.path.join(prefix, *(self.CACHE_XML.split('/'))) + tgt = os.path.join(prefix, *(CACHE_XML.split('/'))) base = os.path.dirname(tgt) if not os.path.exists(base): os.makedirs(base) @@ -221,8 +95,7 @@ class PRS505(CLI, Device): write_card_prefix(self._card_a_prefix, 1) write_card_prefix(self._card_b_prefix, 2) - self.report_progress(1.0, _('Sending metadata to device...')) - + USBMS.sync_booklists(self, booklists, end_session) class PRS700(PRS505): @@ -241,5 +114,3 @@ class PRS700(PRS505): OSX_MAIN_MEM = re.compile(r'Sony PRS-((700/[^:]+)|((6|9)00)) Media') OSX_CARD_A_MEM = re.compile(r'Sony PRS-((700/[^:]+:)|((6|9)00 ))MS Media') OSX_CARD_B_MEM = re.compile(r'Sony PRS-((700/[^:]+:)|((6|9)00 ))SD Media') - - diff --git a/src/calibre/devices/usbms/books.py b/src/calibre/devices/usbms/books.py index 990b335a6d..bc6003de27 100644 --- a/src/calibre/devices/usbms/books.py +++ b/src/calibre/devices/usbms/books.py @@ -33,7 +33,9 @@ class Book(MetaInformation): self.path = os.path.join(prefix, lpath) if os.sep == '\\': self.path = self.path.replace('/', '\\') - self.lpath = lpath + self.lpath = lpath.replace('\\', '/') + else: + self.lpath = lpath self.mime = mime_type_ext(path_to_ext(lpath)) self.size = os.stat(self.path).st_size if size == None else size self.db_id = None diff --git a/src/calibre/devices/usbms/driver.py b/src/calibre/devices/usbms/driver.py index 63d28f5457..1a5b7461ed 100644 --- a/src/calibre/devices/usbms/driver.py +++ b/src/calibre/devices/usbms/driver.py @@ -31,32 +31,36 @@ class USBMS(CLI, Device): CAN_SET_METADATA = True METADATA_CACHE = 'metadata.calibre' + def initialize(self): + Device.initialize(self) + self.booklist_class = BookList + def get_device_information(self, end_session=True): self.report_progress(1.0, _('Get device information...')) return (self.get_gui_name(), '', '', '') def books(self, oncard=None, end_session=True): from calibre.ebooks.metadata.meta import path_to_ext - bl = BookList() - metadata = BookList() - need_sync = False if oncard == 'carda' and not self._card_a_prefix: self.report_progress(1.0, _('Getting list of books on device...')) - return bl + return [] elif oncard == 'cardb' and not self._card_b_prefix: self.report_progress(1.0, _('Getting list of books on device...')) - return bl + return [] elif oncard and oncard != 'carda' and oncard != 'cardb': self.report_progress(1.0, _('Getting list of books on device...')) - return bl + return [] prefix = self._card_a_prefix if oncard == 'carda' else self._card_b_prefix if oncard == 'cardb' else self._main_prefix + metadata = self.booklist_class(oncard, prefix) + ebook_dirs = self.EBOOK_DIR_CARD_A if oncard == 'carda' else \ self.EBOOK_DIR_CARD_B if oncard == 'cardb' else \ self.get_main_ebook_dir() - bl, need_sync = self.parse_metadata_cache(prefix, self.METADATA_CACHE) + bl, need_sync = self.parse_metadata_cache(prefix, self.METADATA_CACHE, + self.booklist_class(oncard, prefix)) # make a dict cache of paths so the lookup in the loop below is faster. bl_cache = {} @@ -109,7 +113,6 @@ class USBMS(CLI, Device): # find on the device. If need_sync is True then there were either items # on the device that were not in bl or some of the items were changed. if self.count_found_in_bl != len(bl) or need_sync: - print 'resync' if oncard == 'cardb': self.sync_booklists((None, None, metadata)) elif oncard == 'carda': @@ -122,7 +125,6 @@ class USBMS(CLI, Device): def upload_books(self, files, names, on_card=None, end_session=True, metadata=None): - path = self._sanity_check(on_card, files) paths = [] @@ -145,7 +147,6 @@ class USBMS(CLI, Device): self.report_progress((i+1) / float(len(files)), _('Transferring books to device...')) self.report_progress(1.0, _('Transferring books to device...')) - return zip(paths, cycle([on_card])) def upload_cover(self, path, filename, metadata): @@ -174,11 +175,10 @@ class USBMS(CLI, Device): lpath = path.partition(prefix)[2] if lpath.startswith(os.sep): lpath = lpath[len(os.sep):] - lpath = lpath.replace('\\', '/') book = Book(prefix, lpath, other=info) - - if book not in booklists[blist]: - booklists[blist].append(book) + opts = self.settings() + collections = opts.extra_customization.split(',') if opts.extra_customization else [] + booklists[blist].add_book(book, collections, *location[1:-1]) self.report_progress(1.0, _('Adding books to device metadata listing...')) @@ -209,7 +209,7 @@ class USBMS(CLI, Device): for bl in booklists: for book in bl: if path.endswith(book.path): - bl.remove(book) + bl.remove_book(book) self.report_progress(1.0, _('Removing books from device metadata listing...')) def sync_booklists(self, booklists, end_session=True): @@ -217,7 +217,7 @@ class USBMS(CLI, Device): os.makedirs(self._main_prefix) def write_prefix(prefix, listid): - if prefix is not None and isinstance(booklists[listid], BookList): + if prefix is not None and isinstance(booklists[listid], self.booklist_class): if not os.path.exists(prefix): os.makedirs(prefix) js = [item.to_json() for item in booklists[listid]] @@ -230,9 +230,8 @@ class USBMS(CLI, Device): self.report_progress(1.0, _('Sending metadata to device...')) @classmethod - def parse_metadata_cache(cls, prefix, name): + def parse_metadata_cache(cls, prefix, name, bl): js = [] - bl = BookList() need_sync = False try: with open(os.path.join(prefix, name), 'rb') as f: @@ -249,7 +248,7 @@ class USBMS(CLI, Device): except: import traceback traceback.print_exc() - bl = BookList() + bl = [] return bl, need_sync @classmethod diff --git a/src/calibre/ebooks/metadata/__init__.py b/src/calibre/ebooks/metadata/__init__.py index 60dffc0cf7..a1c29be337 100644 --- a/src/calibre/ebooks/metadata/__init__.py +++ b/src/calibre/ebooks/metadata/__init__.py @@ -254,11 +254,11 @@ class MetaInformation(object): setattr(self, x, getattr(mi, x, None)) def print_all_attributes(self): - for x in ('author_sort', 'title_sort', 'comments', 'category', 'publisher', - 'series', 'series_index', 'rating', 'isbn', 'language', + for x in ('author', 'author_sort', 'title_sort', 'comments', 'category', 'publisher', + 'series', 'series_index', 'tags', 'rating', 'isbn', 'language', 'application_id', 'manifest', 'toc', 'spine', 'guide', 'cover', 'book_producer', 'timestamp', 'lccn', 'lcc', 'ddc', 'pubdate', - 'rights', 'publication_type', 'uuid', + 'rights', 'publication_type', 'uuid', 'tag_order', ): prints(x, getattr(self, x, 'None')) @@ -278,7 +278,7 @@ class MetaInformation(object): 'isbn', 'application_id', 'manifest', 'spine', 'toc', 'cover', 'language', 'guide', 'book_producer', 'timestamp', 'lccn', 'lcc', 'ddc', 'pubdate', 'rights', - 'publication_type', 'uuid',): + 'publication_type', 'uuid', 'tag_order'): if hasattr(mi, attr): val = getattr(mi, attr) if val is not None: diff --git a/src/calibre/gui2/device.py b/src/calibre/gui2/device.py index df355f0ef5..828756e2c8 100644 --- a/src/calibre/gui2/device.py +++ b/src/calibre/gui2/device.py @@ -821,7 +821,9 @@ class DeviceGUI(object): def sync_to_device(self, on_card, delete_from_library, specific_format=None, send_ids=None, do_auto_convert=True): - ids = [self.library_view.model().id(r) for r in self.library_view.selectionModel().selectedRows()] if send_ids is None else send_ids + ids = [self.library_view.model().id(r) \ + for r in self.library_view.selectionModel().selectedRows()] \ + if send_ids is None else send_ids if not self.device_manager or not ids or len(ids) == 0: return @@ -842,8 +844,7 @@ class DeviceGUI(object): ids = iter(ids) for mi in metadata: if mi.cover and os.access(mi.cover, os.R_OK): - mi.thumbnail = self.cover_to_thumbnail(open(mi.cover, - 'rb').read()) + mi.thumbnail = self.cover_to_thumbnail(open(mi.cover, 'rb').read()) imetadata = iter(metadata) files = [getattr(f, 'name', None) for f in _files] @@ -890,7 +891,9 @@ class DeviceGUI(object): bad.append(self.library_view.model().db.title(id, index_is_id=True)) if auto != []: - format = specific_format if specific_format in list(set(settings.format_map).intersection(set(available_output_formats()))) else None + format = specific_format if specific_format in \ + list(set(settings.format_map).intersection(set(available_output_formats()))) \ + else None if not format: for fmt in settings.format_map: if fmt in list(set(settings.format_map).intersection(set(available_output_formats()))): @@ -1039,7 +1042,8 @@ class DeviceGUI(object): loc[i] = True break if mi.authors and \ - re.sub('(?u)\W|[_]', '', mi.authors.lower()) in cache['authors']: + re.sub('(?u)\W|[_]', '', authors_to_string(mi.authors).lower()) \ + in cache['authors']: loc[i] = True break return loc diff --git a/src/calibre/gui2/library.py b/src/calibre/gui2/library.py index cdebf65489..e116e39397 100644 --- a/src/calibre/gui2/library.py +++ b/src/calibre/gui2/library.py @@ -17,7 +17,7 @@ from PyQt4.QtCore import QAbstractTableModel, QVariant, Qt, pyqtSignal, \ SIGNAL, QObject, QSize, QModelIndex, QDate from calibre import strftime -from calibre.ebooks.metadata import fmt_sidx, authors_to_string +from calibre.ebooks.metadata import fmt_sidx, authors_to_string, string_to_authors from calibre.ebooks.metadata.meta import set_metadata as _set_metadata from calibre.gui2 import NONE, TableView, config, error_dialog, UNDEFINED_QDATE from calibre.gui2.dialogs.comments_dialog import CommentsDialog @@ -1378,7 +1378,10 @@ class DeviceBooksModel(BooksModel): def libcmp(x, y): x, y = self.db[x].in_library, self.db[y].in_library return cmp(x, y) - fcmp = strcmp('title_sorter') if col == 0 else strcmp('authors') if col == 1 else \ + def authorcmp(x, y): + x, y = authors_to_string(self.db[x].authors), authors_to_string(self.db[y].authors) + return cmp(x, y) + fcmp = strcmp('title_sorter') if col == 0 else authorcmp if col == 1 else \ sizecmp if col == 2 else datecmp if col == 3 else tagscmp if col == 4 else libcmp self.map.sort(cmp=fcmp, reverse=descending) if len(self.map) == len(self.db): @@ -1446,9 +1449,9 @@ class DeviceBooksModel(BooksModel): au = self.db[self.map[row]].authors if not au: au = self.unknown - if role == Qt.EditRole: - return QVariant(authors_to_string(au)) - return QVariant(" & ".join(au)) +# if role == Qt.EditRole: +# return QVariant(au) + return QVariant(authors_to_string(au)) elif col == 2: size = self.db[self.map[row]].size return QVariant(BooksView.human_readable(size)) @@ -1501,7 +1504,7 @@ class DeviceBooksModel(BooksModel): self.db[idx].title = val self.db[idx].title_sorter = val elif col == 1: - self.db[idx].authors = val + self.db[idx].authors = string_to_authors(val) elif col == 4: tags = [i.strip() for i in val.split(',')] tags = [t for t in tags if t]