From a61b71ccb1236f62bd0e490a6073aca4482913e9 Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Sun, 9 May 2010 06:57:15 +0100 Subject: [PATCH 1/4] Commit before merge from trunk --- src/calibre/gui2/__init__.py | 2 +- src/calibre/gui2/device.py | 54 +++++++++++++++++++++++++++++++- src/calibre/gui2/library.py | 27 +++++++++++++--- src/calibre/gui2/ui.py | 20 ++++++++++++ src/calibre/library/caches.py | 3 ++ src/calibre/library/database.py | 3 ++ src/calibre/library/database2.py | 23 ++++++++++++++ 7 files changed, 126 insertions(+), 6 deletions(-) diff --git a/src/calibre/gui2/__init__.py b/src/calibre/gui2/__init__.py index 53dc75cc6c..c60acb23fc 100644 --- a/src/calibre/gui2/__init__.py +++ b/src/calibre/gui2/__init__.py @@ -25,7 +25,7 @@ NONE = QVariant() #: Null value to return from the data function of item models UNDEFINED_QDATE = QDate(UNDEFINED_DATE) ALL_COLUMNS = ['title', 'authors', 'size', 'timestamp', 'rating', 'publisher', - 'tags', 'series', 'pubdate'] + 'tags', 'series', 'pubdate', 'ondevice'] def _config(): c = Config('gui', 'preferences for the calibre GUI') diff --git a/src/calibre/gui2/device.py b/src/calibre/gui2/device.py index b87a5e451b..11f7c91a95 100644 --- a/src/calibre/gui2/device.py +++ b/src/calibre/gui2/device.py @@ -1,7 +1,7 @@ from __future__ import with_statement __license__ = 'GPL v3' __copyright__ = '2008, Kovid Goyal ' -import os, traceback, Queue, time, socket, cStringIO +import os, traceback, Queue, time, socket, cStringIO, re from threading import Thread, RLock from itertools import repeat from functools import partial @@ -978,3 +978,55 @@ class DeviceGUI(object): getattr(f, 'close', lambda : True)() 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): + loc = [None, None, None] + + 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 + return loc + + def book_in_library(self, index, oncard=None): + ''' + Used to determine if a book on the device is in the library. + Returns the book's id in the library. + ''' + 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 + 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 diff --git a/src/calibre/gui2/library.py b/src/calibre/gui2/library.py index ba9a5b0b29..074cb3b00e 100644 --- a/src/calibre/gui2/library.py +++ b/src/calibre/gui2/library.py @@ -316,11 +316,13 @@ class BooksModel(QAbstractTableModel): 'publisher' : _("Publisher"), 'tags' : _("Tags"), 'series' : _("Series"), + 'ondevice' : _("On Device"), } def __init__(self, parent=None, buffer=40): QAbstractTableModel.__init__(self, parent) self.db = None + self.book_on_device = None self.editable_cols = ['title', 'authors', 'rating', 'publisher', 'tags', 'series', 'timestamp', 'pubdate'] self.default_image = QImage(I('book.svg')) @@ -359,6 +361,9 @@ class BooksModel(QAbstractTableModel): self.reset() self.emit(SIGNAL('columns_sorted()')) + def set_book_on_device_func(self, func): + self.book_on_device = func + def set_database(self, db): self.db = db self.custom_columns = self.db.custom_column_label_map @@ -799,6 +804,8 @@ class BooksModel(QAbstractTableModel): 'series' : functools.partial(series, idx=self.db.FIELD_MAP['series'], siix=self.db.FIELD_MAP['series_index']), + 'ondevice' : functools.partial(text_type, + idx=self.db.FIELD_MAP['ondevice'], mult=False), } self.dc_decorator = {} @@ -1255,6 +1262,12 @@ class DeviceBooksModel(BooksModel): self.marked_for_deletion = {} self.search_engine = OnDeviceSearch(self) self.editable = True + self.book_in_library = None + self.loc = None + + def set_book_in_library_func(self, func, loc): + self.book_in_library = func + self.loc = loc def mark_for_deletion(self, job, rows): self.marked_for_deletion[job] = self.indices(rows) @@ -1342,8 +1355,11 @@ class DeviceBooksModel(BooksModel): def tagscmp(x, y): 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) + 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 + sizecmp if col == 2 else datecmp if col == 3 else tagscmp if col == 4 else libcmp self.map.sort(cmp=fcmp, reverse=descending) if len(self.map) == len(self.db): self.sorted_map = list(self.map) @@ -1357,7 +1373,7 @@ class DeviceBooksModel(BooksModel): def columnCount(self, parent): if parent and parent.isValid(): return 0 - return 5 + return 6 def rowCount(self, parent): if parent and parent.isValid(): @@ -1398,7 +1414,6 @@ class DeviceBooksModel(BooksModel): ''' return [ self.map[r.row()] for r in rows] - def data(self, index, role): if role == Qt.DisplayRole or role == Qt.EditRole: row, col = index.row(), index.column() @@ -1426,6 +1441,10 @@ class DeviceBooksModel(BooksModel): tags = self.db[self.map[row]].tags 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")) elif role == Qt.TextAlignmentRole and index.column() in [2, 3]: return QVariant(Qt.AlignRight | Qt.AlignVCenter) elif role == Qt.ToolTipRole and index.isValid(): @@ -1446,6 +1465,7 @@ class DeviceBooksModel(BooksModel): elif section == 2: text = _("Size (MB)") elif section == 3: text = _("Date") elif section == 4: text = _("Tags") + elif section == 5: text = _("In Library") return QVariant(text) else: return QVariant(section+1) @@ -1479,4 +1499,3 @@ class DeviceBooksModel(BooksModel): def set_search_restriction(self, s): pass - diff --git a/src/calibre/gui2/ui.py b/src/calibre/gui2/ui.py index 27b19427ae..87c03f7ec1 100644 --- a/src/calibre/gui2/ui.py +++ b/src/calibre/gui2/ui.py @@ -543,7 +543,9 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI): else: self.library_path = dir db = LibraryDatabase2(self.library_path) + 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) prefs['library_path'] = self.library_path self.library_view.restore_sort_at_startup(dynamic.get('sort_history', [('timestamp', Qt.DescendingOrder)])) if not self.library_view.restore_column_widths(): @@ -978,6 +980,7 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI): self.status_bar.reset_info() self.location_view.setCurrentIndex(self.location_view.model().index(0)) self.eject_action.setEnabled(False) + self.refresh_ondevice_info (clear_info = True) def info_read(self, job): ''' @@ -1012,10 +1015,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.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.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.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) @@ -1025,6 +1031,18 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI): view.resize_on_select = not view.isVisible() self.sync_news() self.sync_catalogs() + self.refresh_ondevice_info() + + ############################################################################ + ### 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.library_view.model().refresh() + ############################################################################ + ############################################################################ ######################### Fetch annotations ################################ @@ -2250,7 +2268,9 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI): def library_moved(self, newloc): if newloc is None: return db = LibraryDatabase2(newloc) + 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) self.status_bar.clearMessage() self.search.clear_to_help() self.status_bar.reset_info() diff --git a/src/calibre/library/caches.py b/src/calibre/library/caches.py index 2e7c7c4ca8..e901613fca 100644 --- a/src/calibre/library/caches.py +++ b/src/calibre/library/caches.py @@ -518,6 +518,7 @@ class ResultCache(SearchQueryParser): try: self._data[id] = db.conn.get('SELECT * from meta2 WHERE id=?', (id,))[0] self._data[id].append(db.has_cover(id, index_is_id=True)) + self._data[id].append(db.book_on_device_string(id, index_is_id=True)) except IndexError: return None try: @@ -533,6 +534,7 @@ class ResultCache(SearchQueryParser): for id in ids: self._data[id] = db.conn.get('SELECT * from meta2 WHERE id=?', (id,))[0] self._data[id].append(db.has_cover(id, index_is_id=True)) + self._data[id].append(db.book_on_device_string(id, index_is_id=True)) self._map[0:0] = ids self._map_filtered[0:0] = ids @@ -553,6 +555,7 @@ class ResultCache(SearchQueryParser): for item in self._data: if item is not None: item.append(db.has_cover(item[0], index_is_id=True)) + item.append(db.book_on_device_string(item[0], index_is_id=True)) self._map = [i[0] for i in self._data if i is not None] if field is not None: self.sort(field, ascending) diff --git a/src/calibre/library/database.py b/src/calibre/library/database.py index cfd2213eed..6147101567 100644 --- a/src/calibre/library/database.py +++ b/src/calibre/library/database.py @@ -1070,6 +1070,9 @@ ALTER TABLE books ADD COLUMN isbn TEXT DEFAULT "" COLLATE NOCASE; return [ (i[0], i[1]) for i in \ self.conn.get('SELECT id, name FROM tags')] + def all_titles(self): + return [ (i[0], i[1]) for i in \ + self.conn.get('SELECT id, title FROM books')] def conversion_options(self, id, format): data = self.conn.get('SELECT data FROM conversion_options WHERE book=? AND format=?', (id, format.upper()), all=False) diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py index 724c1bd41a..6a29b0f8d8 100644 --- a/src/calibre/library/database2.py +++ b/src/calibre/library/database2.py @@ -219,6 +219,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): self.FIELD_MAP[col] = base = base+1 self.FIELD_MAP['cover'] = base+1 + self.FIELD_MAP['ondevice'] = base+2 script = ''' DROP VIEW IF EXISTS meta2; @@ -230,6 +231,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): self.conn.executescript(script) self.conn.commit() + self.book_on_device_func = None self.data = ResultCache(self.FIELD_MAP, self.custom_column_label_map) self.search = self.data.search self.refresh = functools.partial(self.data.refresh, self) @@ -465,6 +467,27 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): im = PILImage.open(f) im.convert('RGB').save(path, 'JPEG') + def book_on_device(self, index, index_is_id=False): + if self.book_on_device_func: + return self.book_on_device_func(index, index_is_id) + return None + + def book_on_device_string(self, index, index_is_id=False): + loc = [] + on = self.book_on_device(index, index_is_id) + if on is not None: + m, a, b = 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) + + def set_book_on_device_func(self, func): + self.book_on_device_func = func + def all_formats(self): formats = self.conn.get('SELECT DISTINCT format from data') if not formats: From afadcc7a1be11eedaf2e64cd2b1118559e69ec5f Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Sun, 9 May 2010 10:38:30 +0100 Subject: [PATCH 2/4] Ondevice works with Sony PRS-300 --- src/calibre/devices/usbms/books.py | 8 +++ src/calibre/gui2/device.py | 82 +++++++++++++----------- src/calibre/gui2/library.py | 11 ++-- src/calibre/gui2/ui.py | 19 +++--- src/calibre/library/caches.py | 2 +- src/calibre/utils/search_query_parser.py | 1 + 6 files changed, 71 insertions(+), 52 deletions(-) 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', ] From 27635f58f9897369818e81b348d1210044372c7e Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Sun, 9 May 2010 19:21:50 +0100 Subject: [PATCH 3/4] Moved db_id addition to USBMS. Added db_ids to Jetbook and Kindle overrides. Refactored index_is_id out, since it was always True. Implemented db_id lookup in the on_library cache. --- src/calibre/devices/jetbook/driver.py | 8 +++++++- src/calibre/devices/kindle/driver.py | 5 +++++ src/calibre/devices/prs505/driver.py | 8 -------- src/calibre/devices/usbms/device.py | 8 +++++++- src/calibre/gui2/device.py | 28 ++++++++++++++++----------- src/calibre/library/caches.py | 6 +++--- src/calibre/library/database2.py | 8 ++++---- 7 files changed, 43 insertions(+), 28 deletions(-) diff --git a/src/calibre/devices/jetbook/driver.py b/src/calibre/devices/jetbook/driver.py index e4fd840dc0..71b825f5d8 100644 --- a/src/calibre/devices/jetbook/driver.py +++ b/src/calibre/devices/jetbook/driver.py @@ -55,7 +55,13 @@ class JETBOOK(USBMS): au = mi.format_authors() if not au: au = 'Unknown' - return '%s#%s%s' % (au, title, fileext) + suffix = '' + if getattr(mi, 'application_id', None) is not None: + base = fname.rpartition('.')[0] + suffix = '_%s'%mi.application_id + if base.endswith(suffix): + suffix = '' + return '%s#%s%s%s' % (au, title, fileext, suffix) @classmethod def metadata_from_path(cls, path): diff --git a/src/calibre/devices/kindle/driver.py b/src/calibre/devices/kindle/driver.py index 83eae78de0..c3ae0ef868 100644 --- a/src/calibre/devices/kindle/driver.py +++ b/src/calibre/devices/kindle/driver.py @@ -61,6 +61,11 @@ class KINDLE(USBMS): return mi def filename_callback(self, fname, mi): + if getattr(mi, 'application_id', None) is not None: + base = fname.rpartition('.')[0] + suffix = '_%s'%mi.application_id + if not base.endswith(suffix): + fname = base + suffix + '.' + fname.rpartition('.')[-1] if fname.startswith('.'): return 'x'+fname[1:] return fname diff --git a/src/calibre/devices/prs505/driver.py b/src/calibre/devices/prs505/driver.py index f4fc4b0d29..e73a341909 100644 --- a/src/calibre/devices/prs505/driver.py +++ b/src/calibre/devices/prs505/driver.py @@ -121,14 +121,6 @@ class PRS505(CLI, Device): self.report_progress(1.0, _('Getting list of books on device...')) return bl - def filename_callback(self, fname, mi): - if getattr(mi, 'application_id', None) is not None: - base = fname.rpartition('.')[0] - suffix = '_%s'%mi.application_id - if not base.endswith(suffix): - fname = base + suffix + '.' + fname.rpartition('.')[-1] - return fname - def upload_books(self, files, names, on_card=None, end_session=True, metadata=None): diff --git a/src/calibre/devices/usbms/device.py b/src/calibre/devices/usbms/device.py index 897baf82ca..ce6a17d731 100644 --- a/src/calibre/devices/usbms/device.py +++ b/src/calibre/devices/usbms/device.py @@ -784,8 +784,14 @@ class Device(DeviceConfig, DevicePlugin): def filename_callback(self, default, mi): ''' Callback to allow drivers to change the default file name - set by :method:`create_upload_path`. + set by :method:`create_upload_path`. By default, add the DB_ID + to the end of the string. Helps with ondevice doc matching ''' + if getattr(mi, 'application_id', None) is not None: + base = default.rpartition('.')[0] + suffix = '_%s'%mi.application_id + if not base.endswith(suffix): + default = base + suffix + '.' + default.rpartition('.')[-1] return default def sanitize_path_components(self, components): diff --git a/src/calibre/gui2/device.py b/src/calibre/gui2/device.py index b3c01e0119..ab98470a22 100644 --- a/src/calibre/gui2/device.py +++ b/src/calibre/gui2/device.py @@ -979,7 +979,7 @@ 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, reset=False): + def book_on_device(self, index, format=None, reset=False): loc = [None, None, None] if reset: @@ -1001,9 +1001,9 @@ class DeviceGUI(object): 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 = self.library_view.model().db.title(index, index_is_id=True).lower() db_title = re.sub('(?u)\W|[_]', '', db_title) - au = self.library_view.model().db.authors(index, index_is_id) + au = self.library_view.model().db.authors(index, index_is_id=True) db_authors = au.lower() if au else '' db_authors = re.sub('(?u)\W|[_]', '', db_authors) for i, l in enumerate(self.booklists()): @@ -1022,19 +1022,25 @@ class DeviceGUI(object): cache = {} for id, title in self.library_view.model().db.all_titles(): title = re.sub('(?u)\W|[_]', '', title.lower()) + if title not in cache: + cache[title] = {'authors':set(), 'db_ids':set()} 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 + cache[title]['authors'].add(authors) + cache[title]['db_ids'].add(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 + book.in_library = False + d = cache.get(book_title, None) + if d is not None: + if book.db_id in d['db_ids']: + book.in_library = True + continue + book_authors = authors_to_string(book.authors).lower() if book.authors else '' + book_authors = re.sub('(?u)\W|[_]', '', book_authors) + if book_authors in d['authors']: + book.in_library = True diff --git a/src/calibre/library/caches.py b/src/calibre/library/caches.py index 8830d0538a..9ed150733a 100644 --- a/src/calibre/library/caches.py +++ b/src/calibre/library/caches.py @@ -518,7 +518,7 @@ class ResultCache(SearchQueryParser): try: self._data[id] = db.conn.get('SELECT * from meta2 WHERE id=?', (id,))[0] self._data[id].append(db.has_cover(id, index_is_id=True)) - self._data[id].append(db.book_on_device_string(id, index_is_id=True)) + self._data[id].append(db.book_on_device_string(id)) except IndexError: return None try: @@ -534,7 +534,7 @@ class ResultCache(SearchQueryParser): for id in ids: self._data[id] = db.conn.get('SELECT * from meta2 WHERE id=?', (id,))[0] self._data[id].append(db.has_cover(id, index_is_id=True)) - self._data[id].append(db.book_on_device_string(id, index_is_id=True)) + self._data[id].append(db.book_on_device_string(id)) self._map[0:0] = ids self._map_filtered[0:0] = ids @@ -555,7 +555,7 @@ class ResultCache(SearchQueryParser): for item in self._data: if item is not None: item.append(db.has_cover(item[0], index_is_id=True)) - item.append(db.book_on_device_string(item[0], index_is_id=True)) + item.append(db.book_on_device_string(item[0])) self._map = [i[0] for i in self._data if i is not None] if field is not None: self.sort(field, ascending) diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py index 4bc96d2c20..a50c840930 100644 --- a/src/calibre/library/database2.py +++ b/src/calibre/library/database2.py @@ -470,14 +470,14 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): im = PILImage.open(f) im.convert('RGB').save(path, 'JPEG') - def book_on_device(self, index, index_is_id=False): + def book_on_device(self, index): if self.book_on_device_func: - return self.book_on_device_func(index, index_is_id) + return self.book_on_device_func(index) return None - def book_on_device_string(self, index, index_is_id=False): + def book_on_device_string(self, index): loc = [] - on = self.book_on_device(index, index_is_id) + on = self.book_on_device(index) if on is not None: m, a, b = on if m is not None: From 660668f718d0868fd175fecf3e273408e2e3db21 Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Sun, 9 May 2010 19:37:38 +0100 Subject: [PATCH 4/4] Refactor books_in_library to get rid of call back that wasn't used, and also to build library titles cache once. --- src/calibre/gui2/device.py | 24 ++++++++++++------------ src/calibre/gui2/library.py | 7 ------- src/calibre/gui2/ui.py | 7 ++++--- 3 files changed, 16 insertions(+), 22 deletions(-) diff --git a/src/calibre/gui2/device.py b/src/calibre/gui2/device.py index ab98470a22..855d05ff58 100644 --- a/src/calibre/gui2/device.py +++ b/src/calibre/gui2/device.py @@ -1013,29 +1013,29 @@ class DeviceGUI(object): break return loc - def set_books_in_library(self, booklist): - ''' - 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 - ''' - # First build a cache of the library, so the search isn't On**2 - cache = {} + def set_books_in_library(self, booklist, reset = False): + if reset: + self.book_in_library_cache = None + return + + # First build a self.book_in_library_cache of the library, so the search isn't On**2 + self.book_in_library_cache = {} for id, title in self.library_view.model().db.all_titles(): title = re.sub('(?u)\W|[_]', '', title.lower()) - if title not in cache: - cache[title] = {'authors':set(), 'db_ids':set()} + if title not in self.book_in_library_cache: + self.book_in_library_cache[title] = {'authors':set(), 'db_ids':set()} 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'].add(authors) - cache[title]['db_ids'].add(id) + self.book_in_library_cache[title]['authors'].add(authors) + self.book_in_library_cache[title]['db_ids'].add(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.in_library = False - d = cache.get(book_title, None) + d = self.book_in_library_cache.get(book_title, None) if d is not None: if book.db_id in d['db_ids']: book.in_library = True diff --git a/src/calibre/gui2/library.py b/src/calibre/gui2/library.py index 5d4686b4d2..90dc3eb1ea 100644 --- a/src/calibre/gui2/library.py +++ b/src/calibre/gui2/library.py @@ -1266,13 +1266,6 @@ class DeviceBooksModel(BooksModel): self.search_engine = OnDeviceSearch(self) self.editable = True self.book_in_library = None - self.loc = None - - 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) diff --git a/src/calibre/gui2/ui.py b/src/calibre/gui2/ui.py index df7c246e76..4f5e71174c 100644 --- a/src/calibre/gui2/ui.py +++ b/src/calibre/gui2/ui.py @@ -992,15 +992,16 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI): else: self.device_job_exception(job) return + self.set_books_in_library(None, reset=True) mainlist, cardalist, cardblist = job.result self.memory_view.set_database(mainlist) - self.memory_view.model().set_book_in_library_func(self.set_books_in_library, 'main') + self.set_books_in_library(mainlist) 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.set_books_in_library, 'carda') + self.set_books_in_library(cardalist) 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.set_books_in_library, 'cardb') + self.set_books_in_library(cardblist) 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)