From ec186049493b278c14c7da0bb20061c64ef809a9 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 19 May 2010 16:31:42 -0600 Subject: [PATCH 01/11] Update tags in JSON cache based on collections in XML cache --- src/calibre/devices/prs505/sony_cache.py | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/src/calibre/devices/prs505/sony_cache.py b/src/calibre/devices/prs505/sony_cache.py index 674a2cbddd..14ac03c777 100644 --- a/src/calibre/devices/prs505/sony_cache.py +++ b/src/calibre/devices/prs505/sony_cache.py @@ -144,7 +144,7 @@ class XMLCache(object): self.ensure_unique_playlist_titles() self.prune_empty_playlists() for i, root in self.record_roots.items(): - ans[i] = {} + ans[i] = [] for playlist in root.xpath('//*[local-name()="playlist"]'): items = [] for item in playlist: @@ -153,7 +153,7 @@ class XMLCache(object): '//*[local-name()="text" and @id="%s"]'%id_) if records: items.append(records[0]) - ans[i] = {playlist.get('title'):items} + ans[i].append((playlist.get('title'), items)) return ans def get_or_create_playlist(self, bl_idx, title): @@ -256,6 +256,16 @@ class XMLCache(object): if bl_index not in self.record_roots: return root = self.record_roots[bl_index] + pmap = self.get_playlist_map()[bl_index] + playlist_map = {} + for title, records in pmap: + for record in records: + path = record.get('path', None) + if path: + if path not in playlist_map: + playlist_map[path] = [] + playlist_map[path].append(title) + for book in bl: record = self.book_by_lpath(book.lpath, root) if record is not None: @@ -282,6 +292,15 @@ class XMLCache(object): book.thumbnail = raw break break + if book.lpath in playlist_map: + tags = playlist_map[book.lpath] + if tags: + if DEBUG: + prints('Adding tags:', tags, 'to', book.title) + if not book.tags: + book.tags = [] + book.tags = list(book.tags) + book.tags += tags # }}} # Update XML from JSON {{{ From b97141b2080bb5d04c0ec13bf1e0306ad325e0e5 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 19 May 2010 17:52:37 -0600 Subject: [PATCH 02/11] Change the tags column in the device view to a Collections column that allows the user to directly edit collections on the device. Note that if the user deletes a collection taht corresponds to some data in the calibre library that would be turned intoa coleection, then the deletion has no effect, on device reconnect --- src/calibre/devices/interface.py | 8 --- src/calibre/devices/prs505/sony_cache.py | 71 ++++++++++++++++++------ src/calibre/devices/usbms/books.py | 40 ++++--------- src/calibre/gui2/library/models.py | 6 +- 4 files changed, 67 insertions(+), 58 deletions(-) diff --git a/src/calibre/devices/interface.py b/src/calibre/devices/interface.py index be58bc9b0c..df2d5500e4 100644 --- a/src/calibre/devices/interface.py +++ b/src/calibre/devices/interface.py @@ -396,14 +396,6 @@ class BookList(list): ''' Return True if the the device supports tags (collections) for this book list. ''' raise NotImplementedError() - def set_tags(self, book, tags): - ''' - Set the tags for C{book} to C{tags}. - @param tags: A list of strings. Can be empty. - @param book: A book object that is in this BookList. - ''' - raise NotImplementedError() - def add_book(self, book, replace_metadata): ''' Add the book to the booklist. Intent is to maintain any device-internal diff --git a/src/calibre/devices/prs505/sony_cache.py b/src/calibre/devices/prs505/sony_cache.py index 14ac03c777..ec4c263cf9 100644 --- a/src/calibre/devices/prs505/sony_cache.py +++ b/src/calibre/devices/prs505/sony_cache.py @@ -101,16 +101,25 @@ class XMLCache(object): # Playlist management {{{ def purge_broken_playlist_items(self, root): - for item in root.xpath( - '//*[local-name()="playlist"]/*[local-name()="item"]'): - id_ = item.get('id', None) - if id_ is None or not root.xpath( - '//*[local-name()!="item" and @id="%s"]'%id_): - if DEBUG: - prints('Purging broken playlist item:', - etree.tostring(item, with_tail=False)) - item.getparent().remove(item) - + for pl in root.xpath('//*[local-name()="playlist"]'): + seen = set([]) + for item in list(pl): + id_ = item.get('id', None) + if id_ is None or id_ in seen or not root.xpath( + '//*[local-name()!="item" and @id="%s"]'%id_): + if DEBUG: + if id_ is None: + cause = 'invalid id' + elif id_ in seen: + cause = 'duplicate item' + else: + cause = 'id not found' + prints('Purging broken playlist item:', + id_, 'from playlist:', pl.get('title', None), + 'because:', cause) + item.getparent().remove(item) + continue + seen.add(id_) def prune_empty_playlists(self): for i, root in self.record_roots.items(): @@ -175,6 +184,8 @@ class XMLCache(object): # }}} def fix_ids(self): # {{{ + if DEBUG: + prints('Running fix_ids()') def ensure_numeric_ids(root): idmap = {} @@ -294,13 +305,8 @@ class XMLCache(object): break if book.lpath in playlist_map: tags = playlist_map[book.lpath] - if tags: - if DEBUG: - prints('Adding tags:', tags, 'to', book.title) - if not book.tags: - book.tags = [] - book.tags = list(book.tags) - book.tags += tags + book.device_collections = tags + # }}} # Update XML from JSON {{{ @@ -359,7 +365,25 @@ class XMLCache(object): nsmap=playlist.nsmap, attrib={'id':id_}) playlist.append(item) - + # Delete playlist entries not in collections + for playlist in root.xpath('//*[local-name()="playlist"]'): + title = playlist.get('title', None) + if title not in collections: + if DEBUG: + prints('Deleting playlist:', playlist.get('title', '')) + playlist.getparent().remove(playlist) + continue + books = collections[title] + records = [self.book_by_lpath(b.lpath, root) for b in books] + records = [x for x in records if x is not None] + ids = [x.get('id', None) for x in records] + ids = [x for x in ids if x is not None] + for item in list(playlist): + if item.get('id', None) not in ids: + if DEBUG: + prints('Deleting item:', item.get('id', ''), + 'from playlist:', playlist.get('title', '')) + playlist.remove(item) def create_text_record(self, root, bl_id, lpath): namespace = self.namespaces[bl_id] @@ -414,8 +438,19 @@ class XMLCache(object): child.iterchildren(reversed=True).next().tail = '\n'+'\t'*level root.iterchildren(reversed=True).next().tail = '\n'+'\t'*(level-1) + def move_playlists_to_bottom(self): + for root in self.record_roots.values(): + seen = [] + for pl in root.xpath('//*[local-name()="playlist"]'): + pl.getparent().remove(pl) + seen.append(pl) + for pl in seen: + root.append(pl) + + def write(self): for i, path in self.paths.items(): + self.move_playlists_to_bottom() self.cleanup_whitespace(i) raw = etree.tostring(self.roots[i], encoding='UTF-8', xml_declaration=True) diff --git a/src/calibre/devices/usbms/books.py b/src/calibre/devices/usbms/books.py index 97c911283b..a0e3dd01d2 100644 --- a/src/calibre/devices/usbms/books.py +++ b/src/calibre/devices/usbms/books.py @@ -14,14 +14,14 @@ from calibre import isbytestring class Book(MetaInformation): - BOOK_ATTRS = ['lpath', 'size', 'mime'] + BOOK_ATTRS = ['lpath', 'size', 'mime', 'device_collections'] JSON_ATTRS = [ 'lpath', 'title', 'authors', 'mime', 'size', 'tags', 'author_sort', 'title_sort', 'comments', 'category', 'publisher', 'series', 'series_index', 'rating', 'isbn', 'language', 'application_id', 'book_producer', 'lccn', 'lcc', 'ddc', 'rights', 'publication_type', - 'uuid' + 'uuid', ] def __init__(self, prefix, lpath, size=None, other=None): @@ -29,6 +29,7 @@ class Book(MetaInformation): MetaInformation.__init__(self, '') + self.device_collections = [] self.path = os.path.join(prefix, lpath) if os.sep == '\\': self.path = self.path.replace('/', '\\') @@ -45,27 +46,7 @@ class Book(MetaInformation): self.smart_update(other) def __eq__(self, other): - spath = self.path - opath = other.path - - if not isinstance(self.path, unicode): - try: - spath = unicode(self.path) - except: - try: - spath = self.path.decode(filesystem_encoding) - except: - spath = self.path - if not isinstance(other.path, unicode): - try: - opath = unicode(other.path) - except: - try: - opath = other.path.decode(filesystem_encoding) - except: - opath = other.path - - return spath == opath + return self.path == getattr(other, 'path', None) @dynamic_property def db_id(self): @@ -119,9 +100,6 @@ class BookList(_BookList): def supports_tags(self): return True - def set_tags(self, book, tags): - book.tags = tags - def add_book(self, book, replace_metadata): if book not in self: self.append(book) @@ -134,6 +112,7 @@ class BookList(_BookList): def get_collections(self, collection_attributes): collections = {} series_categories = set([]) + collection_attributes = list(collection_attributes)+['device_collections'] for attr in collection_attributes: for book in self: val = getattr(book, attr, None) @@ -147,9 +126,12 @@ class BookList(_BookList): for category in val: if category not in collections: collections[category] = [] - collections[category].append(book) - if attr == 'series': - series_categories.add(category) + if book not in collections[category]: + collections[category].append(book) + if attr == 'series': + series_categories.add(category) + + # Sort collections for category, books in collections.items(): def tgetter(x): return getattr(x, 'title_sort', 'zzzz') diff --git a/src/calibre/gui2/library/models.py b/src/calibre/gui2/library/models.py index bd8fb20741..43816f3ea0 100644 --- a/src/calibre/gui2/library/models.py +++ b/src/calibre/gui2/library/models.py @@ -807,7 +807,7 @@ class DeviceBooksModel(BooksModel): # {{{ 'authors' : _('Author(s)'), 'timestamp' : _('Date'), 'size' : _('Size'), - 'tags' : _('Tags') + 'tags' : _('Collections') } self.marked_for_deletion = {} self.search_engine = OnDeviceSearch(self) @@ -1000,7 +1000,7 @@ class DeviceBooksModel(BooksModel): # {{{ dt = dt_factory(dt, assume_utc=True, as_utc=False) return QVariant(strftime(TIME_FMT, dt.timetuple())) elif cname == 'tags': - tags = self.db[self.map[row]].tags + tags = self.db[self.map[row]].device_collections if tags: return QVariant(', '.join(tags)) elif role == Qt.ToolTipRole and index.isValid(): @@ -1047,7 +1047,7 @@ class DeviceBooksModel(BooksModel): # {{{ elif cname == 'tags': tags = [i.strip() for i in val.split(',')] tags = [t for t in tags if t] - self.db.set_tags(self.db[idx], tags) + self.db[idx].device_collections = tags self.dataChanged.emit(index, index) self.booklist_dirtied.emit() done = True From b910e737c6f45ea3ed583f311a648a874460f3dc Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 19 May 2010 18:00:46 -0600 Subject: [PATCH 03/11] Don't turn tags surrounded by [] into collections --- src/calibre/devices/usbms/books.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/calibre/devices/usbms/books.py b/src/calibre/devices/usbms/books.py index a0e3dd01d2..f7ae6c4ef4 100644 --- a/src/calibre/devices/usbms/books.py +++ b/src/calibre/devices/usbms/books.py @@ -124,6 +124,9 @@ class BookList(_BookList): elif isinstance(val, unicode): val = [val] for category in val: + if attr == 'tags' and len(category) > 1 and \ + category[0] == '[' and category[-1] == ']': + continue if category not in collections: collections[category] = [] if book not in collections[category]: From dfa38c41418c3ea9c627dbcf4c85d18c5b451c4a Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 19 May 2010 22:26:07 -0600 Subject: [PATCH 04/11] Fix broken series sorting --- src/calibre/library/caches.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/calibre/library/caches.py b/src/calibre/library/caches.py index e280a2178b..17853b818f 100644 --- a/src/calibre/library/caches.py +++ b/src/calibre/library/caches.py @@ -578,12 +578,14 @@ class ResultCache(SearchQueryParser): self._map_filtered = list(self._map) def seriescmp(self, x, y): + sidx = self.FIELD_MAP['series'] try: - ans = cmp(self._data[x][9].lower(), self._data[y][9].lower()) + ans = cmp(self._data[x][sidx].lower(), self._data[y][sidx].lower()) except AttributeError: # Some entries may be None - ans = cmp(self._data[x][9], self._data[y][9]) + ans = cmp(self._data[x][sidx], self._data[y][sidx]) if ans != 0: return ans - return cmp(self._data[x][10], self._data[y][10]) + sidx = self.FIELD_MAP['series_index'] + return cmp(self._data[x][sidx], self._data[y][sidx]) def cmp(self, loc, x, y, asstr=True, subsort=False): try: From 6dadf8cf0ea5515c5c04c8f6a6de58c6118fec52 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 19 May 2010 22:37:31 -0600 Subject: [PATCH 05/11] If last sort was ondevice, ersort on device re-connect --- src/calibre/gui2/library/models.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/calibre/gui2/library/models.py b/src/calibre/gui2/library/models.py index 43816f3ea0..3f0dfc5065 100644 --- a/src/calibre/gui2/library/models.py +++ b/src/calibre/gui2/library/models.py @@ -102,6 +102,9 @@ class BooksModel(QAbstractTableModel): # {{{ def set_device_connected(self, is_connected): self.device_connected = is_connected self.db.refresh_ondevice() + if is_connected and self.sorted_on[0] == 'ondevice': + self.resort() + def set_book_on_device_func(self, func): self.book_on_device = func From a5204b6eac17c5200455fb6f34e7991fab13edea Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 19 May 2010 22:44:41 -0600 Subject: [PATCH 06/11] Fixes for device collections --- src/calibre/gui2/library/models.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/calibre/gui2/library/models.py b/src/calibre/gui2/library/models.py index 3f0dfc5065..66ebc4dc53 100644 --- a/src/calibre/gui2/library/models.py +++ b/src/calibre/gui2/library/models.py @@ -899,7 +899,8 @@ class DeviceBooksModel(BooksModel): # {{{ x, y = int(self.db[x].size), int(self.db[y].size) return cmp(x, y) def tagscmp(x, y): - x, y = ','.join(self.db[x].tags), ','.join(self.db[y].tags) + x = ','.join(self.db[x].device_collections) + y = ','.join(self.db[y].device_collections) return cmp(x, y) def libcmp(x, y): x, y = self.db[x].in_library, self.db[y].in_library @@ -969,7 +970,7 @@ class DeviceBooksModel(BooksModel): # {{{ data[_('Path')] = item.path dt = dt_factory(item.datetime, assume_utc=True) data[_('Timestamp')] = isoformat(dt, sep=' ', as_utc=False) - data[_('Tags')] = ', '.join(item.tags) + data[_('Collections')] = ', '.join(item.device_collections) self.new_bookdisplay_data.emit(data) def paths(self, rows): From ff99f2af2ba8290d4f38fa6a402c012d739f6067 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 19 May 2010 22:50:43 -0600 Subject: [PATCH 07/11] Fix customizing driver plugin leads to tags not being sent to device --- src/calibre/devices/prs505/driver.py | 3 ++- src/calibre/devices/usbms/books.py | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/calibre/devices/prs505/driver.py b/src/calibre/devices/prs505/driver.py index 794bf66600..7277b24723 100644 --- a/src/calibre/devices/prs505/driver.py +++ b/src/calibre/devices/prs505/driver.py @@ -101,7 +101,8 @@ class PRS505(USBMS): opts = self.settings() collections = ['series', 'tags'] if opts.extra_customization: - collections = opts.extra_customization.split(',') + collections = [x.strip() for x in + opts.extra_customization.split(',')] c.update(blists, collections) c.write() diff --git a/src/calibre/devices/usbms/books.py b/src/calibre/devices/usbms/books.py index f7ae6c4ef4..4de1341c41 100644 --- a/src/calibre/devices/usbms/books.py +++ b/src/calibre/devices/usbms/books.py @@ -114,6 +114,7 @@ class BookList(_BookList): series_categories = set([]) collection_attributes = list(collection_attributes)+['device_collections'] for attr in collection_attributes: + attr = attr.strip() for book in self: val = getattr(book, attr, None) if not val: continue From 37bfe8109d9f641424f8ff122cea79b8ee9f279e Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 19 May 2010 23:18:20 -0600 Subject: [PATCH 08/11] Fix content server to always use FIELD_MAP --- src/calibre/library/server.py | 111 ++++++++++++++++++---------------- 1 file changed, 59 insertions(+), 52 deletions(-) diff --git a/src/calibre/library/server.py b/src/calibre/library/server.py index 7023d72f0c..1a15492da3 100644 --- a/src/calibre/library/server.py +++ b/src/calibre/library/server.py @@ -7,7 +7,7 @@ __docformat__ = 'restructuredtext en' HTTP server for remote access to the calibre database. ''' -import sys, textwrap, operator, os, re, logging, cStringIO +import sys, textwrap, operator, os, re, logging, cStringIO, copy import __builtin__ from itertools import repeat from logging.handlers import RotatingFileHandler @@ -63,21 +63,21 @@ class LibraryServer(object): BOOK = textwrap.dedent('''\ ${r[8] if r[8] else ''} + size="${r[FM['size']]}" + isbn="${r[FM['isbn']] if r[FM['isbn']] else ''}" + formats="${r[FM['formats']] if r[FM['formats']] else ''}" + series = "${r[FM['series']] if r[FM['series']] else ''}" + series_index="${r[FM['series_index']]}" + tags="${r[FM['tags']] if r[FM['tags']] else ''}" + publisher="${r[FM['publisher']] if r[FM['publisher']] else ''}">${r[FM['comments']] if r[FM['comments']] else ''} ''') @@ -86,13 +86,13 @@ class LibraryServer(object): MOBILE_BOOK = textwrap.dedent('''\ - + - - ${format.lower()}  + + ${format.lower()}  - ${r[1]}${(' ['+r[9]+'-'+r[10]+']') if r[9] else ''} by ${authors} - ${r[6]/1024}k - ${r[3] if r[3] else ''} ${pubdate} ${'['+r[7]+']' if r[7] else ''} + ${r[FM['title']]}${(' ['+r[FM['series']]+'-'+r[FM['series_index']]+']') if r[FM['series']] else ''} by ${authors} - ${r[FM['size']]/1024}k - ${r[FM['publisher']] if r[FM['publisher']] else ''} ${pubdate} ${'['+r[FM['tags']]+']' if r[FM['tags']] else ''} ''') @@ -628,22 +628,23 @@ class LibraryServer(object): ids = self.db.data.parse(search) if search and search.strip() else self.db.data.universal_set() record_list = list(iter(self.db)) + FM = self.db.FIELD_MAP # Sort the record list if sortby == "bytitle" or authorid or tagid: record_list.sort(lambda x, y: - cmp(title_sort(x[self.db.FIELD_MAP['title']]), - title_sort(y[self.db.FIELD_MAP['title']]))) + cmp(title_sort(x[FM['title']]), + title_sort(y[FM['title']]))) elif seriesid: record_list.sort(lambda x, y: - cmp(x[self.db.FIELD_MAP['series_index']], - y[self.db.FIELD_MAP['series_index']])) + cmp(x[FM['series_index']], + y[FM['series_index']])) else: # Sort by date record_list = reversed(record_list) - fmts = self.db.FIELD_MAP['formats'] + fmts = FM['formats'] pat = re.compile(r'EPUB|PDB', re.IGNORECASE) - record_list = [x for x in record_list if x[0] in ids and + record_list = [x for x in record_list if x[FM['id']] in ids and pat.search(x[fmts] if x[fmts] else '') is not None] next_offset = offset + self.max_stanza_items nrecord_list = record_list[offset:next_offset] @@ -663,10 +664,10 @@ class LibraryServer(object): ) % '&'.join(q) for record in nrecord_list: - r = record[self.db.FIELD_MAP['formats']] + r = record[FM['formats']] r = r.upper() if r else '' - z = record[self.db.FIELD_MAP['authors']] + z = record[FM['authors']] if not z: z = _('Unknown') authors = ' & '.join([i.replace('|', ',') for i in @@ -674,19 +675,19 @@ class LibraryServer(object): # Setup extra description extra = [] - rating = record[self.db.FIELD_MAP['rating']] + rating = record[FM['rating']] if rating > 0: rating = ''.join(repeat('★', rating)) extra.append('RATING: %s
'%rating) - tags = record[self.db.FIELD_MAP['tags']] + tags = record[FM['tags']] if tags: extra.append('TAGS: %s
'%\ prepare_string_for_xml(', '.join(tags.split(',')))) - series = record[self.db.FIELD_MAP['series']] + series = record[FM['series']] if series: extra.append('SERIES: %s [%s]
'%\ (prepare_string_for_xml(series), - fmt_sidx(float(record[self.db.FIELD_MAP['series_index']])))) + fmt_sidx(float(record[FM['series_index']])))) fmt = 'epub' if 'EPUB' in r else 'pdb' mimetype = guess_type('dummy.'+fmt)[0] @@ -699,17 +700,18 @@ class LibraryServer(object): authors=authors, tags=tags, series=series, - FM=self.db.FIELD_MAP, + FM=FM, extra='\n'.join(extra), mimetype=mimetype, fmt=fmt, - urn=record[self.db.FIELD_MAP['uuid']], - timestamp=strftime('%Y-%m-%dT%H:%M:%S+00:00', record[5]) + urn=record[FM['uuid']], + timestamp=strftime('%Y-%m-%dT%H:%M:%S+00:00', + record[FM['timestamp']]) ) books.append(self.STANZA_ENTRY.generate(**data)\ .render('xml').decode('utf8')) - return self.STANZA.generate(subtitle='', data=books, FM=self.db.FIELD_MAP, + return self.STANZA.generate(subtitle='', data=books, FM=FM, next_link=next_link, updated=updated, id='urn:calibre:main').render('xml') @@ -734,23 +736,25 @@ class LibraryServer(object): raise cherrypy.HTTPError(400, 'num: %s is not an integer'%num) ids = self.db.data.parse(search) if search and search.strip() else self.db.data.universal_set() ids = sorted(ids) - items = [r for r in iter(self.db) if r[0] in ids] + FM = self.db.FIELD_MAP + items = copy.deepcopy([r for r in iter(self.db) if r[FM['id']] in ids]) if sort is not None: self.sort(items, sort, (order.lower().strip() == 'ascending')) book, books = MarkupTemplate(self.MOBILE_BOOK), [] for record in items[(start-1):(start-1)+num]: - if record[13] is None: - record[13] = '' - if record[6] is None: - record[6] = 0 - aus = record[2] if record[2] else __builtin__._('Unknown') + if record[FM['formats']] is None: + record[FM['formats']] = '' + if record[FM['size']] is None: + record[FM['size']] = 0 + aus = record[FM['authors']] if record[FM['authors']] else __builtin__._('Unknown') authors = '|'.join([i.replace('|', ',') for i in aus.split(',')]) - record[10] = fmt_sidx(float(record[10])) - ts, pd = strftime('%Y/%m/%d %H:%M:%S', record[5]), \ - strftime('%Y/%m/%d %H:%M:%S', record[self.db.FIELD_MAP['pubdate']]) + record[FM['series_index']] = \ + fmt_sidx(float(record[FM['series_index']])) + ts, pd = strftime('%Y/%m/%d %H:%M:%S', record[FM['timestamp']]), \ + strftime('%Y/%m/%d %H:%M:%S', record[FM['pubdate']]) books.append(book.generate(r=record, authors=authors, timestamp=ts, - pubdate=pd).render('xml').decode('utf-8')) + pubdate=pd, FM=FM).render('xml').decode('utf-8')) updated = self.db.last_modified() cherrypy.response.headers['Content-Type'] = 'text/html; charset=utf-8' @@ -759,8 +763,9 @@ class LibraryServer(object): url_base = "/mobile?search=" + search+";order="+order+";sort="+sort+";num="+str(num) - return self.MOBILE.generate(books=books, start=start, updated=updated, search=search, sort=sort, order=order, num=num, - total=len(ids), url_base=url_base).render('html') + return self.MOBILE.generate(books=books, start=start, updated=updated, + search=search, sort=sort, order=order, num=num, FM=FM, + total=len(ids), url_base=url_base).render('html') @expose @@ -785,25 +790,27 @@ class LibraryServer(object): order = order.lower().strip() == 'ascending' ids = self.db.data.parse(search) if search and search.strip() else self.db.data.universal_set() ids = sorted(ids) - items = [r for r in iter(self.db) if r[0] in ids] + FM = self.db.FIELD_MAP + items = copy.deepcopy([r for r in iter(self.db) if r[FM['id']] in ids]) if sort is not None: self.sort(items, sort, order) book, books = MarkupTemplate(self.BOOK), [] for record in items[start:start+num]: - aus = record[2] if record[2] else __builtin__._('Unknown') + aus = record[FM['authors']] if record[FM['authors']] else __builtin__._('Unknown') authors = '|'.join([i.replace('|', ',') for i in aus.split(',')]) - record[10] = fmt_sidx(float(record[10])) - ts, pd = strftime('%Y/%m/%d %H:%M:%S', record[5]), \ - strftime('%Y/%m/%d %H:%M:%S', record[self.db.FIELD_MAP['pubdate']]) + record[FM['series_index']] = \ + fmt_sidx(float(record[FM['series_index']])) + ts, pd = strftime('%Y/%m/%d %H:%M:%S', record[FM['timestamp']]), \ + strftime('%Y/%m/%d %H:%M:%S', record[FM['pubdate']]) books.append(book.generate(r=record, authors=authors, timestamp=ts, - pubdate=pd).render('xml').decode('utf-8')) + pubdate=pd, FM=FM).render('xml').decode('utf-8')) updated = self.db.last_modified() cherrypy.response.headers['Content-Type'] = 'text/xml' cherrypy.response.headers['Last-Modified'] = self.last_modified(updated) return self.LIBRARY.generate(books=books, start=start, updated=updated, - total=len(ids)).render('xml') + total=len(ids), FM=FM).render('xml') @expose def index(self, **kwargs): From 676bf2b00a623537ce1212fb7a1c39e8af1dceea Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 20 May 2010 00:09:20 -0600 Subject: [PATCH 09/11] Consolidate all sony drivers into one class --- src/calibre/customize/builtins.py | 3 +- src/calibre/devices/prs505/driver.py | 51 +++++++++++----------------- 2 files changed, 20 insertions(+), 34 deletions(-) diff --git a/src/calibre/customize/builtins.py b/src/calibre/customize/builtins.py index 9a32774f5f..045d6289b7 100644 --- a/src/calibre/customize/builtins.py +++ b/src/calibre/customize/builtins.py @@ -442,7 +442,7 @@ from calibre.devices.irexdr.driver import IREXDR1000, IREXDR800 from calibre.devices.jetbook.driver import JETBOOK from calibre.devices.kindle.driver import KINDLE, KINDLE2, KINDLE_DX from calibre.devices.nook.driver import NOOK -from calibre.devices.prs505.driver import PRS505, PRS700 +from calibre.devices.prs505.driver import PRS505 from calibre.devices.android.driver import ANDROID, S60 from calibre.devices.nokia.driver import N770, N810 from calibre.devices.eslick.driver import ESLICK @@ -510,7 +510,6 @@ plugins += [ KINDLE_DX, NOOK, PRS505, - PRS700, ANDROID, S60, N770, diff --git a/src/calibre/devices/prs505/driver.py b/src/calibre/devices/prs505/driver.py index 7277b24723..74e1bf0a7e 100644 --- a/src/calibre/devices/prs505/driver.py +++ b/src/calibre/devices/prs505/driver.py @@ -1,12 +1,9 @@ -# -*- coding: utf-8 -*- - __license__ = 'GPL v3' -__copyright__ = '2008, Kovid Goyal ' \ - '2009, John Schember ' +__copyright__ = '2008, Kovid Goyal ' __docformat__ = 'restructuredtext en' ''' -Device driver for the SONY PRS-505 +Device driver for the SONY devices ''' import os @@ -20,27 +17,33 @@ from calibre import __appname__ class PRS505(USBMS): - name = 'PRS-300/505 Device Interface' + name = 'SONY Device Interface' gui_name = 'SONY Reader' - description = _('Communicate with the Sony PRS-300/505/500 eBook reader.') - author = 'Kovid Goyal and John Schember' + description = _('Communicate with all the Sony eBook readers.') + author = 'Kovid Goyal' supported_platforms = ['windows', 'osx', 'linux'] path_sep = '/' FORMATS = ['epub', 'lrf', 'lrx', 'rtf', 'pdf', 'txt'] VENDOR_ID = [0x054c] #: SONY Vendor Id - PRODUCT_ID = [0x031e] #: Product Id for the PRS 300/505/new 500 - BCD = [0x229, 0x1000, 0x22a] + PRODUCT_ID = [0x031e] + BCD = [0x229, 0x1000, 0x22a, 0x31a] VENDOR_NAME = 'SONY' - WINDOWS_MAIN_MEM = re.compile('PRS-(505|300|500)') - WINDOWS_CARD_A_MEM = re.compile(r'PRS-(505|500)[#/]\S+:MS') - WINDOWS_CARD_B_MEM = re.compile(r'PRS-(505|500)[#/]\S+:SD') + WINDOWS_MAIN_MEM = re.compile( + r'(PRS-(505|300|500))|' + r'(PRS-((700[#/])|((6|9)00&)))' + ) + WINDOWS_CARD_A_MEM = re.compile( + r'(PRS-(505|500)[#/]\S+:MS)|' + r'(PRS-((700[/#]\S+:)|((6|9)00[#_]))MS)' + ) + WINDOWS_CARD_B_MEM = re.compile( + r'(PRS-(505|500)[#/]\S+:SD)|' + r'(PRS-((700[/#]\S+:)|((6|9)00[#_]))SD)' + ) - OSX_MAIN_MEM = re.compile(r'Sony PRS-(((505|300|500)/[^:]+)|(300)) Media') - OSX_CARD_A_MEM = re.compile(r'Sony PRS-(505|500)/[^:]+:MS Media') - OSX_CARD_B_MEM = re.compile(r'Sony PRS-(505|500)/[^:]+:SD Media') MAIN_MEMORY_VOLUME_LABEL = 'Sony Reader Main Memory' STORAGE_CARD_VOLUME_LABEL = 'Sony Reader Storage Card' @@ -109,20 +112,4 @@ class PRS505(USBMS): USBMS.sync_booklists(self, booklists, end_session=end_session) -class PRS700(PRS505): - name = 'PRS-600/700/900 Device Interface' - description = _('Communicate with the Sony PRS-600/700/900 eBook reader.') - author = 'Kovid Goyal and John Schember' - gui_name = 'SONY Reader' - supported_platforms = ['windows', 'osx', 'linux'] - - BCD = [0x31a] - - WINDOWS_MAIN_MEM = re.compile('PRS-((700[#/])|((6|9)00&))') - WINDOWS_CARD_A_MEM = re.compile(r'PRS-((700[/#]\S+:)|((6|9)00[#_]))MS') - WINDOWS_CARD_B_MEM = re.compile(r'PRS-((700[/#]\S+:)|((6|9)00[#_]))SD') - - 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') From 702a2030131f6d2c0e73a49d1963e5c3088044f0 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 20 May 2010 00:14:14 -0600 Subject: [PATCH 10/11] Remove tag_order kludge --- src/calibre/devices/interface.py | 3 +-- src/calibre/ebooks/metadata/__init__.py | 4 ++-- src/calibre/gui2/library/models.py | 3 --- 3 files changed, 3 insertions(+), 7 deletions(-) diff --git a/src/calibre/devices/interface.py b/src/calibre/devices/interface.py index df2d5500e4..f71585fad0 100644 --- a/src/calibre/devices/interface.py +++ b/src/calibre/devices/interface.py @@ -293,8 +293,7 @@ class DevicePlugin(Plugin): put the book. len(metadata) == len(files). Apart from the regular cover (path to cover), there may also be a thumbnail attribute, which should be used in preference. The thumbnail attribute is of the form - (width, height, cover_data as jpeg). In addition the MetaInformation - objects can have a tag_order attribute. + (width, height, cover_data as jpeg). ''' raise NotImplementedError() diff --git a/src/calibre/ebooks/metadata/__init__.py b/src/calibre/ebooks/metadata/__init__.py index a1c29be337..6b573a0420 100644 --- a/src/calibre/ebooks/metadata/__init__.py +++ b/src/calibre/ebooks/metadata/__init__.py @@ -258,7 +258,7 @@ class MetaInformation(object): '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', 'tag_order', + 'rights', 'publication_type', 'uuid' ): 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', 'tag_order'): + 'publication_type', 'uuid'): if hasattr(mi, attr): val = getattr(mi, attr) if val is not None: diff --git a/src/calibre/gui2/library/models.py b/src/calibre/gui2/library/models.py index 66ebc4dc53..cb911d4106 100644 --- a/src/calibre/gui2/library/models.py +++ b/src/calibre/gui2/library/models.py @@ -343,9 +343,6 @@ class BooksModel(QAbstractTableModel): # {{{ ans = [] for id in ids: mi = self.db.get_metadata(id, index_is_id=True, get_cover=True) - if mi.series is not None: - mi.tag_order = { mi.series: self.db.books_in_series_of(id, - index_is_id=True)} ans.append(mi) return ans From 02c3d52c3ea94969f2ec036d668a849f67abaa58 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 20 May 2010 00:24:41 -0600 Subject: [PATCH 11/11] Fix device collections column not being updated when sending book to SONY --- src/calibre/devices/prs505/sony_cache.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/calibre/devices/prs505/sony_cache.py b/src/calibre/devices/prs505/sony_cache.py index ec4c263cf9..262d1a3f64 100644 --- a/src/calibre/devices/prs505/sony_cache.py +++ b/src/calibre/devices/prs505/sony_cache.py @@ -266,6 +266,8 @@ class XMLCache(object): def update_booklist(self, bl, bl_index): if bl_index not in self.record_roots: return + if DEBUG: + prints('Updating JSON cache:', bl_index) root = self.record_roots[bl_index] pmap = self.get_playlist_map()[bl_index] playlist_map = {} @@ -315,7 +317,7 @@ class XMLCache(object): for i, booklist in booklists.items(): if DEBUG: - prints('Updating booklist:', i) + prints('Updating XML Cache:', i) root = self.record_roots[i] for book in booklist: path = os.path.join(self.prefixes[i], *(book.lpath.split('/'))) @@ -329,6 +331,10 @@ class XMLCache(object): self.fix_ids() + # This is needed to update device_collections + for i, booklist in booklists.items(): + self.update_booklist(booklist, i) + def update_playlists(self, bl_index, root, booklist, playlist_map, collections_attributes): collections = booklist.get_collections(collections_attributes)