diff --git a/src/calibre/devices/kobo/driver.py b/src/calibre/devices/kobo/driver.py index 2c8a77c454..25bfc05bc8 100644 --- a/src/calibre/devices/kobo/driver.py +++ b/src/calibre/devices/kobo/driver.py @@ -52,6 +52,13 @@ def qhash(inputstr): return h +def any_in(haystack, *needles): + for n in needles: + if n in haystack: + return True + return False + + class DummyCSSPreProcessor(object): def __call__(self, data, add_namespace=False): @@ -69,7 +76,7 @@ class KOBO(USBMS): dbversion = 0 fwversion = (0,0,0) - supported_dbversion = 129 + supported_dbversion = 146 has_kepubs = False supported_platforms = ['windows', 'osx', 'linux'] @@ -98,33 +105,33 @@ class KOBO(USBMS): VIRTUAL_BOOK_EXTENSIONS = frozenset(['kobo', '']) EXTRA_CUSTOMIZATION_MESSAGE = [ - _('The Kobo supports several collections including ')+ 'Read, Closed, Im_Reading. ' + - _('Create tags for automatic management'), - _('Upload covers for books (newer readers)') + - ':::'+_('Normally, the Kobo readers get the cover image from the' - ' e-book file itself. With this option, calibre will send a ' - 'separate cover image to the reader, useful if you ' - 'have modified the cover.'), + _('The Kobo supports several collections including ')+ 'Read, Closed, Im_Reading. ' + _( + 'Create tags for automatic management'), + _('Upload covers for books (newer readers)') + ':::'+_( + 'Normally, the Kobo readers get the cover image from the' + ' e-book file itself. With this option, calibre will send a ' + 'separate cover image to the reader, useful if you ' + 'have modified the cover.'), _('Upload black and white covers'), - _('Show expired books') + - ':::'+_('A bug in an earlier version left non kepubs book records' - ' in the database. With this option calibre will show the ' - 'expired records and allow you to delete them with ' - 'the new delete logic.'), - _('Show Previews') + - ':::'+_('Kobo previews are included on the Touch and some other versions' - ' 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 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.'), - _('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'), + _('Show expired books') + ':::'+_( + 'A bug in an earlier version left non kepubs book records' + ' in the database. With this option calibre will show the ' + 'expired records and allow you to delete them with ' + 'the new delete logic.'), + _('Show Previews') + ':::'+_( + 'Kobo previews are included on the Touch and some other versions' + ' 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 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.'), + _('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'), ] EXTRA_CUSTOMIZATION_DEFAULT = [ @@ -353,8 +360,7 @@ class KOBO(USBMS): 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 (any_in(err, '___ExpirationStatus', 'FavouritesIndex', 'Accessibility', 'IsDownloaded')): raise query= ('select Title, Attribution, DateCreated, ContentID, MimeType, ContentType, ' 'ImageID, ReadStatus, "-1" as ___ExpirationStatus, "-1" as ' @@ -1316,7 +1322,7 @@ class KOBOTOUCH(KOBO): ' Based on the existing Kobo driver by %s.') % KOBO.author # icon = I('devices/kobotouch.jpg') - supported_dbversion = 143 + supported_dbversion = 146 min_supported_dbversion = 53 min_dbversion_series = 65 min_dbversion_externalid = 65 @@ -1328,7 +1334,7 @@ class KOBOTOUCH(KOBO): # Starting with firmware version 3.19.x, the last number appears to be is a # build number. A number will be recorded here but it can be safely ignored # when testing the firmware version. - max_supported_fwversion = (4, 7, 10413) + max_supported_fwversion = (4, 8, 10956) # The following document firwmare versions where new function or devices were added. # Not all are used, but this feels a good place to record it. min_fwversion_shelves = (2, 0, 0) @@ -1337,8 +1343,9 @@ class KOBOTOUCH(KOBO): min_aurah2o_fwversion = (3, 7, 0) min_reviews_fwversion = (3, 12, 0) min_glohd_fwversion = (3, 14, 0) - min_auraone_fwversion = (3, 20, 7280) - min_fwversion_overdrive = (4, 0, 7523) + min_auraone_fwversion = (3, 20, 7280) + # min_clarahd_fwversion = (4, 8, 10956) # It is coming, this is probably the firmware, but I don't have any ids for it. + min_fwversion_overdrive = (4, 0, 7523) has_kepubs = True @@ -1839,13 +1846,7 @@ class KOBOTOUCH(KOBO): 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 or - 'Series' in err or - 'ExternalId' in err - ): + if not (any_in(err, '___ExpirationStatus', 'FavouritesIndex', 'Accessibility', 'IsDownloaded', 'Series', 'ExternalId')): raise query= ('SELECT Title, Attribution, DateCreated, ContentID, MimeType, ContentType, ' 'ImageID, ReadStatus, -1 AS ___ExpirationStatus, "-1" AS ' @@ -2039,7 +2040,8 @@ class KOBOTOUCH(KOBO): # debug_print('KoboTouch:upload_books: Delete record left if deleted on Touch') cursor.execute(cleanup_query, cleanup_values) - self.set_filesize_in_device_database(connection, contentID, fname) + if self.override_kobo_replace_existing: + self.set_filesize_in_device_database(connection, contentID, fname) if not self.upload_covers: imageID = self.imageid_from_contentid(contentID) @@ -2963,6 +2965,7 @@ class KOBOTOUCH(KOBO): c.add_opt('update_device_metadata', default=True) c.add_opt('modify_css', default=False) + c.add_opt('override_kobo_replace_existing', default=True) # Overriding the replace behaviour is how the driver has always worked. c.add_opt('support_newer_firmware', default=False) c.add_opt('debugging_title', default='') @@ -3124,6 +3127,10 @@ class KOBOTOUCH(KOBO): def modifying_css(self): return self.get_pref('modify_css') + @property + def override_kobo_replace_existing(self): + return self.get_pref('override_kobo_replace_existing') + @property def update_device_metadata(self): return self.get_pref('update_device_metadata') diff --git a/src/calibre/devices/kobo/kobotouch_config.py b/src/calibre/devices/kobo/kobotouch_config.py index a4cecd011a..329907377b 100644 --- a/src/calibre/devices/kobo/kobotouch_config.py +++ b/src/calibre/devices/kobo/kobotouch_config.py @@ -114,6 +114,7 @@ class KOBOTOUCHConfig(TabbedDeviceConfig): p['update_series'] = self.update_series p['modify_css'] = self.modify_css + p['override_kobo_replace_existing'] = self.override_kobo_replace_existing p['support_newer_firmware'] = self.support_newer_firmware p['debugging_title'] = self.debugging_title @@ -185,14 +186,29 @@ class BookUploadsGroupBox(DeviceOptionsGroupBox): 'these are removed for all styles in the original stylesheet.').format(device.KOBO_EXTRA_CSSFILE), device.get_pref('modify_css') ) + self.override_kobo_replace_existing_checkbox = create_checkbox( + _("Do not treat replacements as new books"), + _('When a new book is side-loaded, the Kobo firmware imports details of the book into the internal database. ' + 'Even if the book is a replacement for an existing book, the Kobo will remove the book from the database and then treat it as a new book. ' + 'This means that the reading status, bookmarks and collections for the book will be lost. ' + 'This option overrides firmware behavior and attempts to prevent a book that has been resent from being treated as a new book. ' + 'If you prefer to have replacements treated as new books, turn this option off.' + ), + device.get_pref('override_kobo_replace_existing') + ) self.options_layout.addWidget(self.modify_css_checkbox, 0, 0, 1, 2) - self.options_layout.setRowStretch(1, 1) + self.options_layout.addWidget(self.override_kobo_replace_existing_checkbox, 1, 0, 1, 2) + self.options_layout.setRowStretch(2, 1) @property def modify_css(self): return self.modify_css_checkbox.isChecked() + @property + def override_kobo_replace_existing(self): + return self.override_kobo_replace_existing_checkbox.isChecked() + class CollectionsGroupBox(DeviceOptionsGroupBox): @@ -211,7 +227,7 @@ class CollectionsGroupBox(DeviceOptionsGroupBox): self.collections_columns_label = QLabel(_('Collections columns:')) self.collections_columns_edit = QLineEdit(self) self.collections_columns_edit.setToolTip(_('The Kobo from firmware V2.0.0 supports bookshelves.' - ' These are created on the Kobo. ' + + ' These are created on the Kobo. ' 'Specify a tags type column for automatic management.')) self.collections_columns_edit.setText(device.get_pref('collections_columns')) @@ -228,8 +244,8 @@ class CollectionsGroupBox(DeviceOptionsGroupBox): self.ignore_collections_names_label = QLabel(_('Ignore collections:')) self.ignore_collections_names_edit = QLineEdit(self) - self.ignore_collections_names_edit.setToolTip(_('List the names of collections to be ignored by ' + - 'the collection management. The collections listed ' + + self.ignore_collections_names_edit.setToolTip(_('List the names of collections to be ignored by ' + 'the collection management. The collections listed ' 'will not be changed. Names are separated by commas.')) self.ignore_collections_names_edit.setText(device.get_pref('ignore_collections_names'))