From 26d3b775e6561734353725971a5f0a81bed57694 Mon Sep 17 00:00:00 2001 From: davidfor Date: Thu, 29 Nov 2012 14:04:48 +1100 Subject: [PATCH] Add support for series information in 2.2.0 firmware. Series and SeriesNumber will be set on the device the next time the device is connected. It is only done if the series name or index are different. An option has been added to enable. --- src/calibre/devices/kobo/books.py | 2 + src/calibre/devices/kobo/driver.py | 191 +++++++++++++++++++++-------- 2 files changed, 144 insertions(+), 49 deletions(-) diff --git a/src/calibre/devices/kobo/books.py b/src/calibre/devices/kobo/books.py index 4fe34da6f6..bcf6353e83 100644 --- a/src/calibre/devices/kobo/books.py +++ b/src/calibre/devices/kobo/books.py @@ -60,6 +60,8 @@ class Book(Book_): self.contentID = None self.current_shelves = [] self.kobo_collections = [] + self.kobo_series = None + self.kobo_series_number = None if thumbnail_name is not None: self.thumbnail = ImageWrapper(thumbnail_name) diff --git a/src/calibre/devices/kobo/driver.py b/src/calibre/devices/kobo/driver.py index e2f431ab7f..a8cd1d3a5a 100644 --- a/src/calibre/devices/kobo/driver.py +++ b/src/calibre/devices/kobo/driver.py @@ -33,7 +33,7 @@ class KOBO(USBMS): gui_name = 'Kobo Reader' description = _('Communicate with the Kobo Reader') author = 'Timothy Legge and David Forrester' - version = (2, 0, 3) + version = (2, 0, 4) dbversion = 0 fwversion = 0 @@ -1201,8 +1201,9 @@ class KOBOTOUCH(KOBO): author = 'David Forrester' description = 'Communicate with the Kobo Touch, Glo and Mini firmware. Based on the existing Kobo driver by %s.' % (KOBO.author) - supported_dbversion = 70 + supported_dbversion = 75 min_supported_dbversion = 53 + min_dbversion_series = 65 booklist_class = KTCollectionsBookList book_class = Book @@ -1236,16 +1237,21 @@ class KOBOTOUCH(KOBO): ' by default they are no longer displayed as there is no good reason to ' 'see them. Enable if you wish to see/delete them.'), _('Show Recommendations') + - ':::'+_('Kobo now shows recommendations on the device. In some case these have ' + ':::'+_('Kobo shows recommendations on the device. In some cases these have ' 'files but in other cases they are just pointers to the web site to buy. ' 'Enable if you wish to see/delete them.'), + _('Set Series information') + + ':::'+_('The book lists on the Kobo devices can display series information. ' + 'This is not read by the device from the sideloaded books. ' + 'Series information can only be adde to the device after the book has been processed by the device. ' + 'Enable if you wish to set series information.'), _('Attempt to support newer firmware') + ':::'+_('Kobo routinely updates the firmware and the ' 'database version. With this option Calibre will attempt ' 'to perform full read-write functionality - Here be Dragons!! ' 'Enable only if you are comfortable with restoring your kobo ' 'to factory defaults and testing software. ' - 'This driver supports firmware V2.0.x and DBVersion up to ' + unicode(supported_dbversion)), + 'This driver supports firmware V2.x.x and DBVersion up to ' + unicode(supported_dbversion)), _('Title to test when debugging') + ':::'+_('Part of title of a book that can be used when doing some tests for debugging. ' 'The test is to see if the string is contained in the title of a book. ' @@ -1263,6 +1269,7 @@ class KOBOTOUCH(KOBO): False, False, False, + False, u'' ] @@ -1275,8 +1282,9 @@ class KOBOTOUCH(KOBO): OPT_SHOW_EXPIRED_BOOK_RECORDS = 6 OPT_SHOW_PREVIEWS = 7 OPT_SHOW_RECOMMENDATIONS = 8 - OPT_SUPPORT_NEWER_FIRMWARE = 9 - OPT_DEBUGGING_TITLE = 10 + OPT_UPDATE_SERIES_DETAILS = 9 + OPT_SUPPORT_NEWER_FIRMWARE = 10 + OPT_DEBUGGING_TITLE = 11 opts = None @@ -1364,7 +1372,7 @@ class KOBOTOUCH(KOBO): for idx,b in enumerate(bl): bl_cache[b.lpath] = idx - def update_booklist(prefix, path, title, authors, mime, date, ContentID, ContentType, ImageID, readstatus, MimeType, expired, favouritesindex, accessibility, isdownloaded, bookshelves): + def update_booklist(prefix, path, title, authors, mime, date, ContentID, ContentType, ImageID, readstatus, MimeType, expired, favouritesindex, accessibility, isdownloaded, series, seriesnumber, bookshelves): show_debug = self.is_debugging_title(title) # show_debug = authors == 'L. Frank Baum' if show_debug: @@ -1442,9 +1450,7 @@ class KOBOTOUCH(KOBO): debug_print('KoboTouch:update_booklist - the authors=', bl[idx].authors) debug_print('KoboTouch:update_booklist - application_id=', bl[idx].application_id) bl_cache[lpath] = None - # removed to allow recognizing of ePub with an UUID inside - # if bl[idx].title_sort is not None: - # bl[idx].title = bl[idx].title_sort + if ImageID is not None: imagename = self.imagefilename_from_imageID(ImageID) if imagename is not None: @@ -1460,7 +1466,9 @@ class KOBOTOUCH(KOBO): if show_debug: debug_print("KoboTouch:update_booklist - ContentID='%s'"%ContentID) - bl[idx].contentID = ContentID + bl[idx].contentID = ContentID + bl[idx].kobo_series = series + bl[idx].kobo_series_number = seriesnumber if lpath in playlist_map: bl[idx].device_collections = playlist_map.get(lpath,[]) @@ -1505,9 +1513,11 @@ class KOBOTOUCH(KOBO): # print 'Update booklist' book.device_collections = playlist_map.get(lpath,[])# if lpath in playlist_map else [] - book.current_shelves = bookshelves - book.kobo_collections = kobo_collections - book.contentID = ContentID + book.current_shelves = bookshelves + book.kobo_collections = kobo_collections + book.contentID = ContentID + book.kobo_series = series + book.kobo_series_number = seriesnumber # debug_print('KoboTouch:update_booklist - title=', title, 'book.device_collections', book.device_collections) if bl.add_book(book, replace_metadata=False): @@ -1558,10 +1568,23 @@ class KOBOTOUCH(KOBO): debug_print("KoboTouch:books - shelf list:", self.bookshelvelist) opts = self.settings() - if self.dbversion >= 33: + if self.supports_series(): query= ('select Title, Attribution, DateCreated, ContentID, MimeType, ContentType, ' \ - 'ImageID, ReadStatus, ___ExpirationStatus, FavouritesIndex, Accessibility, IsDownloaded from content where ' \ - 'BookID is Null %(previews)s %(recomendations)s and not ((___ExpirationStatus=3 or ___ExpirationStatus is Null) %(expiry)s') % \ + 'ImageID, ReadStatus, ___ExpirationStatus, FavouritesIndex, Accessibility, ' \ + 'IsDownloaded, Series, SeriesNumber ' \ + ' from content ' \ + ' where BookID is Null %(previews)s %(recomendations)s and not ((___ExpirationStatus=3 or ___ExpirationStatus is Null) %(expiry)s') % \ + dict(\ + expiry=' and ContentType = 6)' if opts.extra_customization[self.OPT_SHOW_EXPIRED_BOOK_RECORDS] else ')', \ + previews=' and Accessibility <> 6' if opts.extra_customization[self.OPT_SHOW_PREVIEWS] == False else '', \ + recomendations=' and IsDownloaded in (\'true\', 1)' if opts.extra_customization[self.OPT_SHOW_RECOMMENDATIONS] == False else ''\ + ) + elif self.dbversion >= 33: + query= ('select Title, Attribution, DateCreated, ContentID, MimeType, ContentType, ' \ + 'ImageID, ReadStatus, ___ExpirationStatus, FavouritesIndex, Accessibility, ' \ + 'IsDownloaded, null as Series, null as SeriesNumber' \ + ' from content ' \ + ' where BookID is Null %(previews)s %(recomendations)s and not ((___ExpirationStatus=3 or ___ExpirationStatus is Null) %(expiry)s') % \ dict(\ expiry=' and ContentType = 6)' if opts.extra_customization[self.OPT_SHOW_EXPIRED_BOOK_RECORDS] else ')', \ previews=' and Accessibility <> 6' if opts.extra_customization[self.OPT_SHOW_PREVIEWS] == False else '', \ @@ -1569,35 +1592,33 @@ class KOBOTOUCH(KOBO): ) elif self.dbversion >= 16 and self.dbversion < 33: query= ('select Title, Attribution, DateCreated, ContentID, MimeType, ContentType, ' \ - 'ImageID, ReadStatus, ___ExpirationStatus, FavouritesIndex, Accessibility, "1" as IsDownloaded from content where ' \ - 'BookID is Null and not ((___ExpirationStatus=3 or ___ExpirationStatus is Null) %(expiry)s') % dict(expiry=' and ContentType = 6)' \ - if opts.extra_customization[self.OPT_SHOW_EXPIRED_BOOK_RECORDS] else ')') - elif self.dbversion < 16 and self.dbversion >= 14: - query= ('select Title, Attribution, DateCreated, ContentID, MimeType, ContentType, ' \ - 'ImageID, ReadStatus, ___ExpirationStatus, FavouritesIndex, "-1" as Accessibility, "1" as IsDownloaded from content where ' \ - 'BookID is Null and not ((___ExpirationStatus=3 or ___ExpirationStatus is Null) %(expiry)s') % dict(expiry=' and ContentType = 6)' \ - if opts.extra_customization[self.OPT_SHOW_EXPIRED_BOOK_RECORDS] else ')') - elif self.dbversion < 14 and self.dbversion >= 8: - query= ('select Title, Attribution, DateCreated, ContentID, MimeType, ContentType, ' \ - 'ImageID, ReadStatus, ___ExpirationStatus, "-1" as FavouritesIndex, "-1" as Accessibility, "1" as IsDownloaded from content where ' \ + 'ImageID, ReadStatus, ___ExpirationStatus, FavouritesIndex, Accessibility, ' \ + '"1" as IsDownloaded, null as Series, null as SeriesNumber' \ + ' from content where ' \ 'BookID is Null and not ((___ExpirationStatus=3 or ___ExpirationStatus is Null) %(expiry)s') % dict(expiry=' and ContentType = 6)' \ if opts.extra_customization[self.OPT_SHOW_EXPIRED_BOOK_RECORDS] else ')') else: query= 'select Title, Attribution, DateCreated, ContentID, MimeType, ContentType, ' \ - 'ImageID, ReadStatus, "-1" as ___ExpirationStatus, "-1" as FavouritesIndex, "-1" as Accessibility, "1" as IsDownloaded from content where BookID is Null' + 'ImageID, ReadStatus, "-1" as ___ExpirationStatus, "-1" as FavouritesIndex, "-1" as Accessibility, ' \ + '"1" as IsDownloaded, null as Series, null as SeriesNumber' \ + ' from content where BookID is Null' debug_print("KoboTouch:books - query=", query) try: cursor.execute (query) except Exception as e: err = str(e) - if not ('___ExpirationStatus' in err or 'FavouritesIndex' in err or - 'Accessibility' in err or 'IsDownloaded' in err): + if not ('___ExpirationStatus' in err + or 'FavouritesIndex' in err + or 'Accessibility' in err + or 'IsDownloaded' in err + or 'Series' in err + ): raise query= ('select Title, Attribution, DateCreated, ContentID, MimeType, ContentType, ' 'ImageID, ReadStatus, "-1" as ___ExpirationStatus, "-1" as ' - 'FavouritesIndex, "-1" as Accessibility, "1" as IsDownloaded from content where ' - 'BookID is Null') + 'FavouritesIndex, "-1" as Accessibility, "1" as IsDownloaded, null as Series, null as SeriesNumber' \ + ' from content where BookID is Null') cursor.execute(query) changed = False @@ -1620,10 +1641,10 @@ class KOBOTOUCH(KOBO): bookshelves = get_bookshelvesforbook(connection, row[3]) if oncard != 'carda' and oncard != 'cardb' and not row[3].startswith("file:///mnt/sd/"): - changed = update_booklist(self._main_prefix, path, row[0], row[1], mime, row[2], row[3], row[5], row[6], row[7], row[4], row[8], row[9], row[10], row[11], bookshelves) + changed = update_booklist(self._main_prefix, path, row[0], row[1], mime, row[2], row[3], row[5], row[6], row[7], row[4], row[8], row[9], row[10], row[11], row[12], row[13], bookshelves) # print "shortbook: " + path elif oncard == 'carda' and row[3].startswith("file:///mnt/sd/"): - changed = update_booklist(self._card_a_prefix, path, row[0], row[1], mime, row[2], row[3], row[5], row[6], row[7], row[4], row[8], row[9], row[10], row[11], bookshelves) + changed = update_booklist(self._card_a_prefix, path, row[0], row[1], mime, row[2], row[3], row[5], row[6], row[7], row[4], row[8], row[9], row[10], row[11], row[12], row[13], bookshelves) if changed: need_sync = True @@ -1669,9 +1690,6 @@ class KOBOTOUCH(KOBO): for ending, cover_options in self.COVER_FILE_ENDINGS.items(): fpath = self._main_prefix + '.kobo/images/' + ImageID + ending fpath = self.normalize_path(fpath.replace('/', os.sep)) - if show_debug: - debug_print("KoboTouch:imagefilename_from_imageID - ending=%s, path length=%d" % (ending, len(fpath))) - debug_print("KoboTouch:imagefilename_from_imageID - fpath=%s" % (fpath)) if os.path.exists(fpath): if show_debug: debug_print("KoboTouch:imagefilename_from_imageID - have cover image fpath=%s" % (fpath)) @@ -1857,13 +1875,16 @@ class KOBOTOUCH(KOBO): opts = self.settings() if opts.extra_customization: - create_bookshelves = opts.extra_customization[self.OPT_CREATE_BOOKSHELVES] and self.supports_bookshelves() - delete_empty_shelves = opts.extra_customization[self.OPT_DELETE_BOOKSHELVES] and self.supports_bookshelves() - debugging_title = opts.extra_customization[self.OPT_DEBUGGING_TITLE] + create_bookshelves = opts.extra_customization[self.OPT_CREATE_BOOKSHELVES] and self.supports_bookshelves() + delete_empty_shelves = opts.extra_customization[self.OPT_DELETE_BOOKSHELVES] and self.supports_bookshelves() + update_series_details = opts.extra_customization[self.OPT_UPDATE_SERIES_DETAILS] and self.supports_series() + debugging_title = opts.extra_customization[self.OPT_DEBUGGING_TITLE] debug_print("KoboTouch:update_device_database_collections - set_debugging_title to", debugging_title ) booklists.set_debugging_title(debugging_title) else: - delete_empty_shelves = False + delete_empty_shelves = False + create_bookshelves = False + update_series_details = False collections = booklists.get_collections(collections_attributes) # debug_print('KoboTouch:update_device_database_collections - Collections:', collections) @@ -1972,21 +1993,32 @@ class KOBOTOUCH(KOBO): debug_print("No Collections - resetting FavouritesIndex") self.reset_favouritesindex(connection, oncard) - if self.supports_bookshelves(): - debug_print("KoboTouch:update_device_database_collections - managing bookshelves.") - if bookshelf_attribute: - debug_print("KoboTouch:update_device_database_collections - bookshelf_attribute=", bookshelf_attribute) - for book in booklists: - if book.application_id is not None: -# debug_print("KoboTouch:update_device_database_collections - about to remove a book from shelves book.title=%s" % book.title) + if self.supports_bookshelves() or self.supports_series(): + debug_print("KoboTouch:update_device_database_collections - managing bookshelves and series.") + + self.series_set = 0 + books_in_library = 0 + for book in booklists: + if book.application_id is not None: + books_in_library += 1 + show_debug = self.is_debugging_title(book.title) + if show_debug: + debug_print("KoboTouch:update_device_database_collections - book.title=%s" % book.title) + if update_series_details: + self.set_series(connection, book) + if bookshelf_attribute: + if show_debug: + debug_print("KoboTouch:update_device_database_collections - about to remove a book from shelves book.title=%s" % book.title) self.remove_book_from_device_bookshelves(connection, book) book.device_collections.extend(book.kobo_collections) if not prefs['manage_device_metadata'] == 'manual' and delete_empty_shelves: debug_print("KoboTouch:update_device_database_collections - about to clear empty bookshelves") self.delete_empty_bookshelves(connection) + debug_print("KoboTouch:update_device_database_collections - Number of series set=%d Number of books=%d" % (self.series_set, books_in_library)) self.dump_bookshelves(connection) + debug_print('KoboTouch:update_device_database_collections - Finished ') def rebuild_collections(self, booklist, oncard): @@ -2310,9 +2342,70 @@ class KOBOTOUCH(KOBO): debug_print("KoboTouch:remove_from_bookshelf - end") + def set_series(self, connection, book): + show_debug = self.is_debugging_title(book.title) + if show_debug: + debug_print('KoboTouch:set_series book.kobo_series="%s"'%book.kobo_series) + debug_print('KoboTouch:set_series book.series="%s"'%book.series) + debug_print('KoboTouch:set_series book.series_index=', book.series_index) + + if book.series == book.kobo_series and book.series_index == book.kobo_series_number: + if show_debug: + debug_print('KoboTouch:set_series - series info the same - not changing') + return + + update_query = 'UPDATE content SET Series=?, SeriesNumber==? where BookID is Null and ContentID = ?' + if book.series is None: + update_values = (None, None, book.contentID, ) + else: + update_values = (book.series, "%g"%book.series_index, book.contentID, ) + + cursor = connection.cursor() + try: + if show_debug: + debug_print('KoboTouch:set_series - about to set - parameters:', update_values) + cursor.execute(update_query, update_values) + self.series_set += 1 + except: + debug_print(' Database Exception: Unable to set series info') + raise + else: + connection.commit() + cursor.close() + + if show_debug: + debug_print("KoboTouch:set_series - end") + + + @classmethod + def settings(cls): + opts = cls._config().parse() + if isinstance(cls.EXTRA_CUSTOMIZATION_DEFAULT, list): + if opts.extra_customization is None: + opts.extra_customization = [] + if not isinstance(opts.extra_customization, list): + opts.extra_customization = [opts.extra_customization] + if len(cls.EXTRA_CUSTOMIZATION_DEFAULT) > len(opts.extra_customization): + extra_options_offset = 0 + extra_customization = [] + for i,d in enumerate(cls.EXTRA_CUSTOMIZATION_DEFAULT): + if i >= len(opts.extra_customization) + extra_options_offset: + extra_customization.append(d) + elif d.__class__ != opts.extra_customization[i - extra_options_offset].__class__: + extra_options_offset += 1 + extra_customization.append(d) + else: + extra_customization.append(opts.extra_customization[i - extra_options_offset]) + opts.extra_customization = extra_customization + return opts + + def supports_bookshelves(self): return self.dbversion >= self.min_supported_dbversion + def supports_series(self): + return self.dbversion >= self.min_dbversion_series + # def is_debugging_title(self, title): ## debug_print("KoboTouch:is_debugging - title=", title) # is_debugging = False