diff --git a/src/calibre/devices/smart_device_app/driver.py b/src/calibre/devices/smart_device_app/driver.py index c981e83c0a..2bf368e3f7 100644 --- a/src/calibre/devices/smart_device_app/driver.py +++ b/src/calibre/devices/smart_device_app/driver.py @@ -355,8 +355,6 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin): self.debug_start_time = time.time() self.debug_time = time.time() - # This must be protected by a lock because it is called from three threads - @synchronous('sync_lock') def _debug(self, *args): # manual synchronization so we don't lose the calling method name import inspect @@ -694,8 +692,8 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin): return None def _metadata_in_cache(self, uuid, ext_or_lpath, lastmod): + from calibre.utils.date import now, parse_date try: - from calibre.utils.date import parse_date, now key = self._make_metadata_cache_key(uuid, ext_or_lpath) if isinstance(lastmod, unicode): if lastmod == 'None': @@ -795,8 +793,8 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin): traceback.print_exc() def _write_metadata_cache(self): - self._debug() from calibre.utils.date import now + self._debug() from calibre.utils.config import to_json cache_file_name = os.path.join(cache_dir(), 'wireless_device_' + self.device_uuid + @@ -990,7 +988,8 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin): 'currentLibraryUUID': library_uuid, 'pubdateFormat': tweaks['gui_pubdate_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}) if opcode != 'OK': # Something wrong with the return. Close the socket # and continue. @@ -1089,7 +1088,7 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin): 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.is_read_date_sync_col = result.get('isReadDateSyncCol', None) self._debug('Device is_read_date sync col', self.is_read_date_sync_col) if password: @@ -1209,7 +1208,8 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin): {'canStream':True, 'canScan':True, 'willUseCachedMetadata': self.client_can_use_metadata_cache, - 'supportsSync': True}) + 'supportsSync': (self.is_read_sync_col or + self.is_read_date_sync_col)}) bl = CollectionsBookList(None, self.PREFIX, self.settings) if opcode == 'OK': count = result['count'] @@ -1257,7 +1257,7 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin): 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)) + book.set('_last_read_date_', result.get('_last_read_date_', None)) bl.add_book(book, replace_metadata=True) if '_new_book_' in result: book.set('_new_book_', True) @@ -1309,7 +1309,8 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin): self._call_client('SEND_BOOKLISTS', {'count': count, 'collections': coldict, 'willStreamMetadata': True, - 'supportsSync': True}, + 'supportsSync': (self.is_read_sync_col or + self.is_read_date_sync_col)}, wait_for_response=False) if count: @@ -1318,7 +1319,9 @@ 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, 'supportsSync': True}, + {'index': i, 'count': count, 'data': book, + 'supportsSync': (self.is_read_sync_col or + self.is_read_date_sync_col)}, print_debug_info=False, wait_for_response=False) @@ -1466,86 +1469,123 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin): @synchronous('sync_lock') def synchronize_with_db(self, db, id_, book): + from calibre.utils.date import parse_date, UNDEFINED_DATE def show_message(message): self._call_client("DISPLAY_MESSAGE", {'messageKind': self.MESSAGE_SHOW_TOAST, 'message': message}) - if not (self.is_read_sync_col or self.is_read_date_sync_col): - # Not syncing + if self.have_bad_sync_columns or not (self.is_read_sync_col or + self.is_read_date_sync_col): + # Not syncing or sync columns are invalid return None + # Check the validity of the columns once per connection. We do it + # here because we have access to the db to get field_metadata if not self.have_checked_sync_columns: - # Check the validity of the columns once per connection. We do it - # here because we have access to the db to get field_metadata fm = db.field_metadata.custom_field_metadata() if self.is_read_sync_col: if self.is_read_sync_col not in fm: self._debug('is_read_sync_col not in field_metadata') show_message(_("The read sync column %s is " "not in calibre's library")%self.is_read_sync_col) + self.have_bad_sync_columns = True elif fm[self.is_read_sync_col]['datatype'] != 'bool': self._debug('is_read_sync_col not bool type') show_message(_("The read sync column %s is " "not a Yes/No column")%self.is_read_sync_col) + self.have_bad_sync_columns = True if self.is_read_date_sync_col: if self.is_read_date_sync_col not in fm: self._debug('is_read_date_sync_col not in field_metadata') show_message(_("The read date sync column %s is " "not in calibre's library")%self.is_read_date_sync_col) + self.have_bad_sync_columns = True elif fm[self.is_read_date_sync_col]['datatype'] != 'datetime': self._debug('is_read_date_sync_col not date type') show_message(_("The read date sync column %s is " "not a Date column")%self.is_read_date_sync_col) + self.have_bad_sync_columns = True self.have_checked_sync_columns = True + if self.have_bad_sync_columns: + return None is_changed = book.get('_is_read_changed_', None); is_read = book.get('_is_read_', None) + # This returns UNDEFINED_DATE if the value is None + is_read_date = parse_date(book.get('_last_read_date_', None)); + value_to_return = None - if is_changed == 2 and is_read is None: + if is_changed == 2: # 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 + # value wins. The orig_* values are set to None to force the normal + # sync code to actually sync because the values are different + orig_is_read_date = UNDEFINED_DATE + orig_is_read = None + if is_read is None: + calibre_val = db.new_api.field_for(self.is_read_sync_col, + id_, default_value=None) + if calibre_val is not None: + # This forces the metadata for the book to be sent to the + # device even if the mod dates haven't changed. + book.set('_force_send_metadata_', True) + self._debug('special update is_read', book.get('title', 'huh?'), + 'to', calibre_val) + value_to_return = set() - 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: + if is_read_date == UNDEFINED_DATE: + calibre_val = db.new_api.field_for(self.is_read_date_sync_col, + id_, default_value=None) + if calibre_val != UNDEFINED_DATE: + book.set('_force_send_metadata_', True) + self._debug('special update is_read_date', book.get('title', 'huh?'), + 'to', calibre_val) + value_to_return = set() + # Fall through to the normal sync. At this point either the is_read* + # values are different from the orig_is_read* which will cause a + # sync below, or they are both None which will cause the code below + # to do nothing. If either of the calibre data fields were set, the + # method will return set(), which will force updated metadata to be + # given back to the device, effectively forcing the sync of the + # calibre values back to the device. + else: + orig_is_read = book.get(self.is_read_sync_col, None) + orig_is_read_date = book.get(self.is_read_date_sync_col, None) + + changed_books = set() + try: + 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 + # device to calibre's db. + self._debug('standard update book is_read', book.get('title', 'huh?'), + 'to', is_read) + if self.is_read_sync_col: 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: + except: + self._debug('exception syncing is_read col', self.is_read_sync_col) + traceback.print_exc() + + try: + if is_read_date != orig_is_read_date: + self._debug('standard update book is_read_date', book.get('title', 'huh?'), + 'to', is_read_date) + if self.is_read_date_sync_col: 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) + {id_: is_read_date}) + except: + self._debug('Exception while syncing is_read_date', self.is_read_date_sync_col) + traceback.print_exc() + + if changed_books: + # One of the two values was synced, giving a list of changed books. + # Return that. return changed_books # The user might have changed the value in calibre. If so, that value @@ -1553,7 +1593,7 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin): # 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 + return value_to_return @synchronous('sync_lock') def startup(self): @@ -1581,6 +1621,7 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin): self.is_read_sync_col = None self.is_read_date_sync_col = None self.have_checked_sync_columns = False + self.have_bad_sync_columns = False message = None compression_quality_ok = True