This commit is contained in:
Kovid Goyal 2022-04-15 08:21:39 +05:30
commit 2b40d1dbfb
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
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)