diff --git a/src/calibre/devices/smart_device_app/driver.py b/src/calibre/devices/smart_device_app/driver.py index fe32c493d0..33767d3e24 100644 --- a/src/calibre/devices/smart_device_app/driver.py +++ b/src/calibre/devices/smart_device_app/driver.py @@ -171,6 +171,7 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin): self.debug_start_time = time.time() self.debug_time = time.time() self.client_can_stream_books = False + self.client_can_stream_metadata = False def _debug(self, *args): if not DEBUG: @@ -391,9 +392,9 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin): extra_debug = self.settings().extra_customization[self.OPT_EXTRA_DEBUG] if print_debug_info or extra_debug: if extra_debug: - self._debug(op, arg) + self._debug(op, 'wfr', wait_for_response, arg) else: - self._debug(op) + self._debug(op, 'wfr', wait_for_response) if self.device_socket is None: return None, None try: @@ -502,29 +503,11 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin): else: return None - def _compare_metadata(self, mi1, mi2): - for key in SERIALIZABLE_FIELDS: - if key in ['cover', 'mime']: - continue - if key == 'user_metadata': - meta1 = mi1.get_all_user_metadata(make_copy=False) - meta2 = mi1.get_all_user_metadata(make_copy=False) - if meta1 != meta2: - self._debug('custom metadata different') - return False - for ckey in meta1: - if mi1.get(ckey) != mi2.get(ckey): - self._debug(ckey, mi1.get(ckey), mi2.get(ckey)) - return False - elif mi1.get(key, None) != mi2.get(key, None): - self._debug(key, mi1.get(key), mi2.get(key)) - return False - return True - def _metadata_already_on_device(self, book): v = self.known_metadata.get(book.lpath, None) if v is not None: - return self._compare_metadata(book, v) + return (v.get('uuid', None) == book.get('uuid', None) and + v.get('last_modified', None) == book.get('last_modified', None)) return False def _set_known_metadata(self, book, remove=False): @@ -665,8 +648,12 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin): self._close_device_socket() return False self._debug('CC version #:', result.get('ccVersionNumber', 'unknown')) + self.client_can_stream_books = result.get('canStreamBooks', False) - self._debug('CC can stream', self.client_can_stream_books) + self._debug('CC can stream books', self.client_can_stream_books) + self.client_can_stream_metadata = result.get('canStreamMetadata', False) + self._debug('CC can stream metadata', self.client_can_stream_metadata) + self.max_book_packet_len = result.get('maxBookContentPacketLen', self.BASE_PACKET_LEN) exts = result.get('acceptedExtensions', None) @@ -779,6 +766,8 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin): count = result['count'] will_stream = 'willStream' in result for i in range(0, count): + if (i % 100) == 0: + self._debug('getting book metadata. Done', i, 'of', count) if will_stream: opcode, result = self._receive_from_client(print_debug_info=False) else: @@ -792,6 +781,7 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin): bl.add_book(book, replace_metadata=True) else: raise ControlError(desc='book metadata not returned') + self._debug('finished getting book metadata') return bl @synchronous('sync_lock') @@ -811,15 +801,27 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin): # If we ever do device_db plugboards, this is where it will go. We will # probably need to send two booklists, one with calibre's data that is # given back by "books", and one that has been plugboarded. - self._call_client('SEND_BOOKLISTS', { 'count': len(booklists[0]), - 'collections': coldict}) - for i,book in enumerate(booklists[0]): + books_to_send = [] + for book in booklists[0]: if 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': self.client_can_stream_metadata}, + wait_for_response=not self.client_can_stream_metadata) + + if count: + for i,book in enumerate(books_to_send): + self._debug('sending metadata for book', book.lpath) self._set_known_metadata(book) - opcode, result = self._call_client('SEND_BOOK_METADATA', - {'index': i, 'data': book}, - print_debug_info=False) - if opcode != 'OK': + opcode, result = self._call_client( + 'SEND_BOOK_METADATA', + {'index': i, 'count': count, 'data': book}, + print_debug_info=False, + wait_for_response=not self.client_can_stream_metadata) + if not self.client_can_stream_metadata and opcode != 'OK': self._debug('protocol error', opcode, i) raise ControlError(desc='sync_booklists') @@ -956,6 +958,7 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin): self.noop_counter = 0 self.connection_attempts = {} self.client_can_stream_books = False + self.client_can_stream_metadata = False message = None try: @@ -973,7 +976,7 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin): except: message = _('Invalid port in options: %s')% \ self.settings().extra_customization[self.OPT_PORT_NUMBER] - self.debug(message) + self._debug(message) self.listen_socket.close() self.listen_socket = None self.is_connected = False @@ -981,7 +984,7 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin): port = self._attach_to_port(opt_port) if port == 0: - message = 'Failed to connect to port %d'%opt_port + message = _('Failed to connect to port %d. Try a different value.')%opt_port self._debug(message) self.listen_socket.close() self.listen_socket = None diff --git a/src/calibre/ebooks/metadata/book/__init__.py b/src/calibre/ebooks/metadata/book/__init__.py index 38a824374c..28c69df2c5 100644 --- a/src/calibre/ebooks/metadata/book/__init__.py +++ b/src/calibre/ebooks/metadata/book/__init__.py @@ -42,6 +42,7 @@ PUBLICATION_METADATA_FIELDS = frozenset([ 'book_producer', 'timestamp', # Dates and times must be timezone aware 'pubdate', + 'last_modified', 'rights', # So far only known publication type is periodical:calibre # If None, means book diff --git a/src/calibre/gui2/device.py b/src/calibre/gui2/device.py index e319e95535..03e1932035 100644 --- a/src/calibre/gui2/device.py +++ b/src/calibre/gui2/device.py @@ -1608,7 +1608,10 @@ class DeviceMixin(object): # {{{ if getattr(book, 'uuid', None) in self.db_book_uuid_cache: id_ = db_book_uuid_cache[book.uuid] if update_metadata: - book.smart_update(db.get_metadata(id_, + mi = db.get_metadata(id_, index_is_id=True, + get_cover=get_covers) + if book.get('last_modified', None) != mi.last_modified: + book.smart_update(db.get_metadata(id_, index_is_id=True, get_cover=get_covers), replace_metadata=True) diff --git a/src/calibre/gui2/dialogs/smartdevice.py b/src/calibre/gui2/dialogs/smartdevice.py index 1a8829323b..5de933a21c 100644 --- a/src/calibre/gui2/dialogs/smartdevice.py +++ b/src/calibre/gui2/dialogs/smartdevice.py @@ -33,13 +33,12 @@ class SmartdeviceDialog(QDialog, Ui_Dialog): _('Check this box if you want calibre to use a fixed network ' 'port. Normally you will not need to do this. However, if ' 'your device consistently fails to connect to calibre, ' - 'try checking this box.') + '
') + 'try checking this box and entering a number.') + '') self.fixed_port.setToolTip('' + - _('A port number must be a 4-digit integer less than 32,000. No ' - 'two network applications on the same computer can use ' - 'the same port number. If calibre says that it fails to connect ' - 'to the port, try a different number.') + '
') + _('Try 9090. If calibre says that it fails to connect ' + 'to the port, try another number. You can use any number between ' + '8,000 and 32,000.') + '') self.show_password.stateChanged[int].connect(self.toggle_password) self.use_fixed_port.stateChanged[int].connect(self.use_fixed_port_changed) @@ -75,6 +74,23 @@ class SmartdeviceDialog(QDialog, Ui_Dialog): Qt.Unchecked else QLineEdit.Normal) def accept(self): + port = unicode(self.fixed_port.text()) + if not port: + error_dialog(self, _('Invalid port number'), + _('You must provide a port number.'), show=True) + return + try: + port = int(port) + except: + error_dialog(self, _('Invalid port number'), + _('The port must be a number between 8000 and 32000.'), show=True) + return + + if port < 8000 or port > 32000: + error_dialog(self, _('Invalid port number'), + _('The port must be a number between 8000 and 32000.'), show=True) + return + self.device_manager.set_option('smartdevice', 'password', unicode(self.password_box.text())) self.device_manager.set_option('smartdevice', 'autostart',