From 372cc73c13abb6c0d2d9e7ce1613e638ad948a4f Mon Sep 17 00:00:00 2001 From: Kolenka Date: Sat, 8 Oct 2011 08:15:37 -0700 Subject: [PATCH 1/7] Sony T1: Fix variables so configuration is a bit more appropriate. --- src/calibre/devices/prst1/driver.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/calibre/devices/prst1/driver.py b/src/calibre/devices/prst1/driver.py index 5c756cdabc..13f503fc00 100644 --- a/src/calibre/devices/prst1/driver.py +++ b/src/calibre/devices/prst1/driver.py @@ -42,8 +42,8 @@ class PRST1(USBMS): ) THUMBNAIL_HEIGHT = 144 - SCAN_FROM_ROOT = True - SUPPORT_SUB_DIRS = True + SUPPORTS_SUB_DIRS = True + MUST_READ_METADATA = True EBOOK_DIR_MAIN = 'Sony_Reader/media/books' EXTRA_CUSTOMIZATION_MESSAGE = [ From a2f2c3355c35f7b5b3d96172d60efee76fb864b2 Mon Sep 17 00:00:00 2001 From: Kolenka Date: Sat, 8 Oct 2011 18:17:33 -0700 Subject: [PATCH 2/7] Sony T1: Fix caching behavior so detection of books in the library is more consistent. Also fixes a minor bug with direct editing of collections on the device. --- src/calibre/devices/prst1/driver.py | 52 ++++++----------------------- 1 file changed, 11 insertions(+), 41 deletions(-) diff --git a/src/calibre/devices/prst1/driver.py b/src/calibre/devices/prst1/driver.py index 13f503fc00..8ec6a3d620 100644 --- a/src/calibre/devices/prst1/driver.py +++ b/src/calibre/devices/prst1/driver.py @@ -84,10 +84,9 @@ class PRST1(USBMS): prefix = self._card_a_prefix if oncard == 'carda' else self._main_prefix - # get the metadata cache + # Let parent driver get the books self.booklist_class.rebuild_collections = self.rebuild_collections - bl = self.booklist_class(oncard, prefix, self.settings) - need_sync = self.parse_metadata_cache(bl, prefix, self.METADATA_CACHE) + bl = USBMS.books(self, oncard=oncard, end_session=end_session) debug_print("SQLite DB Path: " + self.normalize_path(prefix + 'Sony_Reader/database/books.db')) @@ -108,47 +107,17 @@ class PRST1(USBMS): for i, row in enumerate(cursor): bl_collections.setdefault(row[0], []) bl_collections[row[0]].append(row[1]) - - # Query books themselves - query = 'select _id, file_path, title, author, mime_type, modified_date, thumbnail, file_size ' \ - 'from books' - cursor.execute (query) - - # make a dict cache of paths so the lookup in the loop below is faster. - bl_cache = {} - for idx,b in enumerate(bl): - bl_cache[b.lpath] = idx - - changed = False - for i, row in enumerate(cursor): - #Book(prefix, bookId, lpath, title, author, mime, date, thumbnail_name, size=None, other=None) - thumbnail = row[6] - if thumbnail is not None: - thumbnail = self.normalize_path(prefix + row[6]) + + for idx,book in enumerate(bl): + query = 'select _id from books where file_path = ?' + t = (book.lpath,) + cursor.execute (query, t) - book = Book(row[0], prefix, row[1], row[2], row[3], row[4], row[5], thumbnail, row[7]) - book.device_collections = bl_collections.get(row[0], None) - debug_print('Collections for ' + row[2] + ': ' + str(book.device_collections)) - bl_cache[row[1]] = None - if bl.add_book(book, replace_metadata=True): - changed = True - - # Remove books that are no longer in the filesystem. Cache contains - # indices into the booklist if book not in filesystem, None otherwise - # Do the operation in reverse order so indices remain valid - for idx in sorted(bl_cache.itervalues(), reverse=True): - if idx is not None: - changed = True - del bl[idx] + for i, row in enumerate(cursor): + book.device_collections = bl_collections.get(row[0], None) cursor.close() - if changed: - if oncard == 'carda': - self.sync_booklists((None, bl, None)) - else: - self.sync_booklists((bl, None, None)) - return bl def sync_booklists(self, booklists, end_session=True): @@ -269,7 +238,8 @@ class PRST1(USBMS): for book in books: if dbBooks.get(book.lpath, None) is None: - book.device_collections.append(collection) + if collection not in book.device_collections: + book.device_collections.append(collection) query = 'insert into collections (collection_id, content_id) values (?,?)' t = (dbCollections[collection], book.bookId) cursor.execute(query, t) From ff7f90c2eced345d9e9d2fa57ec056164a3b38dd Mon Sep 17 00:00:00 2001 From: Kolenka Date: Sun, 9 Oct 2011 09:28:02 -0700 Subject: [PATCH 3/7] Sony T1: Support for copying covers similar to the 505 driver. Performs the copy during sync_booklists due to having access to the book's id/lpath at that point, and being able to detect new books. When upload_cover is normally called, this information is usually not accessible, and the book isn't actually in the database. It's less fragile this way. Fixes an issue with setting floating point values in 'added_time' column. Also removes Book from books.py (not needed at this point) --- src/calibre/devices/prst1/books.py | 35 -------------- src/calibre/devices/prst1/driver.py | 73 +++++++++++++++++++++++++++-- 2 files changed, 69 insertions(+), 39 deletions(-) diff --git a/src/calibre/devices/prst1/books.py b/src/calibre/devices/prst1/books.py index a840d13b5a..40e70f2af0 100644 --- a/src/calibre/devices/prst1/books.py +++ b/src/calibre/devices/prst1/books.py @@ -3,41 +3,6 @@ __copyright__ = '2010, Timothy Legge ' ''' ''' -import os -import time - -from calibre.devices.usbms.books import Book as Book_ - -class Book(Book_): - - def __init__(self, bookId, prefix, lpath, title, author, mime, date, thumbnail_name, size=None, other=None): - Book_.__init__(self, prefix, lpath) - - self.bookId = bookId - self.title = title - if not author: - self.authors = [''] - else: - self.authors = [author] - - if not title: - self.title = _('Unknown') - - self.mime = mime - - self.size = size # will be set later if None - - try: - self.datetime = time.gmtime(os.path.getctime(self.path)) - except: - self.datetime = time.gmtime() - - if thumbnail_name is not None: - self.thumbnail = ImageWrapper(thumbnail_name) - self.tags = [] - if other: - self.smart_update(other) - class ImageWrapper(object): def __init__(self, image_path): self.image_path = image_path diff --git a/src/calibre/devices/prst1/driver.py b/src/calibre/devices/prst1/driver.py index 8ec6a3d620..0034224ac9 100644 --- a/src/calibre/devices/prst1/driver.py +++ b/src/calibre/devices/prst1/driver.py @@ -18,7 +18,7 @@ from calibre.devices.usbms.driver import USBMS, debug_print from calibre import __appname__, prints from calibre.devices.usbms.books import CollectionsBookList from calibre.devices.usbms.books import BookList -from calibre.devices.prst1.books import Book +from calibre.devices.prst1.books import ImageWrapper class PRST1(USBMS): name = 'SONY PRST1 and newer Device Interface' @@ -50,12 +50,41 @@ class PRST1(USBMS): _('Comma separated list of metadata fields ' 'to turn into collections on the device. Possibilities include: ')+\ 'series, tags, authors', + _('Upload separate cover thumbnails for books') + + ':::'+_('Normally, the SONY readers get the cover image from the' + ' ebook file itself. With this option, calibre will send a ' + 'separate cover image to the reader, useful if you are ' + 'sending DRMed books in which you cannot change the cover.'), + _('Refresh separate covers when using automatic management') + + ':::' + + _('Set this option to have separate book covers uploaded ' + 'every time you connect your device. Unset this option if ' + 'you have so many books on the reader that performance is ' + 'unacceptable.'), + _('Preserve cover aspect ratio when building thumbnails') + + ':::' + + _('Set this option if you want the cover thumbnails to have ' + 'the same aspect ratio (width to height) as the cover. ' + 'Unset it if you want the thumbnail to be the maximum size, ' + 'ignoring aspect ratio.'), ] EXTRA_CUSTOMIZATION_DEFAULT = [ ', '.join(['series', 'tags']), + False, + False, + True, ] OPT_COLLECTIONS = 0 + OPT_UPLOAD_COVERS = 1 + OPT_REFRESH_COVERS = 2 + OPT_PRESERVE_ASPECT_RATIO = 3 + + def post_open_callback(self): + # Set the thumbnail width to the theoretical max if the user has asked + # that we do not preserve aspect ratio + if not self.settings().extra_customization[self.OPT_PRESERVE_ASPECT_RATIO]: + self.THUMBNAIL_WIDTH = 108 def windows_filter_pnp_id(self, pnp_id): return '_LAUNCHER' in pnp_id or '_SETTING' in pnp_id @@ -109,12 +138,17 @@ class PRST1(USBMS): bl_collections[row[0]].append(row[1]) for idx,book in enumerate(bl): - query = 'select _id from books where file_path = ?' + query = 'select _id, thumbnail from books where file_path = ?' t = (book.lpath,) cursor.execute (query, t) for i, row in enumerate(cursor): - book.device_collections = bl_collections.get(row[0], None) + book.device_collections = bl_collections.get(row[0], None) + thumbnail = row[1] + if thumbnail is not None: + thumbnail = self.normalize_path(prefix + thumbnail) + book.thumbnail = ImageWrapper(thumbnail) + debug_print('Got thumnail for :' + book.title) cursor.close() @@ -155,6 +189,10 @@ class PRST1(USBMS): debug_print('PRST1: finished update_device_database') def update_device_books(self, connection, booklist, source_id): + opts = self.settings() + upload_covers = opts.extra_customization[self.OPT_UPLOAD_COVERS]; + refresh_covers = opts.extra_customization[self.OPT_REFRESH_COVERS] + cursor = connection.cursor() # Get existing books @@ -173,9 +211,11 @@ class PRST1(USBMS): query = 'insert into books ' \ '(title, author, source_id, added_date, modified_date, file_path, file_name, file_size, mime_type, corrupted, prevent_delete) ' \ 'values (?,?,?,?,?,?,?,?,?,0,0)' - t = (book.title, book.authors[0], source_id, time.time() * 1000, calendar.timegm(book.datetime), lpath, os.path.basename(book.lpath), book.size, book.mime ) + t = (book.title, book.authors[0], source_id, int(time.time() * 1000), calendar.timegm(book.datetime), lpath, os.path.basename(book.lpath), book.size, book.mime ) cursor.execute(query, t) book.bookId = cursor.lastrowid + if upload_covers: + self.upload_book_cover(connection, book, source_id) debug_print('Inserted New Book: ' + book.title) else: query = 'update books ' \ @@ -184,6 +224,8 @@ class PRST1(USBMS): t = (book.title, book.authors[0], calendar.timegm(book.datetime), book.size, lpath) cursor.execute(query, t) book.bookId = dbBooks[lpath] + if refresh_covers: + self.upload_book_cover(connection, book, source_id) dbBooks[lpath] = None for book, bookId in dbBooks.items(): @@ -289,4 +331,27 @@ class PRST1(USBMS): self.update_device_database(booklist, collections, oncard) debug_print('PRS-T1: finished rebuild_collections') + + def upload_book_cover(self, connection, book, source_id): + debug_print('PRST1: Uploading/Refreshing Cover for ' + book.title) + cursor = connection.cursor() + if book.thumbnail and book.thumbnail[-1]: + thumbnailPath = 'Sony_Reader/database/cache/books/' + str(book.bookId) +'/thumbnail/main_thumbnail.jpg' + + prefix = self._main_prefix if source_id is 0 else self._card_a_prefix + thumbnailFilePath = os.path.join(prefix, *thumbnailPath.split('/')) + thumbnailDirPath = os.path.dirname(thumbnailFilePath) + if not os.path.exists(thumbnailDirPath): + os.makedirs(thumbnailDirPath) + + with open(thumbnailFilePath, 'wb') as f: + f.write(book.thumbnail[-1]) + + query = 'update books ' \ + 'set thumbnail = ?' \ + 'where _id = ? ' + t = (thumbnailPath,book.bookId,) + cursor.execute(query, t) + + cursor.close() \ No newline at end of file From d808ffd23148dbc873bce2821883e51c0896d43b Mon Sep 17 00:00:00 2001 From: Kolenka Date: Sun, 9 Oct 2011 17:00:24 -0700 Subject: [PATCH 4/7] Sony T1: Support "added_order" for collections, and enable plugboards. --- src/calibre/devices/prst1/driver.py | 49 +++++++++++++++++++++++------ 1 file changed, 39 insertions(+), 10 deletions(-) diff --git a/src/calibre/devices/prst1/driver.py b/src/calibre/devices/prst1/driver.py index 0034224ac9..0987e21a1c 100644 --- a/src/calibre/devices/prst1/driver.py +++ b/src/calibre/devices/prst1/driver.py @@ -31,6 +31,7 @@ class PRST1(USBMS): FORMATS = ['epub', 'pdf', 'txt'] CAN_SET_METADATA = ['title', 'authors', 'collections'] + CAN_DO_DEVICE_DB_PLUGBOARD = True VENDOR_ID = [0x054c] #: SONY Vendor Id PRODUCT_ID = [0x05c2] @@ -80,6 +81,9 @@ class PRST1(USBMS): OPT_REFRESH_COVERS = 2 OPT_PRESERVE_ASPECT_RATIO = 3 + plugboards = None + plugboard_func = None + def post_open_callback(self): # Set the thumbnail width to the theoretical max if the user has asked # that we do not preserve aspect ratio @@ -148,12 +152,15 @@ class PRST1(USBMS): if thumbnail is not None: thumbnail = self.normalize_path(prefix + thumbnail) book.thumbnail = ImageWrapper(thumbnail) - debug_print('Got thumnail for :' + book.title) cursor.close() return bl + def set_plugboards(self, plugboards, pb_func): + self.plugboards = plugboards + self.plugboard_func = pb_func + def sync_booklists(self, booklists, end_session=True): debug_print('PRST1: starting sync_booklists') @@ -176,6 +183,11 @@ class PRST1(USBMS): def update_device_database(self, booklist, collections_attributes, oncard): debug_print('PRST1: starting update_device_database') + plugboard = None + if self.plugboard_func: + plugboard = self.plugboard_func(self.__class__.__name__, 'device_db', self.plugboards) + debug_print("PRST1: Using Plugboard", plugboard) + prefix = self._card_a_prefix if oncard == 'carda' else self._main_prefix source_id = 1 if oncard == 'carda' else 0 debug_print("SQLite DB Path: " + self.normalize_path(prefix + 'Sony_Reader/database/books.db')) @@ -183,14 +195,14 @@ class PRST1(USBMS): collections = booklist.get_collections(collections_attributes) with closing(sqlite.connect(self.normalize_path(prefix + 'Sony_Reader/database/books.db'))) as connection: - self.update_device_books(connection, booklist, source_id) + self.update_device_books(connection, booklist, source_id, plugboard) self.update_device_collections(connection, booklist, collections, source_id) debug_print('PRST1: finished update_device_database') - def update_device_books(self, connection, booklist, source_id): + def update_device_books(self, connection, booklist, source_id, plugboard): opts = self.settings() - upload_covers = opts.extra_customization[self.OPT_UPLOAD_COVERS]; + upload_covers = opts.extra_customization[self.OPT_UPLOAD_COVERS] refresh_covers = opts.extra_customization[self.OPT_REFRESH_COVERS] cursor = connection.cursor() @@ -205,13 +217,24 @@ class PRST1(USBMS): lpath = row[0].replace('\\', '/') dbBooks[lpath] = row[1] - for book in booklist: + for book in booklist: + # Run through plugboard if needed + if plugboard is not None: + newmi = book.deepcopy_metadata() + newmi.template_to_attribute(book, plugboard) + else: + newmi = book + + # Get Metadata We Want lpath = book.lpath + author = newmi.authors[0] + title = newmi.title + if lpath not in dbBooks: query = 'insert into books ' \ '(title, author, source_id, added_date, modified_date, file_path, file_name, file_size, mime_type, corrupted, prevent_delete) ' \ 'values (?,?,?,?,?,?,?,?,?,0,0)' - t = (book.title, book.authors[0], source_id, int(time.time() * 1000), calendar.timegm(book.datetime), lpath, os.path.basename(book.lpath), book.size, book.mime ) + t = (title, author, source_id, int(time.time() * 1000), calendar.timegm(book.datetime), lpath, os.path.basename(book.lpath), book.size, book.mime ) cursor.execute(query, t) book.bookId = cursor.lastrowid if upload_covers: @@ -221,7 +244,7 @@ class PRST1(USBMS): query = 'update books ' \ 'set title = ?, author = ?, modified_date = ?, file_size = ? ' \ 'where file_path = ?' - t = (book.title, book.authors[0], calendar.timegm(book.datetime), book.size, lpath) + t = (title, author, calendar.timegm(book.datetime), book.size, lpath) cursor.execute(query, t) book.bookId = dbBooks[lpath] if refresh_covers: @@ -278,14 +301,20 @@ class PRST1(USBMS): for i, row in enumerate(cursor): dbBooks[row[0]] = row[1] - for book in books: + for idx, book in enumerate(books): if dbBooks.get(book.lpath, None) is None: if collection not in book.device_collections: book.device_collections.append(collection) - query = 'insert into collections (collection_id, content_id) values (?,?)' - t = (dbCollections[collection], book.bookId) + query = 'insert into collections (collection_id, content_id, added_order) values (?,?,?)' + t = (dbCollections[collection], book.bookId, idx) cursor.execute(query, t) debug_print('Inserted Book Into Collection: ' + book.title + ' -> ' + collection) + else: + query = 'update collections ' \ + 'set added_order = ? ' \ + 'where content_id = ? and collection_id = ? ' + t = (idx, book.bookId, dbCollections[collection]) + cursor.execute(query, t) dbBooks[book.lpath] = None From 948d176e242c2700d620056825f847ea2de1d341 Mon Sep 17 00:00:00 2001 From: Kolenka Date: Sun, 9 Oct 2011 17:17:03 -0700 Subject: [PATCH 5/7] Sony T1: Disable editing title/author on the device directly. It does no good --- src/calibre/devices/prst1/driver.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/calibre/devices/prst1/driver.py b/src/calibre/devices/prst1/driver.py index 0987e21a1c..0fde160e50 100644 --- a/src/calibre/devices/prst1/driver.py +++ b/src/calibre/devices/prst1/driver.py @@ -30,7 +30,7 @@ class PRST1(USBMS): booklist_class = CollectionsBookList FORMATS = ['epub', 'pdf', 'txt'] - CAN_SET_METADATA = ['title', 'authors', 'collections'] + CAN_SET_METADATA = ['collections'] CAN_DO_DEVICE_DB_PLUGBOARD = True VENDOR_ID = [0x054c] #: SONY Vendor Id From 236778f22f7b0d7ea87c9355d2cefe768697d7f1 Mon Sep 17 00:00:00 2001 From: Kolenka Date: Mon, 10 Oct 2011 09:18:43 -0700 Subject: [PATCH 6/7] Sony T1: Tweaks/Bugfixes based on feedback/discovery --- src/calibre/devices/prst1/driver.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/calibre/devices/prst1/driver.py b/src/calibre/devices/prst1/driver.py index 327334aaec..0a5ef4139f 100644 --- a/src/calibre/devices/prst1/driver.py +++ b/src/calibre/devices/prst1/driver.py @@ -265,7 +265,7 @@ class PRST1(USBMS): values (?,?,?,?,?,?,?,?,?,0,0) ''' t = (title, author, source_id, int(time.time() * 1000), - calendar.timegm(book.datetime), lpath, + int(calendar.timegm(book.datetime) * 1000), lpath, os.path.basename(book.lpath), book.size, book.mime) cursor.execute(query, t) book.bookId = cursor.lastrowid @@ -278,7 +278,7 @@ class PRST1(USBMS): SET title = ?, author = ?, modified_date = ?, file_size = ? WHERE file_path = ? ''' - t = (title, author, calendar.timegm(book.datetime), book.size, + t = (title, author, int(calendar.timegm(book.datetime) * 1000), book.size, lpath) cursor.execute(query, t) book.bookId = db_books[lpath] @@ -337,9 +337,9 @@ class PRST1(USBMS): db_books[row[0]] = row[1] for idx, book in enumerate(books): + if collection not in book.device_collections: + book.device_collections.append(collection) if db_books.get(book.lpath, None) is None: - if collection not in book.device_collections: - book.device_collections.append(collection) query = ''' INSERT INTO collections (collection_id, content_id, added_order) values (?,?,?) @@ -424,4 +424,5 @@ class PRST1(USBMS): t = (thumbnail_path, book.bookId,) cursor.execute(query, t) + connection.commit() cursor.close() From 4f0fc544bdcb646b7952b1ac84d83a6070b8e6c7 Mon Sep 17 00:00:00 2001 From: Kolenka Date: Mon, 10 Oct 2011 09:36:04 -0700 Subject: [PATCH 7/7] Sony T1: Ensure books that are resent have their cover refreshed. --- src/calibre/devices/prst1/driver.py | 32 +++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/src/calibre/devices/prst1/driver.py b/src/calibre/devices/prst1/driver.py index 0a5ef4139f..ca8e2ae435 100644 --- a/src/calibre/devices/prst1/driver.py +++ b/src/calibre/devices/prst1/driver.py @@ -403,6 +403,38 @@ class PRST1(USBMS): debug_print('PRS-T1: finished rebuild_collections') + def upload_cover(self, path, filename, metadata, filepath): + debug_print('PRS-T1: uploading cover') + + if filepath.startswith(self._main_prefix): + prefix = self._main_prefix + source_id = 0 + else: + prefix = self._card_a_prefix + source_id = 1 + + metadata.lpath = filepath.partition(prefix)[2] + dbpath = self.normalize_path(prefix + DBPATH) + debug_print("SQLite DB Path: " + dbpath) + + with closing(sqlite.connect(dbpath)) as connection: + cursor = connection.cursor() + + query = 'SELECT _id FROM books WHERE file_path = ?' + t = (metadata.lpath,) + cursor.execute(query, t) + + for i, row in enumerate(cursor): + metadata.bookId = row[0] + + cursor.close() + + if metadata.bookId is not None: + debug_print('PRS-T1: refreshing cover for book being sent') + self.upload_book_cover(connection, metadata, source_id) + + debug_print('PRS-T1: done uploading cover') + def upload_book_cover(self, connection, book, source_id): debug_print('PRST1: Uploading/Refreshing Cover for ' + book.title) if not book.thumbnail and book.thumbnail[-1]: