Updates for new firmware and book stats

Updates to support firmware 4.32. This adds some books stats such as word and page count and expected reading time. Plus did some code cleanup.
This commit is contained in:
David 2022-04-15 11:52:22 +10:00
parent 02368d2888
commit 2982a46696
3 changed files with 303 additions and 56 deletions

View File

@ -70,6 +70,7 @@ class Book(Book_):
self.kobo_series_number = None # Kobo stores the series number as string. And it can have a leading "#".
self.kobo_series_id = None
self.kobo_subtitle = None
self.kobo_bookstats = {}
if thumbnail_name is not None:
self.thumbnail = ImageWrapper(thumbnail_name)
@ -151,14 +152,14 @@ class KTCollectionsBookList(CollectionsBookList):
if show_debug: # or len(book.device_collections) > 0:
debug_print('KTCollectionsBookList:get_collections - tsval=', tsval, "book.title=", book.title, "book.title_sort=", book.title_sort)
debug_print('KTCollectionsBookList:get_collections - book.device_collections=', book.device_collections)
# debug_print(book)
# debug_print(book)
# Make sure we can identify this book via the lpath
lpath = getattr(book, 'lpath', None)
if lpath is None:
continue
# If the book is not in the current library, we don't want to use the metadtaa for the collections
if book.application_id is None:
# debug_print("KTCollectionsBookList:get_collections - Book not in current library")
# debug_print("KTCollectionsBookList:get_collections - Book not in current library")
continue
# Decide how we will build the collections. The default: leave the
# book in all existing collections. Do not add any new ones.
@ -205,7 +206,7 @@ class KTCollectionsBookList(CollectionsBookList):
debug_print("KTCollectionsBookList:get_collections - adding book.device_collections", book.device_collections)
# If the book is not in the current library, we don't want to use the metadtaa for the collections
elif book.application_id is None or not book.can_put_on_shelves:
# debug_print("KTCollectionsBookList:get_collections - Book not in current library")
# debug_print("KTCollectionsBookList:get_collections - Book not in current library")
continue
else:
doing_dc = False
@ -221,7 +222,7 @@ class KTCollectionsBookList(CollectionsBookList):
val = val.decode(preferred_encoding, 'replace')
if isinstance(val, (list, tuple)):
val = list(val)
# debug_print("KTCollectionsBookList:get_collections - val is list=", val)
# debug_print("KTCollectionsBookList:get_collections - val is list=", val)
elif fm is not None and fm['datatype'] == 'series':
val = [orig_val]
elif fm is not None and fm['datatype'] == 'rating':
@ -244,7 +245,7 @@ class KTCollectionsBookList(CollectionsBookList):
debug_print("KTCollectionsBookList:get_collections - val=", val)
for category in val:
# debug_print("KTCollectionsBookList:get_collections - category=", category)
# debug_print("KTCollectionsBookList:get_collections - category=", category)
is_series = False
if doing_dc:
# Attempt to determine if this value is a series by

View File

