From a61b71ccb1236f62bd0e490a6073aca4482913e9 Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Sun, 9 May 2010 06:57:15 +0100 Subject: [PATCH] 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: