This commit is contained in:
Kovid Goyal 2014-09-06 11:51:07 +05:30
commit 1baae866cc
3 changed files with 106 additions and 14 deletions

View File

@ -732,12 +732,23 @@ class DevicePlugin(Plugin):
a book in calibre's db. The method is responsible for syncronizing a book in calibre's db. The method is responsible for syncronizing
data from the device to calibre's db (if needed). data from the device to calibre's db (if needed).
The method must return a set of calibre book ids changed if calibre's The method must return a two-value tuple. The first value is a set of
database was changed, None if the database was not changed. If the calibre book ids changed if calibre's database was changed or None if the
method returns an empty set then the metadata for the book on the database was not changed. If the first value is an empty set then the
device is updated with calibre's metadata and given back to the device, metadata for the book on the device is updated with calibre's metadata
but no GUI refresh of that book is done. This is useful when the calire and given back to the device, but no GUI refresh of that book is done.
data is correct but must be sent to the device. This is useful when the calire data is correct but must be sent to the
device.
The second value in the tuple specifies whether a book format should be
sent to the device. The intent is to permit verifying that the book on
the device is the same as the book in calibre. Return None if no book is
to be sent, otherwise return the base file name on the device (a string
like foobar.epub). Be sure to include the extension in the name. The
device subsystem will construct a send_books job for all books with not-
None returned values. Note: other than to later retrieve the extension,
the name is ignored in cases where the device uses a template to
generate the file name, which most do.
Extremely important: this method is called on the GUI thread. It must Extremely important: this method is called on the GUI thread. It must
be threadsafe with respect to the device manager's thread. be threadsafe with respect to the device manager's thread.
@ -745,7 +756,7 @@ class DevicePlugin(Plugin):
book_id: the calibre id for the book in the database. book_id: the calibre id for the book in the database.
book_metadata: the Metadata object for the book coming from the device. book_metadata: the Metadata object for the book coming from the device.
''' '''
return None return None, None
class BookList(list): class BookList(list):
''' '''

View File

