diff --git a/src/calibre/devices/smart_device_app/driver.py b/src/calibre/devices/smart_device_app/driver.py index 903cd4cd56..aaef36d8d9 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: @@ -188,7 +189,7 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin): printable[k] = 'too long' else: printable[k] = v - prints('', printable, end=''); + prints('', printable, end='') else: prints('', a, end='') except: @@ -368,7 +369,7 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin): if not isinstance(s, bytes): self._debug('given a non-byte string!') raise PacketError("Internal error: found a string that isn't bytes") - sent_len = 0; + sent_len = 0 total_len = len(s) while sent_len < total_len: try: @@ -377,7 +378,7 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin): else: amt_sent = self.device_socket.send(s[sent_len:]) if amt_sent <= 0: - raise IOError('Bad write on device socket'); + raise IOError('Bad write on device socket') sent_len += amt_sent except socket.error as e: self._debug('socket error', e, e.errno) @@ -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: @@ -473,7 +474,7 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin): b = infile.read(self.max_book_packet_len) blen = len(b) if not b: - break; + break b = b64encode(b) opcode, result = self._call_client('BOOK_DATA', {'lpath': lpath, 'position': pos, 'data': b}, @@ -502,29 +503,30 @@ 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 _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 +667,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) @@ -676,7 +682,7 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin): return False config = self._configProxy() config['format_map'] = exts - self._debug('selected formats', config['format_map']); + self._debug('selected formats', config['format_map']) if password: returned_hash = result.get('passwordHash', None) if result.get('passwordHash', None) is None: @@ -777,8 +783,10 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin): bl = CollectionsBookList(None, self.PREFIX, self.settings) if opcode == 'OK': count = result['count'] - will_stream = 'willStream' in result; + 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 +800,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 +820,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 +977,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: @@ -982,7 +1004,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 - self._debug(message); + self._debug(message) self.listen_socket.close() self.listen_socket = None self.is_connected = False @@ -992,10 +1014,10 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin): i += 1 port = self._attach_to_port(random.randint(8192, 32000)) if port != 0: - break; + break if port == 0: message = _('Failed to allocate a random port') - self._debug(message); + self._debug(message) self.listen_socket.close() self.listen_socket = None self.is_connected = False 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/ui.py b/src/calibre/gui2/ui.py index d9bfa069d6..2271c82c4f 100644 --- a/src/calibre/gui2/ui.py +++ b/src/calibre/gui2/ui.py @@ -373,18 +373,13 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{ 'the file: %s

The ' 'log will be displayed automatically.')%self.gui_debug, show=True) - smartdevice_action = self.iactions['Connect Share'] - smartdevice_action.check_smartdevice_menus() - self.sd_timer = QTimer(); - self.sd_timer.setSingleShot(True) - self.sd_timer.timeout.connect(self.start_smartdevice, type=Qt.QueuedConnection) - self.sd_timer.start(0) + self.iactions['Connect Share'].check_smartdevice_menus() + QTimer.singleShot(1, self.start_smartdevice) def esc(self, *args): self.clear_button.click() def start_smartdevice(self): - self.sd_timer = None message = None if self.device_manager.get_option('smartdevice', 'autostart'): try: @@ -399,8 +394,7 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{ error_dialog(self, _('Problem starting the wireless device'), _('The wireless device driver did not start. ' 'It said "%s"')%message, show=True) - smartdevice_action = self.iactions['Connect Share'] - smartdevice_action.set_smartdevice_action_state() + self.iactions['Connect Share'].set_smartdevice_action_state() def start_content_server(self, check_started=True): from calibre.library.server.main import start_threaded_server