mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Merge branch 'master' of https://github.com/davidfor/calibre
This commit is contained in:
commit
2b40d1dbfb
@ -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
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user