@ -36,6 +36,7 @@ from polyglot.builtins import iteritems, itervalues, string_or_bytes
EPUB_EXT = '.epub'
KEPUB_EXT = '.kepub'
KOBO_ROOT_DIR_NAME = ".kobo"
DEFAULT_COVER_LETTERBOX_COLOR = '#000000'
@ -84,10 +85,11 @@ class KOBO(USBMS):
dbversion = 0
fwversion = (0,0,0)
_device_version_info = None
# The firmware for these devices is not being updated. But the Kobo desktop application
# will update the database if the device is connected. The database structure is completely
# backwardly compatible.
supported_dbversion = 162
supported_dbversion = 169
has_kepubs = False
supported_platforms = ['windows', 'osx', 'linux']
@ -170,9 +172,14 @@ class KOBO(USBMS):
def initialize(self):
USBMS.initialize(self)
self.dbversion = 7
self._device_version_info = None
def eject(self):
self._device_version_info = None
super().eject()
def device_database_path(self):
return self.normalize_path(self._main_prefix + '.kobo/KoboReader.sqlite')
return os.path.join(self._main_prefix, KOBO_ROOT_DIR_NAME, 'KoboReader.sqlite')
def device_database_connection(self, use_row_factory=False):
import apsw
@ -197,14 +204,29 @@ class KOBO(USBMS):
return dbversion
def device_version_info(self):
debug_print("device_version_info - start")
if not self._device_version_info:
version_file = os.path.join(self._main_prefix, KOBO_ROOT_DIR_NAME, "version")
debug_print(f"device_version_info - version_file={version_file}")
if os.path.isfile(version_file):
debug_print("device_version_info - have opened version_file")
vf = open(version_file, "r")
self._device_version_info = vf.read().strip().split(",")
vf.close()
debug_print("device_version_info - self._device_version_info=", self._device_version_info)
return self._device_version_info
def device_serial_no(self):
return self.device_version_info()[0]
def get_firmware_version(self):
# Determine the firmware version
try:
with lopen(self.normalize_path(self._main_prefix + '.kobo/version'), 'rb') as f:
fwversion = f.readline().split(b',')[2]
fwversion = tuple(int(x) for x in fwversion.split(b'.'))
except Exception:
debug_print("Kobo::get_firmware_version - didn't get firmware version from file'")
fwversion = self.device_version_info()[2]
fwversion = tuple(int(x) for x in fwversion.split('.'))
except Exception as e:
debug_print(f"Kobo::get_firmware_version - didn't get firmware version from file' - Exception: {e}")
fwversion = (0,0,0)
return fwversion
@ -292,10 +314,10 @@ class KOBO(USBMS):
if idx is not None:
bl_cache[lpath] = None
if ImageID is not None:
imagename = self.normalize_path(self._main_prefix + '.kobo/images/' + ImageID + ' - NickelBookCover.parsed')
imagename = self.normalize_path(self._main_prefix + KOBO_ROOT_DIR_NAME + '/images/' + ImageID + ' - NickelBookCover.parsed')
if not os.path.exists(imagename):
# Try the Touch version if the image does not exist
imagename = self.normalize_path(self._main_prefix + '.kobo/images/' + ImageID + ' - N3_LIBRARY_FULL.parsed')
imagename = self.normalize_path(self._main_prefix + KOBO_ROOT_DIR_NAME + '/images/' + ImageID + ' - N3_LIBRARY_FULL.parsed')
# print "Image name Normalized: " + imagename
if not os.path.exists(imagename):
@ -505,7 +527,7 @@ class KOBO(USBMS):
def delete_images(self, ImageID, book_path):
if ImageID is not None:
path_prefix = '.kobo/images/'
path_prefix = KOBO_ROOT_DIR_NAME + '/images/'
path = self._main_prefix + path_prefix + ImageID
file_endings = (' - iPhoneThumbnail.parsed', ' - bbMediumGridList.parsed', ' - NickelBookCover.parsed', ' - N3_LIBRARY_FULL.parsed',
@ -622,7 +644,7 @@ class KOBO(USBMS):
ContentID = ContentID.replace(self._main_prefix, '')
else:
ContentID = path
ContentID = ContentID.replace(self._main_prefix + self.normalize_path('.kobo/kepub/'), '')
ContentID = ContentID.replace(self._main_prefix + self.normalize_path(KOBO_ROOT_DIR_NAME + '/kepub/'), '')
if self._card_a_prefix is not None:
ContentID = ContentID.replace(self._card_a_prefix, '')
@ -684,7 +706,7 @@ class KOBO(USBMS):
if path.startswith("file:///mnt/onboard/"):
path = self._main_prefix + path.replace("file:///mnt/onboard/", '')
else:
path = self._main_prefix + '.kobo/kepub/' + path
path = self._main_prefix + KOBO_ROOT_DIR_NAME + '/kepub/' + path
# print "Internal: " + path
else:
# if path.startswith("file:///mnt/onboard/"):
@ -1037,7 +1059,7 @@ class KOBO(USBMS):
cursor.close()
if ImageID is not None:
path_prefix = '.kobo/images/'
path_prefix = KOBO_ROOT_DIR_NAME + '/images/'
path = self._main_prefix + path_prefix + ImageID
file_endings = {' - iPhoneThumbnail.parsed':(103,150),
@ -1355,7 +1377,7 @@ class KOBOTOUCH(KOBO):
' Based on the existing Kobo driver by %s.') % KOBO.author
# icon = I('devices/kobotouch.jpg')
supported_dbversion = 166
supported_dbversion = 169
min_supported_dbversion = 53
min_dbversion_series = 65
min_dbversion_externalid = 65
@ -1364,11 +1386,12 @@ class KOBOTOUCH(KOBO):
min_dbversion_activity = 77
min_dbversion_keywords = 82
min_dbversion_seriesid = 136
min_dbversion_bookstats = 169
# 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, 31, 19086)
max_supported_fwversion = (4, 32, 19501)
# The following document firmware 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)
@ -1390,6 +1413,7 @@ class KOBOTOUCH(KOBO):
min_libra2_fwversion = (4, 29, 18730)
min_sage_fwversion = (4, 29, 18730)
min_fwversion_audiobooks = (4, 29, 18730)
min_fwversion_bookstats = (4, 32, 19501)
has_kepubs = True
@ -1636,7 +1660,7 @@ class KOBOTOUCH(KOBO):
series, seriesnumber, SeriesID, SeriesNumberFloat,
ISBN, Language, Subtitle,
readstatus, expired, favouritesindex, accessibility, isdownloaded,
userid, bookshelves
userid, bookshelves, book_stats=None
):
show_debug = self.is_debugging_title(title)
# show_debug = authors == 'L. Frank Baum'
@ -1781,6 +1805,7 @@ class KOBOTOUCH(KOBO):
bl[idx].kobo_series_id = SeriesID
bl[idx].kobo_series_number_float = SeriesNumberFloat
bl[idx].kobo_subtitle = Subtitle
bl[idx].kobo_bookstats = book_stats
bl[idx].can_put_on_shelves = allow_shelves
bl[idx].mime = MimeType
@ -1841,6 +1866,7 @@ class KOBOTOUCH(KOBO):
book.kobo_series_id = SeriesID
book.kobo_series_number_float = SeriesNumberFloat
book.kobo_subtitle = Subtitle
book.kobo_bookstats = book_stats
book.can_put_on_shelves = allow_shelves
# debug_print('KoboTouch:update_booklist - title=', title, 'book.device_collections', book.device_collections)
@ -1912,6 +1938,10 @@ class KOBOTOUCH(KOBO):
columns += ", SeriesID, SeriesNumberFloat"
else:
columns += ', null as SeriesID, null as SeriesNumberFloat'
if self.supports_bookstats:
columns += ", StorePages, StoreWordCount, StoreTimeToReadLowerEstimate, StoreTimeToReadUpperEstimate"
else:
columns += ', null as StorePages, null as StoreWordCount, null as StoreTimeToReadLowerEstimate, null as StoreTimeToReadUpperEstimate'
where_clause = ''
if self.supports_kobo_archive() or self.supports_overdrive():
@ -2010,7 +2040,13 @@ class KOBOTOUCH(KOBO):
row['ISBN'], row['Language'], row['Subtitle'],
row['ReadStatus'], row['___ExpirationStatus'],
int(row['FavouritesIndex']), row['Accessibility'], row['IsDownloaded'],
row['___UserID'], bookshelves
row['___UserID'], bookshelves,
book_stats={
'StorePages': row['StorePages'],
'StoreWordCount': row['StoreWordCount'],
'StoreTimeToReadLowerEstimate': row['StoreTimeToReadLowerEstimate'],
'StoreTimeToReadUpperEstimate': row['StoreTimeToReadUpperEstimate']
}
)
if changed:
@ -2082,7 +2118,7 @@ class KOBOTOUCH(KOBO):
else:
if (ContentType == "6" or ContentType == "10"):
if (MimeType == 'application/octet-stream'): # Audiobooks purchased from Kobo are in a different location.
path = self._main_prefix + '.kobo/audiobook/' + path
path = self._main_prefix + KOBO_ROOT_DIR_NAME + '/audiobook/' + path
elif path.startswith("file:///mnt/onboard/"):
path = self._main_prefix + path.replace("file:///mnt/onboard/", '')
elif path.startswith("file:///mnt/sd/"):
@ -2090,7 +2126,7 @@ class KOBOTOUCH(KOBO):
elif externalId:
path = self._card_a_prefix + 'koboExtStorage/kepub/' + path
else:
path = self._main_prefix + '.kobo/kepub/' + path
path = self._main_prefix + KOBO_ROOT_DIR_NAME + '/kepub/' + path
else: # Should never get here, but, just in case...
# if path.startswith("file:///mnt/onboard/"):
path = path.replace("file:///mnt/onboard/", self._main_prefix)
@ -2387,7 +2423,7 @@ class KOBOTOUCH(KOBO):
ContentID = ContentID.replace(self._main_prefix, '')
elif not extension:
ContentID = path
ContentID = ContentID.replace(self._main_prefix + self.normalize_path('.kobo/kepub/'), '')
ContentID = ContentID.replace(self._main_prefix + self.normalize_path(KOBO_ROOT_DIR_NAME + '/kepub/'), '')
else:
ContentID = path
ContentID = ContentID.replace(self._main_prefix, "file:///mnt/onboard/")
@ -2663,7 +2699,7 @@ class KOBOTOUCH(KOBO):
path_prefix = 'koboExtStorage/images-cache/' if self.supports_images_tree() else 'koboExtStorage/images/'
path = os.path.join(self._card_a_prefix, path_prefix)
else:
path_prefix = '.kobo-images/' if self.supports_images_tree() else '.kobo/images/'
path_prefix = '.kobo-images/' if self.supports_images_tree() else KOBO_ROOT_DIR_NAME + '/images/'
path = os.path.join(self._main_prefix, path_prefix)
if self.supports_images_tree() and imageId:
@ -3173,11 +3209,28 @@ class KOBOTOUCH(KOBO):
debug_print("KoboTouch:set_series - end")
def set_core_metadata(self, connection, book, series_only=False):
# debug_print('KoboTouch:set_core_metadata book="%s"' % book.title)
debug_print('KoboTouch:set_core_metadata book="%s"' % book.title)
show_debug = self.is_debugging_title(book.title)
if show_debug:
debug_print(f'KoboTouch:set_core_metadata book="{book}", \nseries_only="{series_only}"')
def generate_update_from_template(book, update_values, set_clause, column_name, new_value=None, template=None, current_value=None):
if template is None or template == '':
new_value = None
else:
new_value = new_value if len(new_value.strip()) else None
if new_value is not None and new_value.startswith("PLUGBOARD TEMPLATE ERROR"):
debug_print("KoboTouch:generate_update_from_template template error - template='%s'" % template)
debug_print("KoboTouch:generate_update_from_template - new_value=", new_value)
debug_print(f"KoboTouch:generate_update_from_template - {book.title} - column_name='{column_name}', current_value='{current_value}', new_value='{new_value}'")
if (new_value is not None and \
(current_value is None or new_value != current_value) ) or \
(new_value is None and current_value is not None):
update_values.append(new_value)
set_clause.append(column_name)
plugboard = None
if self.plugboard_func and not series_only:
if book.contentID.endswith('.kepub.epub') or not os.path.splitext(book.contentID)[1]:
@ -3198,7 +3251,7 @@ class KOBOTOUCH(KOBO):
update_query = 'UPDATE content SET '
update_values = []
set_clause = ''
set_clause = []
changes_found = False
kobo_metadata = book.kobo_metadata
@ -3229,9 +3282,9 @@ class KOBOTOUCH(KOBO):
if series_changed or series_number_changed:
update_values.append(new_series)
set_clause += ', Series = ? '
set_clause.append('Series')
update_values.append(new_series_number)
set_clause += ', SeriesNumber = ? '
set_clause.append('SeriesNumber')
if self.supports_series_list and book.is_sideloaded:
series_id = self.kobo_series_dict.get(new_series, new_series)
try:
@ -3245,50 +3298,63 @@ class KOBOTOUCH(KOBO):
or not kobo_series_id == series_id \
or not kobo_series_number_float == newmi.series_index:
update_values.append(series_id)
set_clause += ', SeriesID = ? '
set_clause.append('SeriesID')
update_values.append(newmi.series_index)
set_clause += ', SeriesNumberFloat = ? '
set_clause.append('SeriesNumberFloat')
if show_debug:
debug_print(f"KoboTouch:set_core_metadata Setting SeriesID - new_series='{new_series}', series_id='{series_id}'")
if not series_only:
pb = []
if self.subtitle_template is not None:
pb.append((self.subtitle_template, 'subtitle'))
if self.bookstats_pagecount_template is not None:
pb.append((self.bookstats_pagecount_template, 'bookstats_pagecount'))
if self.bookstats_wordcount_template is not None:
pb.append((self.bookstats_wordcount_template, 'bookstats_wordcount'))
if self.bookstats_timetoread_upper_template is not None:
pb.append((self.bookstats_timetoread_upper_template, 'bookstats_timetoread_upper'))
if self.bookstats_timetoread_lower_template is not None:
pb.append((self.bookstats_timetoread_lower_template, 'bookstats_timetoread_lower'))
if show_debug:
debug_print(f"KoboTouch:set_core_metadata templates being used - pb='{pb}'")
book.template_to_attribute(book, pb)
if not (newmi.title == kobo_metadata.title):
update_values.append(newmi.title)
set_clause += ', Title = ? '
set_clause.append('Title')
if not (authors_to_string(newmi.authors) == authors_to_string(kobo_metadata.authors)):
update_values.append(authors_to_string(newmi.authors))
set_clause += ', Attribution = ? '
set_clause.append('Attribution')
if not (newmi.publisher == kobo_metadata.publisher):
update_values.append(newmi.publisher)
set_clause += ', Publisher = ? '
set_clause.append('Publisher')
if not (newmi.pubdate == kobo_metadata.pubdate):
pubdate_string = strftime(self.TIMESTAMP_STRING, newmi.pubdate) if newmi.pubdate else None
update_values.append(pubdate_string)
set_clause += ', DateCreated = ? '
set_clause.append('DateCreated')
if not (newmi.comments == kobo_metadata.comments):
update_values.append(newmi.comments)
set_clause += ', Description = ? '
set_clause.append('Description')
if not (newmi.isbn == kobo_metadata.isbn):
update_values.append(newmi.isbn)
set_clause += ', ISBN = ? '
set_clause.append('ISBN')
library_language = normalize_languages(kobo_metadata.languages, newmi.languages)
library_language = library_language[0] if library_language is not None and len(library_language) > 0 else None
if not (library_language == kobo_metadata.language):
update_values.append(library_language)
set_clause += ', Language = ? '
set_clause.append('Language')
if self.update_subtitle:
if self.subtitle_template is None or self.subtitle_template == '':
new_subtitle = None
else:
pb = [(self.subtitle_template, 'subtitle')]
book.template_to_attribute(book, pb)
new_subtitle = book.subtitle if len(book.subtitle.strip()) else None
if new_subtitle is not None and new_subtitle.startswith("PLUGBOARD TEMPLATE ERROR"):
debug_print("KoboTouch:set_core_metadata subtitle template error - self.subtitle_template='%s'" % self.subtitle_template)
@ -3297,16 +3363,51 @@ class KOBOTOUCH(KOBO):
if (new_subtitle is not None and (book.kobo_subtitle is None or book.subtitle != book.kobo_subtitle)) or \
(new_subtitle is None and book.kobo_subtitle is not None):
update_values.append(new_subtitle)
set_clause += ', Subtitle = ? '
set_clause.append('Subtitle')
if self.update_bookstats:
if self.bookstats_pagecount_template is not None:
current_bookstats_pagecount = book.kobo_bookstats.get('StorePages', None)
generate_update_from_template(book, update_values, set_clause,
column_name='StorePages',
template=self.bookstats_pagecount_template,
new_value=book.bookstats_pagecount,
current_value=current_bookstats_pagecount
)
if self.bookstats_wordcount_template is not None:
current_bookstats_wordcount = book.kobo_bookstats.get('StoreWordCount', None)
generate_update_from_template(book, update_values, set_clause,
column_name='StoreWordCount',
template=self.bookstats_wordcount_template,
new_value=book.bookstats_wordcount,
current_value=current_bookstats_wordcount
)
if self.bookstats_timetoread_upper_template is not None:
current_bookstats_timetoread_upper = book.kobo_bookstats.get('StoreTimeToReadUpperEstimate', None)
generate_update_from_template(book, update_values, set_clause,
column_name='StoreTimeToReadUpperEstimate',
template=self.bookstats_timetoread_upper_template,
new_value=book.bookstats_timetoread_upper,
current_value=current_bookstats_timetoread_upper
)
if self.bookstats_timetoread_lower_template is not None:
current_bookstats_timetoread_lower = book.kobo_bookstats.get('StoreTimeToReadLowerEstimate', None)
generate_update_from_template(book, update_values, set_clause,
column_name='StoreTimeToReadLowerEstimate',
template=self.bookstats_timetoread_lower_template,
new_value=book.bookstats_timetoread_lower,
current_value=current_bookstats_timetoread_lower
)
if len(set_clause) > 0:
update_query += set_clause[1:]
update_query += ', '.join([col_name + ' = ?' for col_name in set_clause])
changes_found = True
if show_debug:
debug_print('KoboTouch:set_core_metadata set_clause="%s"' % set_clause)
debug_print('KoboTouch:set_core_metadata update_values="%s"' % update_values)
debug_print('KoboTouch:set_core_metadata update_values="%s"' % update_query)
if changes_found:
update_query += 'WHERE ContentID = ? AND BookID IS NULL'
update_query += ' WHERE ContentID = ? AND BookID IS NULL'
update_values.append(book.contentID)
cursor = connection.cursor()
try:
@ -3317,6 +3418,8 @@ class KOBOTOUCH(KOBO):
self.core_metadata_set += 1
except:
debug_print(' Database Exception: Unable to set the core metadata')
debug_print(f' Query was: {update_query}')
debug_print(f' Values were: {update_values}')
raise
finally:
cursor.close()
@ -3324,6 +3427,7 @@ class KOBOTOUCH(KOBO):
if show_debug:
debug_print("KoboTouch:set_core_metadata - end")
@classmethod
def config_widget(cls):
# TODO: Cleanup the following
@ -3387,6 +3491,11 @@ class KOBOTOUCH(KOBO):
c.add_opt('update_device_metadata', default=True)
c.add_opt('update_subtitle', default=False)
c.add_opt('subtitle_template', default=None)
c.add_opt('update_bookstats', default=False)
c.add_opt('bookstats_wordcount_template', default=None)
c.add_opt('bookstats_pagecount_template', default=None)
c.add_opt('bookstats_timetoread_upper_template', default=None)
c.add_opt('bookstats_timetoread_lower_template', default=None)
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.
@ -3644,6 +3753,43 @@ class KOBOTOUCH(KOBO):
subtitle_template = subtitle_template.strip() if subtitle_template is not None else None
return subtitle_template
@property
def update_bookstats(self):
# Subtitle was added to the database at the same time as the series support.
return self.update_device_metadata and self.supports_bookstats and self.get_pref('update_bookstats')
@property
def bookstats_wordcount_template(self):
if not self.update_bookstats:
return None
bookstats_wordcount_template = self.get_pref('bookstats_wordcount_template')
bookstats_wordcount_template = bookstats_wordcount_template.strip() if bookstats_wordcount_template is not None else None
return bookstats_wordcount_template
@property
def bookstats_pagecount_template(self):
if not self.update_bookstats:
return None
bookstats_pagecount_template = self.get_pref('bookstats_pagecount_template')
bookstats_pagecount_template = bookstats_pagecount_template.strip() if bookstats_pagecount_template is not None else None
return bookstats_pagecount_template
@property
def bookstats_timetoread_lower_template(self):
if not self.update_bookstats:
return None
bookstats_timetoread_lower_template = self.get_pref('bookstats_timetoread_lower_template')
bookstats_timetoread_lower_template = bookstats_timetoread_lower_template.strip() if bookstats_timetoread_lower_template is not None else None
return bookstats_timetoread_lower_template
@property
def bookstats_timetoread_upper_template(self):
if not self.update_bookstats:
return None
bookstats_timetoread_upper_template = self.get_pref('bookstats_timetoread_upper_template')
bookstats_timetoread_upper_template = bookstats_timetoread_upper_template.strip() if bookstats_timetoread_upper_template is not None else None
return bookstats_timetoread_upper_template
@property
def update_core_metadata(self):
return self.update_device_metadata and self.get_pref('update_core_metadata')
@ -3682,6 +3828,10 @@ class KOBOTOUCH(KOBO):
def supports_series(self):
return self.dbversion >= self.min_dbversion_series
@property
def supports_bookstats(self):
return self.fwversion >= self.min_fwversion_bookstats and self.dbversion >= self.min_dbversion_bookstats
@property
def supports_series_list(self):
return self.dbversion >= self.min_dbversion_seriesid and self.fwversion >= self.min_fwversion_serieslist

