From 563b6d0b2e85062706ed5e72b928e79207deadfc Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Tue, 7 Sep 2010 13:04:45 +0100 Subject: [PATCH 1/2] Fix problem where book matching did not record how books were matched, which prevented downstream matching such as 'delete matching books' --- src/calibre/gui2/device.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/calibre/gui2/device.py b/src/calibre/gui2/device.py index 5fb8b1028b..aba949230f 100644 --- a/src/calibre/gui2/device.py +++ b/src/calibre/gui2/device.py @@ -1393,10 +1393,12 @@ class DeviceMixin(object): # {{{ self.db_book_uuid_cache[mi.uuid] = mi # Now iterate through all the books on the device, setting the - # in_library field Fastest and most accurate key is the uuid. Second is + # in_library field. Fastest and most accurate key is the uuid. Second is # the application_id, which is really the db key, but as this can # accidentally match across libraries we also verify the title. The - # db_id exists on Sony devices. Fallback is title and author match + # db_id exists on Sony devices. Fallback is title and author match. + # We set the application ID so that we can reproduce book matching, + # necessary for identifying copies of books. update_metadata = prefs['manage_device_metadata'] == 'on_connect' for booklist in booklists: @@ -1418,12 +1420,15 @@ class DeviceMixin(object): # {{{ if d is not None: if getattr(book, 'application_id', None) in d['db_ids']: book.in_library = True + # application already matches db_id, so no need to set it if update_metadata: book.smart_update(d['db_ids'][book.application_id], replace_metadata=True) continue if book.db_id in d['db_ids']: book.in_library = True + book.application_id = \ + d['db_ids'][book.db_id].application_id if update_metadata: book.smart_update(d['db_ids'][book.db_id], replace_metadata=True) @@ -1435,11 +1440,15 @@ class DeviceMixin(object): # {{{ book_authors = re.sub('(?u)\W|[_]', '', book_authors) if book_authors in d['authors']: book.in_library = True + book.application_id = \ + d['authors'][book_authors].application_id if update_metadata: book.smart_update(d['authors'][book_authors], replace_metadata=True) elif book_authors in d['author_sort']: book.in_library = True + book.application_id = \ + d['author_sort'][book_authors].application_id if update_metadata: book.smart_update(d['author_sort'][book_authors], replace_metadata=True) From d9d1981fbea307ab1d2799856c7e5c7bd2a849f1 Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Tue, 7 Sep 2010 14:18:14 +0100 Subject: [PATCH 2/2] Add a count of how many books on the device matched a particular copy in the library. Useful because books on the device can accumulate when sent after changing metadata used in the file path. The UUID doesn't change, so we can detect that they are copies of the same book. searching for 'ondevice:books' will find instances of more than one copy. --- src/calibre/gui2/device.py | 33 +++++++++++++++++++++++++++++--- src/calibre/library/database2.py | 5 +++-- 2 files changed, 33 insertions(+), 5 deletions(-) diff --git a/src/calibre/gui2/device.py b/src/calibre/gui2/device.py index aba949230f..099cae4ba6 100644 --- a/src/calibre/gui2/device.py +++ b/src/calibre/gui2/device.py @@ -1306,16 +1306,26 @@ class DeviceMixin(object): # {{{ self.library_view.model().refresh_ids(list(changed)) def book_on_device(self, id, format=None, reset=False): - loc = [None, None, None] + ''' + Return an indication of whether the given book represented by its db id + is on the currently connected device. It returns a 4 element list. The + first three elements represent memory locations main, carda, and cardb, + and are true if the book is identifiably in that memory. The fourth + is the a count of how many instances of the book were found across all + the memory locations. + ''' + loc = [None, None, None, 0] if reset: self.book_db_title_cache = None self.book_db_uuid_cache = None + self.book_db_id_counts = None return if self.book_db_title_cache is None: self.book_db_title_cache = [] self.book_db_uuid_cache = [] + self.book_db_id_counts = {} for i, l in enumerate(self.booklists()): self.book_db_title_cache.append({}) self.book_db_uuid_cache.append(set()) @@ -1333,6 +1343,10 @@ class DeviceMixin(object): # {{{ db_id = book.db_id if db_id is not None: self.book_db_title_cache[i][book_title]['db_ids'].add(db_id) + # increment the count of books on the device with this + # db_id. + c = self.book_db_id_counts.get(db_id, 0) + self.book_db_id_counts[db_id] = c + 1 uuid = getattr(book, 'uuid', None) if uuid is not None: self.book_db_uuid_cache[i].add(uuid) @@ -1351,7 +1365,13 @@ class DeviceMixin(object): # {{{ if mi.authors and \ re.sub('(?u)\W|[_]', '', authors_to_string(mi.authors).lower()) \ in cache['authors']: + # We really shouldn't get here, because set_books_in_library + # should have set the db_ids for the books, and therefore + # the if just above should have found them. Mark the book + # anyway, and print a message about the situation loc[i] = True + print 'book_on_device: matched title/author but not db_id!', \ + mi.title, authors_to_string(mi.authors) continue # Also check author sort, because it can be used as author in # some formats @@ -1360,9 +1380,16 @@ class DeviceMixin(object): # {{{ in cache['authors']: loc[i] = True continue + loc[3] = self.book_db_id_counts.get(id, 0) return loc def set_books_in_library(self, booklists, reset=False): + ''' + Set the ondevice indications in the device database. + This method should be called before book_on_device is called, because + it sets the application_id for matched books. Book_on_device uses that + to both speed up matching and to count matches. + ''' # Force a reset if the caches are not initialized if reset or not hasattr(self, 'db_book_title_cache'): # It might be possible to get here without having initialized the @@ -1428,7 +1455,7 @@ class DeviceMixin(object): # {{{ if book.db_id in d['db_ids']: book.in_library = True book.application_id = \ - d['db_ids'][book.db_id].application_id + d['db_ids'][book.db_id].application_id if update_metadata: book.smart_update(d['db_ids'][book.db_id], replace_metadata=True) @@ -1441,7 +1468,7 @@ class DeviceMixin(object): # {{{ if book_authors in d['authors']: book.in_library = True book.application_id = \ - d['authors'][book_authors].application_id + d['authors'][book_authors].application_id if update_metadata: book.smart_update(d['authors'][book_authors], replace_metadata=True) diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py index 23ec60d320..52b5f2d4e6 100644 --- a/src/calibre/library/database2.py +++ b/src/calibre/library/database2.py @@ -640,16 +640,17 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): def book_on_device_string(self, id): loc = [] + count = 0 on = self.book_on_device(id) if on is not None: - m, a, b = on + m, a, b, count = on if m is not None: loc.append(_('Main')) if a is not None: loc.append(_('Card A')) if b is not None: loc.append(_('Card B')) - return ', '.join(loc) + return ', '.join(loc) + ((' (%s books)'%count) if count > 1 else '') def set_book_on_device_func(self, func): self.book_on_device_func = func