mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Add a mechanism for device drivers to update books in the calibre database
Merge branch 'master' of https://github.com/cbhaley/calibre
This commit is contained in:
commit
f1c3e38dc9
@ -718,6 +718,27 @@ class DevicePlugin(Plugin):
|
||||
'''
|
||||
return False
|
||||
|
||||
def synchronize_with_db(self, db, book_id, book_metadata):
|
||||
'''
|
||||
Called during book matching when a book on the device is matched with
|
||||
a book in calibre's db. The method is responsible for syncronizing
|
||||
data from the device to calibre's db (if needed).
|
||||
|
||||
The method must return a set of calibre book ids changed if calibre's
|
||||
database was changed, None if the database was not changed. If the
|
||||
method returns an empty set then the metadata for the book on the
|
||||
device is updated with calibre's metadata and given back to the device,
|
||||
but no GUI refresh of that book is done. This is useful when the calire
|
||||
data is correct but must be sent to the device.
|
||||
|
||||
Extremely important: this method is called on the GUI thread. It must
|
||||
be threadsafe with respect to the device manager's thread.
|
||||
|
||||
book_id: the calibre id for the book in the database.
|
||||
book_metadata: the Metadata object for the book coming from the device.
|
||||
'''
|
||||
return None
|
||||
|
||||
class BookList(list):
|
||||
'''
|
||||
A list of books. Each Book object must have the fields
|
||||
|
@ -1080,6 +1080,12 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
|
||||
elif hasattr(self, 'THUMBNAIL_WIDTH'):
|
||||
delattr(self, 'THUMBNAIL_WIDTH')
|
||||
|
||||
self.is_read_sync_col = result.get('isReadSyncCol', None)
|
||||
self._debug('Device is_read sync col', self.is_read_sync_col)
|
||||
|
||||
self.is_read_date_sync_col = result.get('isReadDateSyncCol', False)
|
||||
self._debug('Device is_read_date sync col', self.is_read_date_sync_col)
|
||||
|
||||
if password:
|
||||
returned_hash = result.get('passwordHash', None)
|
||||
if result.get('passwordHash', None) is None:
|
||||
@ -1196,7 +1202,8 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
|
||||
opcode, result = self._call_client('GET_BOOK_COUNT',
|
||||
{'canStream':True,
|
||||
'canScan':True,
|
||||
'willUseCachedMetadata': self.client_can_use_metadata_cache})
|
||||
'willUseCachedMetadata': self.client_can_use_metadata_cache,
|
||||
'supportsSync': True})
|
||||
bl = CollectionsBookList(None, self.PREFIX, self.settings)
|
||||
if opcode == 'OK':
|
||||
count = result['count']
|
||||
@ -1219,6 +1226,9 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
|
||||
r['last_modified'])
|
||||
if book:
|
||||
bl.add_book(book, replace_metadata=True)
|
||||
book.set('_is_read_', r.get('_is_read_', None))
|
||||
book.set('_is_read_changed_', r.get('_is_read_changed_', None))
|
||||
book.set('_last_read_date_', r.get('_last_read_date_', None))
|
||||
else:
|
||||
books_to_send.append(r['priKey'])
|
||||
|
||||
@ -1239,6 +1249,9 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
|
||||
if '_series_sort_' in result:
|
||||
del result['_series_sort_']
|
||||
book = self.json_codec.raw_to_book(result, SDBook, self.PREFIX)
|
||||
book.set('_is_read_', result.get('_is_read_', None))
|
||||
book.set('_is_read_changed_', result.get('_is_read_changed_', None))
|
||||
book.set('_last_read_date_', r.get('_last_read_date_', None))
|
||||
bl.add_book(book, replace_metadata=True)
|
||||
if '_new_book_' in result:
|
||||
book.set('_new_book_', True)
|
||||
@ -1282,13 +1295,15 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
|
||||
# given back by "books", and one that has been plugboarded.
|
||||
books_to_send = []
|
||||
for book in booklists[0]:
|
||||
if not self._metadata_already_on_device(book):
|
||||
if (book.get('_force_send_metadata_', None) or
|
||||
not self._metadata_already_on_device(book)):
|
||||
books_to_send.append(book)
|
||||
|
||||
count = len(books_to_send)
|
||||
self._call_client('SEND_BOOKLISTS', {'count': count,
|
||||
'collections': coldict,
|
||||
'willStreamMetadata': True},
|
||||
'willStreamMetadata': True,
|
||||
'supportsSync': True},
|
||||
wait_for_response=False)
|
||||
|
||||
if count:
|
||||
@ -1297,7 +1312,7 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
|
||||
self._set_known_metadata(book)
|
||||
opcode, result = self._call_client(
|
||||
'SEND_BOOK_METADATA',
|
||||
{'index': i, 'count': count, 'data': book},
|
||||
{'index': i, 'count': count, 'data': book, 'supportsSync': True},
|
||||
print_debug_info=False,
|
||||
wait_for_response=False)
|
||||
|
||||
@ -1443,6 +1458,66 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
|
||||
def specialize_global_preferences(self, device_prefs):
|
||||
device_prefs.set_overrides(manage_device_metadata='on_connect')
|
||||
|
||||
@synchronous('sync_lock')
|
||||
def synchronize_with_db(self, db, id_, book):
|
||||
if not (self.is_read_sync_col or self.is_read_date_sync_col):
|
||||
# Not syncing
|
||||
return None
|
||||
|
||||
is_changed = book.get('_is_read_changed_', None);
|
||||
is_read = book.get('_is_read_', None)
|
||||
|
||||
if is_changed == 2 and is_read is None:
|
||||
# This is a special case where the user just set the sync column. In
|
||||
# this case the device value wins if it is not None by falling
|
||||
# through to the normal sync situation below, otherwise the calibre
|
||||
# value wins.
|
||||
calibre_val = db.new_api.field_for(self.is_read_sync_col,
|
||||
id_, default_value=None)
|
||||
if calibre_val is not None:
|
||||
# This will force the metadata for the book to be sent . Note
|
||||
# that because the devices last_read date is one-way sync, this
|
||||
# could leave an empty date in the device.
|
||||
book.set('_force_send_metadata_', True)
|
||||
self._debug('special update book', book.get('title', 'huh?'),
|
||||
'to', calibre_val)
|
||||
return set(id_)
|
||||
# Both values are None. Do nothing
|
||||
return None
|
||||
|
||||
orig_is_read = book.get(self.is_read_sync_col, None)
|
||||
if is_read != orig_is_read:
|
||||
# The value in the device's is_read checkbox is not the same as the
|
||||
# last one that came to the device from calibre during the last
|
||||
# connect, meaning that the user changed it. Write the one from the
|
||||
# checkbox to calibre's db.
|
||||
changed_books = set()
|
||||
is_read_date = book.get('_last_read_date_', None);
|
||||
self._debug('standard update book', book.get('title', 'huh?'), 'to',
|
||||
is_read, is_read_date)
|
||||
if self.is_read_sync_col:
|
||||
try:
|
||||
changed_books = db.new_api.set_field(self.is_read_sync_col,
|
||||
{id_: is_read})
|
||||
except:
|
||||
self._debug('setting read sync col tossed exception',
|
||||
self.is_read_sync_col)
|
||||
if self.is_read_date_sync_col:
|
||||
try:
|
||||
changed_books |= db.new_api.set_field(self.is_read_date_sync_col,
|
||||
{id_: is_read_date})
|
||||
except:
|
||||
self._debug('setting read date sync col tossed exception',
|
||||
self.is_read_date_sync_col)
|
||||
return changed_books
|
||||
|
||||
# The user might have changed the value in calibre. If so, that value
|
||||
# will be sent to the device in the normal way. Note that because any
|
||||
# updated value has already been synced and so will also be sent, the
|
||||
# device should put the calibre value into its checkbox (or whatever it
|
||||
# uses)
|
||||
return None
|
||||
|
||||
@synchronous('sync_lock')
|
||||
def startup(self):
|
||||
self.listen_socket = None
|
||||
@ -1466,6 +1541,8 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
|
||||
self.noop_counter = 0
|
||||
self.connection_attempts = {}
|
||||
self.client_wants_uuid_file_names = False
|
||||
self.is_read_sync_col = None
|
||||
self.is_read_date_sync_col = None
|
||||
|
||||
message = None
|
||||
compression_quality_ok = True
|
||||
|
@ -1751,12 +1751,7 @@ class DeviceMixin(object): # {{{
|
||||
self.db_book_title_cache = db_book_title_cache
|
||||
self.db_book_uuid_cache = db_book_uuid_cache
|
||||
|
||||
# Now iterate through all the books on the device, setting the
|
||||
# in_library field. If the UUID matches a book in the library, then
|
||||
# do not consider that book for other matching. In all cases set
|
||||
# the application_id to the db_id of the matching book. This value
|
||||
# will be used by books_on_device to indicate matches. While we are
|
||||
# going by, update the metadata for a book if automatic management is on
|
||||
book_ids_to_refresh = set()
|
||||
|
||||
def update_book(id_, book) :
|
||||
if not update_metadata:
|
||||
@ -1768,17 +1763,31 @@ class DeviceMixin(object): # {{{
|
||||
|
||||
def updateq(id_, book):
|
||||
try:
|
||||
return (update_metadata and
|
||||
(db.metadata_last_modified(id_, index_is_id=True) !=
|
||||
if not update_metadata:
|
||||
return False
|
||||
|
||||
if self.device_manager.device is not None:
|
||||
set_of_ids = self.device_manager.device.synchronize_with_db(db, id_, book)
|
||||
if set_of_ids is not None:
|
||||
book_ids_to_refresh.update(set_of_ids)
|
||||
return True
|
||||
|
||||
return (db.metadata_last_modified(id_, index_is_id=True) !=
|
||||
getattr(book, 'last_modified', None) or
|
||||
(isinstance(getattr(book, 'thumbnail', None), (list, tuple))
|
||||
and max(book.thumbnail[0], book.thumbnail[1]) != desired_thumbnail_height
|
||||
)
|
||||
)
|
||||
)
|
||||
except:
|
||||
return True
|
||||
|
||||
# Now iterate through all the books on the device, setting the
|
||||
# in_library field. If the UUID matches a book in the library, then
|
||||
# do not consider that book for other matching. In all cases set
|
||||
# the application_id to the db_id of the matching book. This value
|
||||
# will be used by books_on_device to indicate matches. While we are
|
||||
# going by, update the metadata for a book if automatic management is on
|
||||
|
||||
total_book_count = 0
|
||||
for booklist in booklists:
|
||||
for book in booklist:
|
||||
@ -1873,6 +1882,16 @@ class DeviceMixin(object): # {{{
|
||||
FunctionDispatcher(self.metadata_synced), booklists,
|
||||
plugboards, add_as_step_to_job)
|
||||
|
||||
if book_ids_to_refresh:
|
||||
try:
|
||||
prints('DeviceJob: set_books_in_library refreshing GUI for ',
|
||||
len(book_ids_to_refresh), 'books')
|
||||
self.library_view.model().refresh_ids(book_ids_to_refresh,
|
||||
current_row=self.library_view.currentIndex().row())
|
||||
except:
|
||||
# This shouldn't ever happen, but just in case ...
|
||||
traceback.print_exc()
|
||||
|
||||
if DEBUG:
|
||||
prints('DeviceJob: set_books_in_library finished: time=',
|
||||
time.time() - start_time)
|
||||
|
Loading…
x
Reference in New Issue
Block a user