View File

@ -120,6 +120,11 @@ class KOBOTOUCHConfig(TabbedDeviceConfig):
p['update_purchased_kepubs'] = self.update_purchased_kepubs
p['subtitle_template'] = self.subtitle_template
p['update_subtitle'] = self.update_subtitle
p['update_bookstats'] = self.update_bookstats
p['bookstats_wordcount_template'] = self.bookstats_wordcount_template
p['bookstats_pagecount_template'] = self.bookstats_pagecount_template
p['bookstats_timetoread_upper_template'] = self.bookstats_timetoread_upper_template
p['bookstats_timetoread_lower_template'] = self.bookstats_timetoread_lower_template
p['modify_css'] = self.modify_css
p['override_kobo_replace_existing'] = self.override_kobo_replace_existing
@ -560,28 +565,88 @@ class MetadataGroupBox(DeviceOptionsGroupBox):
"If the template is empty, the subtitle will be cleared."
)
)
self.update_bookstats_checkbox = create_checkbox(
_("Book stats"),
_('Update the book stats '),
device.get_pref('update_bookstats')
)
self.bookstats_wordcount_template_edit = TemplateConfig(
device.get_pref('bookstats_wordcount_template'),
label=_("Words:"),
tooltip=_("Enter a template to use to set the word count for the book. "
"If the template is empty, the word count will be cleared."
)
)
self.bookstats_pagecount_template_edit = TemplateConfig(
device.get_pref('bookstats_pagecount_template'),
label=_("Pages:"),
tooltip=_("Enter a template to use to set the page count for the book. "
"If the template is empty, the page count will be cleared."
)
)
self.options_layout.addWidget(self.update_series_checkbox, 0, 0, 1, 2)
self.options_layout.addWidget(self.update_core_metadata_checkbox, 1, 0, 1, 2)
self.options_layout.addWidget(self.update_subtitle_checkbox, 2, 0, 1, 1)
self.options_layout.addWidget(self.subtitle_template_edit, 2, 1, 1, 1)
self.options_layout.addWidget(self.update_purchased_kepubs_checkbox, 3, 0, 1, 2)
self.bookstats_timetoread_label = QLabel(_('Hours to read estimates:'))
self.bookstats_timetoread_upper_template_edit = TemplateConfig(
device.get_pref('bookstats_timetoread_upper_template'),
label=_("Upper:"),
tooltip=_("Enter a template to use to set the upper estimate of the time to read for the book. "
"The estimate is in hours. "
"If the template is empty, the time will be cleared."
)
)
self.bookstats_timetoread_lower_template_edit = TemplateConfig(
device.get_pref('bookstats_timetoread_lower_template'),
label=_("Lower:"),
tooltip=_("Enter a template to use to set the lower estimate of the time to read for the book. "
"The estimate is in hours. "
"If the template is empty, the time will be cleared."
)
)
line = 0
self.options_layout.addWidget(self.update_series_checkbox, line, 0, 1, 4)
line += 1
self.options_layout.addWidget(self.update_core_metadata_checkbox, line, 0, 1, 4)
line += 1
self.options_layout.addWidget(self.update_subtitle_checkbox, line, 0, 1, 2)
self.options_layout.addWidget(self.subtitle_template_edit, line, 2, 1, 2)
line += 1
self.options_layout.addWidget(self.update_bookstats_checkbox, line, 0, 1, 2)
self.options_layout.addWidget(self.bookstats_wordcount_template_edit, line, 2, 1, 1)
self.options_layout.addWidget(self.bookstats_pagecount_template_edit, line, 3, 1, 1)
line += 1
self.options_layout.addWidget(self.bookstats_timetoread_label, line, 1, 1, 1)
self.options_layout.addWidget(self.bookstats_timetoread_lower_template_edit, line, 2, 1, 1)
self.options_layout.addWidget(self.bookstats_timetoread_upper_template_edit, line, 3, 1, 1)
line += 1
self.options_layout.addWidget(self.update_purchased_kepubs_checkbox, line, 0, 1, 4)
self.update_core_metadata_checkbox.clicked.connect(self.update_core_metadata_checkbox_clicked)
self.update_subtitle_checkbox.clicked.connect(self.update_subtitle_checkbox_clicked)
self.update_bookstats_checkbox.clicked.connect(self.update_bookstats_checkbox_clicked)
self.update_core_metadata_checkbox_clicked(device.get_pref('update_core_metadata'))
self.update_subtitle_checkbox_clicked(device.get_pref('update_subtitle'))
self.update_bookstats_checkbox_clicked(device.get_pref('update_bookstats'))
def update_core_metadata_checkbox_clicked(self, checked):
self.update_series_checkbox.setEnabled(not checked)
self.subtitle_template_edit.setEnabled(checked)
self.update_subtitle_checkbox.setEnabled(checked)
self.update_bookstats_checkbox.setEnabled(checked)
self.update_subtitle_checkbox_clicked(self.update_subtitle)
self.update_bookstats_checkbox_clicked(self.update_bookstats)
self.update_purchased_kepubs_checkbox.setEnabled(checked)
def update_subtitle_checkbox_clicked(self, checked):
self.subtitle_template_edit.setEnabled(checked and self.update_core_metadata)
def update_bookstats_checkbox_clicked(self, checked):
self.bookstats_timetoread_label.setEnabled(checked and self.update_bookstats and self.update_core_metadata)
self.bookstats_wordcount_template_edit.setEnabled(checked and self.update_bookstats and self.update_core_metadata)
self.bookstats_pagecount_template_edit.setEnabled(checked and self.update_bookstats and self.update_core_metadata)
self.bookstats_timetoread_upper_template_edit.setEnabled(checked and self.update_bookstats and self.update_core_metadata)
self.bookstats_timetoread_lower_template_edit.setEnabled(checked and self.update_bookstats and self.update_core_metadata)
def edit_template(self):
t = TemplateDialog(self, self.template)
t.setWindowTitle(_('Edit template'))
@ -591,6 +656,14 @@ class MetadataGroupBox(DeviceOptionsGroupBox):
def validate(self):
if self.update_subtitle and not self.subtitle_template_edit.validate():
return False
if self.update_bookstats and not self.bookstats_pagecount_template_edit.validate():
return False
if self.update_bookstats and not self.bookstats_wordcount_template_edit.validate():
return False
if self.update_bookstats and not self.bookstats_timetoread_upper_template_edit.validate():
return False
if self.update_bookstats and not self.bookstats_timetoread_lower_template_edit.validate():
return False
return True
@property
@ -617,20 +690,43 @@ class MetadataGroupBox(DeviceOptionsGroupBox):
def update_subtitle(self):
return self.update_subtitle_checkbox.isChecked()
@property
def update_bookstats(self):
return self.update_bookstats_checkbox.isChecked()
@property
def bookstats_pagecount_template(self):
return self.bookstats_pagecount_template_edit.template
@property
def bookstats_wordcount_template(self):
return self.bookstats_wordcount_template_edit.template
@property
def bookstats_timetoread_lower_template(self):
return self.bookstats_timetoread_lower_template_edit.template
@property
def bookstats_timetoread_upper_template(self):
return self.bookstats_timetoread_upper_template_edit.template
from calibre.gui2.dialogs.template_line_editor import TemplateLineEditor
class TemplateConfig(QWidget): # {{{
def __init__(self, val, tooltip=None):
QWidget.__init__(self)
self.t = t = QLineEdit(self)
def __init__(self, val, label=None, tooltip=None):
super().__init__()
self.l = l = QGridLayout(self)
self.setLayout(l)
col = 0
if label is not None:
l.addWidget(QLabel(label), 0, col, 1, 1)
col += 1
self.t = t = TemplateLineEditor(self)
t.setText(val or '')
t.setCursorPosition(0)
self.setMinimumWidth(300)
self.l = l = QGridLayout(self)
self.setLayout(l)
l.addWidget(t, 1, 0, 1, 1)
l.addWidget(t, 0, col, 1, 1)
col += 1
b = self.b = QPushButton(_('&Template editor'))
l.addWidget(b, 1, 1, 1, 1)
l.addWidget(b, 0, col, 1, 1)
b.clicked.connect(self.edit_template)
self.setToolTip(tooltip)