@ -999,7 +999,8 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
'pubdateFormat': tweaks['gui_pubdate_display_format'], 'pubdateFormat': tweaks['gui_pubdate_display_format'],
'timestampFormat': tweaks['gui_timestamp_display_format'], 'timestampFormat': tweaks['gui_timestamp_display_format'],
'lastModifiedFormat': tweaks['gui_last_modified_display_format'], 'lastModifiedFormat': tweaks['gui_last_modified_display_format'],
'calibre_version': numeric_version}) 'calibre_version': numeric_version,
'canSupportUpdateBooks': True})
if opcode != 'OK': if opcode != 'OK':
# Something wrong with the return. Close the socket # Something wrong with the return. Close the socket
# and continue. # and continue.
@ -1017,6 +1018,8 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
self._close_device_socket() self._close_device_socket()
return False return False
# Set up to recheck the sync columns
self.have_checked_sync_columns = False
client_can_stream_books = result.get('canStreamBooks', False) client_can_stream_books = result.get('canStreamBooks', False)
self._debug('Device can stream books', client_can_stream_books) self._debug('Device can stream books', client_can_stream_books)
client_can_stream_metadata = result.get('canStreamMetadata', False) client_can_stream_metadata = result.get('canStreamMetadata', False)
@ -1043,6 +1046,12 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
self._debug('Can send OK to sendbook', self.can_send_ok_to_sendbook) self._debug('Can send OK to sendbook', self.can_send_ok_to_sendbook)
self.can_accept_library_info = result.get('canAcceptLibraryInfo', False) self.can_accept_library_info = result.get('canAcceptLibraryInfo', False)
self._debug('Can accept library info', self.can_accept_library_info) self._debug('Can accept library info', self.can_accept_library_info)
self.will_ask_for_update_books = result.get('willAskForUpdateBooks', False)
self._debug('Will ask for update books', self.will_ask_for_update_books)
self.set_temp_mark_when_syncing_read = \
result.get('setTempMarkWhenReadInfoSynced', False)
self._debug('Will set temp mark when syncing read',
self.set_temp_mark_when_syncing_read)
if not self.settings().extra_customization[self.OPT_USE_METADATA_CACHE]: if not self.settings().extra_customization[self.OPT_USE_METADATA_CACHE]:
self.client_can_use_metadata_cache = False self.client_can_use_metadata_cache = False
@ -1223,7 +1232,8 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
'canScan':True, 'canScan':True,
'willUseCachedMetadata': self.client_can_use_metadata_cache, 'willUseCachedMetadata': self.client_can_use_metadata_cache,
'supportsSync': (bool(self.is_read_sync_col) or 'supportsSync': (bool(self.is_read_sync_col) or
bool(self.is_read_date_sync_col))}) bool(self.is_read_date_sync_col)),
'canSupportBookFormatSync': True})
bl = CollectionsBookList(None, self.PREFIX, self.settings) bl = CollectionsBookList(None, self.PREFIX, self.settings)
if opcode == 'OK': if opcode == 'OK':
count = result['count'] count = result['count']
@ -1252,6 +1262,7 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
book.set('_is_read_', r.get('_is_read_', None)) book.set('_is_read_', r.get('_is_read_', None))
book.set('_sync_type_', r.get('_sync_type_', None)) book.set('_sync_type_', r.get('_sync_type_', None))
book.set('_last_read_date_', r.get('_last_read_date_', None)) book.set('_last_read_date_', r.get('_last_read_date_', None))
book.set('_format_mtime_', r.get('_format_mtime_', None))
else: else:
books_to_send.append(r['priKey']) books_to_send.append(r['priKey'])
@ -1529,6 +1540,30 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
def specialize_global_preferences(self, device_prefs): def specialize_global_preferences(self, device_prefs):
device_prefs.set_overrides(manage_device_metadata='on_connect') device_prefs.set_overrides(manage_device_metadata='on_connect')
def _check_if_format_send_needed(self, db, id_, book):
if not self.will_ask_for_update_books:
return None
from calibre.utils.date import parse_date, now, isoformat
try:
if not hasattr(book, '_format_mtime_'):
return None
cc_mtime = parse_date(book.get('_format_mtime_'), as_utc=False)
ext = posixpath.splitext(book.lpath)[1][1:]
fmt_metadata = db.new_api.format_metadata(id_, ext)
if fmt_metadata:
calibre_mtime = fmt_metadata['mtime']
self._debug(book.title, 'cal_mtime', calibre_mtime, 'cc_mtime', cc_mtime)
if cc_mtime < calibre_mtime:
book.set('_format_mtime_', isoformat(now()))
return posixpath.basename(book.lpath)
except:
self._debug('exception checking if must send format', book.title)
traceback.print_exc()
return None
@synchronous('sync_lock') @synchronous('sync_lock')
def synchronize_with_db(self, db, id_, book): def synchronize_with_db(self, db, id_, book):
from calibre.utils.date import parse_date, is_date_undefined from calibre.utils.date import parse_date, is_date_undefined
@ -1540,7 +1575,7 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
if self.have_bad_sync_columns or not (self.is_read_sync_col or if self.have_bad_sync_columns or not (self.is_read_sync_col or
self.is_read_date_sync_col): self.is_read_date_sync_col):
# Not syncing or sync columns are invalid # Not syncing or sync columns are invalid
return None return (None, self._check_if_format_send_needed(db, id_, book))
# Check the validity of the columns once per connection. We do it # Check the validity of the columns once per connection. We do it
# here because we have access to the db to get field_metadata # here because we have access to the db to get field_metadata
@ -1572,7 +1607,12 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
self.have_checked_sync_columns = True self.have_checked_sync_columns = True
if self.have_bad_sync_columns: if self.have_bad_sync_columns:
return None return (None, self._check_if_format_send_needed(db, id_, book))
# if we are marking synced books, clear all the current marks
if self.set_temp_mark_when_syncing_read:
self._debug('clearing temp marks')
db.set_marked_ids(())
sync_type = book.get('_sync_type_', None) sync_type = book.get('_sync_type_', None)
# We need to check if our attributes are in the book. If they are not # We need to check if our attributes are in the book. If they are not
@ -1624,6 +1664,8 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
book.get('title', 'huh?'), 'to', is_read, calibre_val) book.get('title', 'huh?'), 'to', is_read, calibre_val)
changed_books = db.new_api.set_field(self.is_read_sync_col, changed_books = db.new_api.set_field(self.is_read_sync_col,
{id_: is_read}) {id_: is_read})
if self.set_temp_mark_when_syncing_read:
db.data.toggle_marked_ids({id_})
elif calibre_val is not None: elif calibre_val is not None:
# Calibre value wins. Force the metadata for the # Calibre value wins. Force the metadata for the
# book to be sent to the device even if the mod # book to be sent to the device even if the mod
@ -1648,6 +1690,8 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
book.get('title', 'huh?'), 'to', is_read_date, calibre_val) book.get('title', 'huh?'), 'to', is_read_date, calibre_val)
changed_books |= db.new_api.set_field(self.is_read_date_sync_col, changed_books |= db.new_api.set_field(self.is_read_date_sync_col,
{id_: is_read_date}) {id_: is_read_date})
if self.set_temp_mark_when_syncing_read:
db.data.toggle_marked_ids({id_})
elif calibre_val is not None: elif calibre_val is not None:
self._debug('special update is_read_date to calibre value', self._debug('special update is_read_date to calibre value',
book.get('title', 'huh?'), 'to', calibre_val) book.get('title', 'huh?'), 'to', calibre_val)
@ -1674,6 +1718,8 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
'to', is_read, 'was', orig_is_read) 'to', is_read, 'was', orig_is_read)
changed_books = db.new_api.set_field(self.is_read_sync_col, changed_books = db.new_api.set_field(self.is_read_sync_col,
{id_: is_read}) {id_: is_read})
if self.set_temp_mark_when_syncing_read:
db.data.toggle_marked_ids({id_})
except: except:
self._debug('exception standard syncing is_read', self.is_read_sync_col) self._debug('exception standard syncing is_read', self.is_read_sync_col)
traceback.print_exc() traceback.print_exc()
@ -1689,6 +1735,8 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
'to', is_read_date, 'was', orig_is_read_date) 'to', is_read_date, 'was', orig_is_read_date)
changed_books |= db.new_api.set_field(self.is_read_date_sync_col, changed_books |= db.new_api.set_field(self.is_read_date_sync_col,
{id_: is_read_date}) {id_: is_read_date})
if self.set_temp_mark_when_syncing_read:
db.data.toggle_marked_ids({id_})
except: except:
self._debug('Exception standard syncing is_read_date', self._debug('Exception standard syncing is_read_date',
self.is_read_date_sync_col) self.is_read_date_sync_col)
@ -1697,14 +1745,14 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
if changed_books or force_return_changed_books: if changed_books or force_return_changed_books:
# One of the two values was synced, giving a (perhaps empty) list of # One of the two values was synced, giving a (perhaps empty) list of
# changed books. Return that. # changed books. Return that.
return changed_books return (changed_books, self._check_if_format_send_needed(db, id_, book))
# Nothing was synced. The user might have changed the value in calibre. # Nothing was synced. The user might have changed the value in calibre.
# If so, that value will be sent to the device in the normal way. Note # 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 # that because any updated value has already been synced and so will
# also be sent, the device should put the calibre value into its # also be sent, the device should put the calibre value into its
# checkbox (or whatever it uses) # checkbox (or whatever it uses)
return None return (None, self._check_if_format_send_needed(db, id_, book))
@synchronous('sync_lock') @synchronous('sync_lock')
def startup(self): def startup(self):

