diff --git a/src/calibre/devices/usbms/books.py b/src/calibre/devices/usbms/books.py index 4cde8dbe57..e04498b0c8 100644 --- a/src/calibre/devices/usbms/books.py +++ b/src/calibre/devices/usbms/books.py @@ -46,6 +46,14 @@ class Book(object): return self.title.encode('utf-8') + " by " + \ self.authors.encode('utf-8') + " at " + self.path.encode('utf-8') + @dynamic_property + def db_id(self): + doc = '''The database id in the application database that this file corresponds to''' + def fget(self): + match = re.search(r'_(\d+)$', self.rpath.rpartition('.')[0]) + if match: + return int(match.group(1)) + return property(fget=fget, doc=doc) class BookList(_BookList): diff --git a/src/calibre/gui2/device.py b/src/calibre/gui2/device.py index 11f7c91a95..b3c01e0119 100644 --- a/src/calibre/gui2/device.py +++ b/src/calibre/gui2/device.py @@ -979,54 +979,62 @@ class DeviceGUI(object): if memory and memory[1]: self.library_view.model().delete_books_by_id(memory[1]) - def book_on_device(self, index, index_is_id=False, format=None): + def book_on_device(self, index, index_is_id=False, format=None, reset=False): loc = [None, None, None] + if reset: + self.book_on_device_cache = None + return + + if self.book_on_device_cache is None: + self.book_on_device_cache = [] + for i, l in enumerate(self.booklists()): + self.book_on_device_cache.append({}) + for book in l: + book_title = book.title.lower() if book.title else '' + book_title = re.sub('(?u)\W|[_]', '', book_title) + if book_title not in self.book_on_device_cache[i]: + self.book_on_device_cache[i][book_title] = \ + {'authors':set(), 'db_ids':set()} + book_authors = authors_to_string(book.authors).lower() + book_authors = re.sub('(?u)\W|[_]', '', book_authors) + self.book_on_device_cache[i][book_title]['authors'].add(book_authors) + self.book_on_device_cache[i][book_title]['db_ids'].add(book.db_id) + db_title = self.library_view.model().db.title(index, index_is_id).lower() db_title = re.sub('(?u)\W|[_]', '', db_title) au = self.library_view.model().db.authors(index, index_is_id) db_authors = au.lower() if au else '' db_authors = re.sub('(?u)\W|[_]', '', db_authors) - for i, l in enumerate(self.booklists()): - for book in l: - book_title = book.title.lower() if book.title else '' - book_title = re.sub('(?u)\W|[_]', '', book_title) - book_authors = authors_to_string(book.authors).lower() - book_authors = re.sub('(?u)\W|[_]', '', book_authors) - if book_title == db_title and book_authors == db_authors: - loc[i] = True - break + d = self.book_on_device_cache[i].get(db_title, None) + if d and (index in d['db_ids'] or db_authors in d['authors']): + loc[i] = True + break return loc - def book_in_library(self, index, oncard=None): + def set_books_in_library(self, booklist): ''' - Used to determine if a book on the device is in the library. - Returns the book's id in the library. + Set the 'in_library' attribute for all books on a device to True if a + book on the device is in the library, else False ''' - bl = [] - if oncard == 'carda': - bl = self.booklists()[1] - elif oncard == 'cardb': - bl = self.booklists()[2] - else: - bl = self.booklists()[0] - - book = bl[index] - book_title = book.title.lower() if book.title else '' - book_title = re.sub('(?u)\W|[_]', '', book_title) - book_authors = authors_to_string(book.authors).lower() if book.authors else '' - book_authors = re.sub('(?u)\W|[_]', '', book_authors) - -# if getattr(book, 'application_id', None) != None and self.library_view.model().db.has_id(book.application_id): -# if book.uuid and self.library_view.model().db.uuid(book.application_id, index_is_id=True) == book.uuid: -# return book.application_id + # First build a cache of the library, so the search isn't On**2 + cache = {} for id, title in self.library_view.model().db.all_titles(): title = re.sub('(?u)\W|[_]', '', title.lower()) - if title == book_title: - au = self.library_view.model().db.authors(id, index_is_id=True) - authors = au.lower() if au else '' - authors = re.sub('(?u)\W|[_]', '', authors) - if authors == book_authors: - return id - return None + au = self.library_view.model().db.authors(id, index_is_id=True) + authors = au.lower() if au else '' + authors = re.sub('(?u)\W|[_]', '', authors) + cache[title+authors] = id + + # Now iterate through all the books on the device, setting the in_library field + for book in booklist: + book_title = book.title.lower() if book.title else '' + book_title = re.sub('(?u)\W|[_]', '', book_title) + book_authors = authors_to_string(book.authors).lower() if book.authors else '' + book_authors = re.sub('(?u)\W|[_]', '', book_authors) + + if cache.get(book_title + book_authors, None) is not None: + book.in_library = True + else: + book.in_library = False diff --git a/src/calibre/gui2/library.py b/src/calibre/gui2/library.py index 074cb3b00e..e133d7a0bd 100644 --- a/src/calibre/gui2/library.py +++ b/src/calibre/gui2/library.py @@ -1268,6 +1268,8 @@ class DeviceBooksModel(BooksModel): def set_book_in_library_func(self, func, loc): self.book_in_library = func self.loc = loc + # Not convinced that this should be here ... + func(self.db) def mark_for_deletion(self, job, rows): self.marked_for_deletion[job] = self.indices(rows) @@ -1356,7 +1358,7 @@ class DeviceBooksModel(BooksModel): x, y = ','.join(self.db[x].tags), ','.join(self.db[y].tags) return cmp(x, y) def libcmp(x, y): - x, y = self.book_in_library(self.map[x], self.loc), self.book_in_library(self.map[y], self.loc) + x, y = self.db[x].in_library, self.db[y].in_library return cmp(x, y) fcmp = strcmp('title_sorter') if col == 0 else strcmp('authors') if col == 1 else \ sizecmp if col == 2 else datecmp if col == 3 else tagscmp if col == 4 else libcmp @@ -1429,7 +1431,7 @@ class DeviceBooksModel(BooksModel): if role == Qt.EditRole: return QVariant(au) authors = string_to_authors(au) - return QVariant("\n".join(authors)) + return QVariant(" & ".join(authors)) elif col == 2: size = self.db[self.map[row]].size return QVariant(BooksView.human_readable(size)) @@ -1442,9 +1444,8 @@ class DeviceBooksModel(BooksModel): if tags: return QVariant(', '.join(tags)) elif col == 5: - if self.book_in_library: - if self.book_in_library(self.map[row], self.loc) != None: - return QVariant(_("True")) + return QVariant(_('Yes')) \ + if self.db[self.map[row]].in_library else QVariant(_('No')) elif role == Qt.TextAlignmentRole and index.column() in [2, 3]: return QVariant(Qt.AlignRight | Qt.AlignVCenter) elif role == Qt.ToolTipRole and index.isValid(): diff --git a/src/calibre/gui2/ui.py b/src/calibre/gui2/ui.py index 6c74763105..df7c246e76 100644 --- a/src/calibre/gui2/ui.py +++ b/src/calibre/gui2/ui.py @@ -520,6 +520,7 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI): if self.system_tray_icon.isVisible() and opts.start_in_tray: self.hide_windows() self.stack.setCurrentIndex(0) + self.book_on_device(None, reset=True) db.set_book_on_device_func(self.book_on_device) self.library_view.set_database(db) self.library_view.model().set_book_on_device_func(self.book_on_device) @@ -993,13 +994,13 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI): return mainlist, cardalist, cardblist = job.result self.memory_view.set_database(mainlist) - self.memory_view.model().set_book_in_library_func(self.book_in_library, 'main') + self.memory_view.model().set_book_in_library_func(self.set_books_in_library, 'main') self.memory_view.set_editable(self.device_manager.device.CAN_SET_METADATA) self.card_a_view.set_database(cardalist) - self.card_a_view.model().set_book_in_library_func(self.book_in_library, 'carda') + self.card_a_view.model().set_book_in_library_func(self.set_books_in_library, 'carda') self.card_a_view.set_editable(self.device_manager.device.CAN_SET_METADATA) self.card_b_view.set_database(cardblist) - self.card_b_view.model().set_book_in_library_func(self.book_in_library, 'cardb') + self.card_b_view.model().set_book_in_library_func(self.set_books_in_library, 'cardb') self.card_b_view.set_editable(self.device_manager.device.CAN_SET_METADATA) for view in (self.memory_view, self.card_a_view, self.card_b_view): view.sortByColumn(3, Qt.DescendingOrder) @@ -1007,6 +1008,10 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI): if not view.restore_column_widths(): view.resizeColumnsToContents() view.resize_on_select = not view.isVisible() + if view.model().rowCount(None) > 1: + view.resizeRowToContents(0) + height = view.rowHeight(0) + view.verticalHeader().setDefaultSectionSize(height) self.sync_news() self.sync_catalogs() self.refresh_ondevice_info() @@ -1014,15 +1019,10 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI): ############################################################################ ### Force the library view to refresh, taking into consideration books information def refresh_ondevice_info(self, clear_flags = False): -# self.library_view.model().db.set_all_ondevice('') -# if not clear_flags: -# for id in self.library_view.model().db: -# self.library_view.model().db.set_book_on_device_string(id, index_is_id=True)) + self.book_on_device(None, reset=True) self.library_view.model().refresh() ############################################################################ - ############################################################################ - ######################### Fetch annotations ################################ def fetch_annotations(self, *args): @@ -2246,6 +2246,7 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI): def library_moved(self, newloc): if newloc is None: return db = LibraryDatabase2(newloc) + self.book_on_device(None, reset=True) db.set_book_on_device_func(self.book_on_device) self.library_view.set_database(db) self.library_view.model().set_book_on_device_func(self.book_on_device) diff --git a/src/calibre/library/caches.py b/src/calibre/library/caches.py index e901613fca..8830d0538a 100644 --- a/src/calibre/library/caches.py +++ b/src/calibre/library/caches.py @@ -370,7 +370,7 @@ class ResultCache(SearchQueryParser): location += 's' all = ('title', 'authors', 'publisher', 'tags', 'comments', 'series', - 'formats', 'isbn', 'rating', 'cover') + 'formats', 'isbn', 'rating', 'cover', 'ondevice') MAP = {} for x in all: # get the db columns for the standard searchables diff --git a/src/calibre/utils/search_query_parser.py b/src/calibre/utils/search_query_parser.py index 79324e6b8b..11991727b7 100644 --- a/src/calibre/utils/search_query_parser.py +++ b/src/calibre/utils/search_query_parser.py @@ -100,6 +100,7 @@ class SearchQueryParser(object): 'search', 'date', 'pubdate', + 'ondevice', 'all', ]