View File

@ -34,6 +34,7 @@ from calibre.constants import DEBUG
from calibre.utils.config import tweaks, device_prefs from calibre.utils.config import tweaks, device_prefs
from calibre.utils.magick.draw import thumbnail from calibre.utils.magick.draw import thumbnail
from calibre.library.save_to_disk import find_plugboard from calibre.library.save_to_disk import find_plugboard
from calibre.ptempfile import PersistentTemporaryFile, force_unicode as filename_to_unicode
# }}} # }}}
class DeviceJob(BaseJob): # {{{ class DeviceJob(BaseJob): # {{{
@ -1774,6 +1775,7 @@ class DeviceMixin(object): # {{{
self.db_book_uuid_cache = db_book_uuid_cache self.db_book_uuid_cache = db_book_uuid_cache
book_ids_to_refresh = set() book_ids_to_refresh = set()
book_formats_to_send = []
def update_book(id_, book) : def update_book(id_, book) :
if not update_metadata: if not update_metadata:
@ -1789,7 +1791,10 @@ class DeviceMixin(object): # {{{
return False return False
if self.device_manager.device is not None: if self.device_manager.device is not None:
set_of_ids = self.device_manager.device.synchronize_with_db(db, id_, book) set_of_ids, fmt_name = \
self.device_manager.device.synchronize_with_db(db, id_, book)
if fmt_name is not None:
book_formats_to_send.append((id_, fmt_name))
if set_of_ids is not None: if set_of_ids is not None:
book_ids_to_refresh.update(set_of_ids) book_ids_to_refresh.update(set_of_ids)
return True return True
@ -1913,6 +1918,34 @@ class DeviceMixin(object): # {{{
# This shouldn't ever happen, but just in case ... # This shouldn't ever happen, but just in case ...
traceback.print_exc() traceback.print_exc()
# Sync books if necessary
try:
files, names, metadata = [], [], []
for id_, fmt_name in book_formats_to_send:
if DEBUG:
prints('DeviceJob: Syncing book. id:', id_, 'name from device', fmt_name)
ext = os.path.splitext(fmt_name)[1][1:]
fmt_info = db.new_api.format_metadata(id_, ext)
if fmt_info:
try:
pt = PersistentTemporaryFile(suffix='caltmpfmt.'+ext)
db.new_api.copy_format_to(id_, ext, pt)
pt.close()
files.append(filename_to_unicode(os.path.abspath(pt.name)))
names.append(fmt_name)
metadata.append(db.new_api.get_metadata(id_, get_cover=True))
except:
prints('Problem creating temporary file for', fmt_name)
traceback.print_exc()
else:
if DEBUG:
prints("DeviceJob: book doesn't have that format")
if files:
self.upload_books(files, names, metadata)
except:
# Shouldn't ever happen, but just in case
traceback.print_exc()
if DEBUG: if DEBUG:
prints('DeviceJob: set_books_in_library finished: time=', prints('DeviceJob: set_books_in_library finished: time=',
time.time() - start_time) time.time() - start_time)