diff --git a/src/calibre/gui2/actions/edit_metadata.py b/src/calibre/gui2/actions/edit_metadata.py index f50251e700..6c2cfb8126 100644 --- a/src/calibre/gui2/actions/edit_metadata.py +++ b/src/calibre/gui2/actions/edit_metadata.py @@ -160,6 +160,7 @@ class EditMetadataAction(InterfaceAction): break changed.add(d.id) + self.gui.library_view.model().refresh_ids(list(d.books_to_refresh)) if d.row_delta == 0: break current_row += d.row_delta diff --git a/src/calibre/gui2/dialogs/metadata_single.py b/src/calibre/gui2/dialogs/metadata_single.py index f36fd3019d..0085d40c2d 100644 --- a/src/calibre/gui2/dialogs/metadata_single.py +++ b/src/calibre/gui2/dialogs/metadata_single.py @@ -622,6 +622,7 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog): self.original_author = unicode(self.authors.text()).strip() self.original_title = unicode(self.title.text()).strip() + self.books_to_refresh = set() self.show() @@ -779,7 +780,8 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog): _('You have changed the tags. In order to use the tags' ' editor, you must either discard or apply these ' 'changes. Apply changes?'), show_copy_button=False): - self.apply_tags(commit=True, notify=True) + self.books_to_refresh |= self.apply_tags(commit=True, notify=True, + allow_case_change=True) self.original_tags = unicode(self.tags.text()) else: self.tags.setText(self.original_tags) @@ -886,9 +888,9 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog): break def apply_tags(self, commit=False, notify=False): - self.db.set_tags(self.id, [x.strip() for x in - unicode(self.tags.text()).split(',')], - notify=notify, commit=commit) + return self.db.set_tags(self.id, [x.strip() for x in + unicode(self.tags.text()).split(',')], + notify=notify, commit=commit, allow_case_change=True) def next_triggered(self, row_delta, *args): self.row_delta = row_delta @@ -907,7 +909,10 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog): self.db.set_title_sort(self.id, ts, notify=False, commit=False) au = unicode(self.authors.text()).strip() if au and au != self.original_author: - self.db.set_authors(self.id, string_to_authors(au), notify=False) + self.books_to_refresh |= self.db.set_authors(self.id, + string_to_authors(au), + notify=False, + allow_case_change=True) aus = unicode(self.author_sort.text()).strip() if aus: self.db.set_author_sort(self.id, aus, notify=False, commit=False) @@ -917,7 +922,7 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog): notify=False, commit=False) self.db.set_rating(self.id, 2*self.rating.value(), notify=False, commit=False) - self.apply_tags() + self.books_to_refresh |= self.apply_tags() self.db.set_publisher(self.id, unicode(self.publisher.currentText()).strip(), notify=False, commit=False) diff --git a/src/calibre/gui2/library/models.py b/src/calibre/gui2/library/models.py index 31b8cf46bf..088b9f6d02 100644 --- a/src/calibre/gui2/library/models.py +++ b/src/calibre/gui2/library/models.py @@ -819,6 +819,7 @@ class BooksModel(QAbstractTableModel): # {{{ value.toDate() if column in ('timestamp', 'pubdate') else \ unicode(value.toString()) id = self.db.id(row) + books_to_refresh = set([id]) if column == 'rating': val = 0 if val < 0 else 5 if val > 5 else val val *= 2 @@ -850,8 +851,9 @@ class BooksModel(QAbstractTableModel): # {{{ return False self.db.set_pubdate(id, qt_to_dt(val, as_utc=False)) else: - self.db.set(row, column, val) - self.refresh_ids([id], row) + books_to_refresh |= self.db.set(row, column, val, + allow_case_change=True) + self.refresh_ids(list(books_to_refresh), row) self.dataChanged.emit(index, index) return True diff --git a/src/calibre/gui2/metadata/basic_widgets.py b/src/calibre/gui2/metadata/basic_widgets.py index 6b89e306e6..d9c450807a 100644 --- a/src/calibre/gui2/metadata/basic_widgets.py +++ b/src/calibre/gui2/metadata/basic_widgets.py @@ -156,6 +156,7 @@ class AuthorsEdit(MultiCompleteComboBox): def __init__(self, parent): self.dialog = parent + self.books_to_refresh = set([]) MultiCompleteComboBox.__init__(self, parent) self.setToolTip(self.TOOLTIP) self.setWhatsThis(self.TOOLTIP) @@ -166,6 +167,7 @@ class AuthorsEdit(MultiCompleteComboBox): return _('Unknown') def initialize(self, db, id_): + self.books_to_refresh = set([]) all_authors = db.all_authors() all_authors.sort(key=lambda x : sort_key(x[1])) for i in all_authors: @@ -185,7 +187,8 @@ class AuthorsEdit(MultiCompleteComboBox): def commit(self, db, id_): authors = self.current_val - db.set_authors(id_, authors, notify=False) + self.books_to_refresh |= db.set_authors(id_, authors, notify=False, + allow_case_change=True) return True @dynamic_property @@ -824,6 +827,7 @@ class TagsEdit(MultiCompleteLineEdit): # {{{ def __init__(self, parent): MultiCompleteLineEdit.__init__(self, parent) + self.books_to_refresh = set([]) self.setToolTip(self.TOOLTIP) self.setWhatsThis(self.TOOLTIP) @@ -838,6 +842,7 @@ class TagsEdit(MultiCompleteLineEdit): # {{{ return property(fget=fget, fset=fset) def initialize(self, db, id_): + self.books_to_refresh = set([]) tags = db.tags(id_, index_is_id=True) tags = tags.split(',') if tags else [] self.current_val = tags @@ -866,7 +871,9 @@ class TagsEdit(MultiCompleteLineEdit): # {{{ def commit(self, db, id_): - db.set_tags(id_, self.current_val, notify=False, commit=False) + self.books_to_refresh |= db.set_tags( + id_, self.current_val, notify=False, commit=False, + allow_case_change=True) return True # }}} diff --git a/src/calibre/gui2/metadata/single.py b/src/calibre/gui2/metadata/single.py index 3b163d84f7..e9d34bd47b 100644 --- a/src/calibre/gui2/metadata/single.py +++ b/src/calibre/gui2/metadata/single.py @@ -31,6 +31,8 @@ class MetadataSingleDialogBase(ResizableDialog): def __init__(self, db, parent=None): self.db = db self.changed = set([]) + self.books_to_refresh = set([]) + self.rows_to_refresh = set([]) ResizableDialog.__init__(self, parent) def setupUi(self, *args): # {{{ @@ -192,6 +194,7 @@ class MetadataSingleDialogBase(ResizableDialog): def __call__(self, id_): self.book_id = id_ + self.books_to_refresh = set([]) for widget in self.basic_metadata_widgets: widget.initialize(self.db, id_) for widget in self.custom_metadata_widgets: @@ -295,6 +298,8 @@ class MetadataSingleDialogBase(ResizableDialog): try: if not widget.commit(self.db, self.book_id): return False + self.books_to_refresh |= getattr(widget, 'books_to_refresh', + set([])) except IOError, err: if err.errno == 13: # Permission denied import traceback @@ -352,6 +357,9 @@ class MetadataSingleDialogBase(ResizableDialog): self.prev_button.setToolTip(tip) self.prev_button.setVisible(prev is not None) self(self.db.id(self.row_list[self.current_row])) + rows = self.db.refresh_ids(list(self.books_to_refresh)) + if rows: + self.rows_to_refresh |= set(rows) def break_cycles(self): # Break any reference cycles that could prevent python @@ -618,7 +626,7 @@ class MetadataSingleDialogAlt(MetadataSingleDialogBase): # {{{ def edit_metadata(db, row_list, current_row, parent=None, view_slot=None): d = MetadataSingleDialog(db, parent) d.start(row_list, current_row, view_slot=view_slot) - return d.changed + return d.changed, d.rows_to_refresh if __name__ == '__main__': from PyQt4.Qt import QApplication diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py index bfe54df36e..95e568bdbe 100644 --- a/src/calibre/library/database2.py +++ b/src/calibre/library/database2.py @@ -1479,17 +1479,19 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): return float(tweaks['series_index_auto_increment']) return 1.0 - def set(self, row, column, val): + def set(self, row, column, val, allow_case_change=False): ''' Convenience method for setting the title, authors, publisher or rating ''' id = self.data[row][0] col = {'title':1, 'authors':2, 'publisher':3, 'rating':4, 'tags':7}[column] + books_to_refresh = set() self.data.set(row, col, val) if column == 'authors': val = string_to_authors(val) - self.set_authors(id, val, notify=False) + books_to_refresh |= self.set_authors(id, val, notify=False, + allow_case_change=allow_case_change) elif column == 'title': self.set_title(id, val, notify=False) elif column == 'publisher': @@ -1497,11 +1499,13 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): elif column == 'rating': self.set_rating(id, val, notify=False) elif column == 'tags': - self.set_tags(id, [x.strip() for x in val.split(',') if x.strip()], - append=False, notify=False) + books_to_refresh |= \ + self.set_tags(id, [x.strip() for x in val.split(',') if x.strip()], + append=False, notify=False, allow_case_change=allow_case_change) self.data.refresh_ids(self, [id]) self.set_path(id, True) self.notify('metadata', [id]) + return books_to_refresh def set_metadata(self, id, mi, ignore_errors=False, set_title=True, set_authors=True, commit=True): @@ -1627,28 +1631,41 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): result.append(r) return ' & '.join(result).replace('|', ',') - def _set_authors(self, id, authors): + def _set_authors(self, id, authors, allow_case_change=False): if not authors: authors = [_('Unknown')] self.conn.execute('DELETE FROM books_authors_link WHERE book=?',(id,)) + books_to_refresh = set() for a in authors: if not a: continue a = a.strip().replace(',', '|') if not isinstance(a, unicode): a = a.decode(preferred_encoding, 'replace') - author = self.conn.get('SELECT id from authors WHERE name=?', (a,), all=False) - if author: - aid = author + aus = self.conn.get('SELECT id, name from authors WHERE name=?', (a,)) + if aus: + author_id, name = aus[0] + else: + author_id, name = (None, None) + if author_id: + aid = author_id # Handle change of case - self.conn.execute('UPDATE authors SET name=? WHERE id=?', (a, aid)) + if allow_case_change and name != a: + self.conn.execute('UPDATE authors SET name=? WHERE id=?', (a, aid)) + case_change = True else: aid = self.conn.execute('INSERT INTO authors(name) VALUES (?)', (a,)).lastrowid + case_change = False try: self.conn.execute('INSERT INTO books_authors_link(book, author) VALUES (?,?)', (id, aid)) except IntegrityError: # Sometimes books specify the same author twice in their metadata pass + if case_change: + bks = self.conn.get('SELECT book FROM books_authors_link WHERE author=?', + (aid,)) + books_to_refresh |= set([bk[0] for bk in bks]) + ss = self.author_sort_from_book(id, index_is_id=True) self.conn.execute('UPDATE books SET author_sort=? WHERE id=?', (ss, id)) @@ -1660,21 +1677,25 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): self.data.set(id, self.FIELD_MAP['au_map'], ':#:'.join([':::'.join((au.replace(',', '|'), aus)) for (au, aus) in aum]), row_is_id=True) + return books_to_refresh - def set_authors(self, id, authors, notify=True, commit=True): + def set_authors(self, id, authors, notify=True, commit=True, + allow_case_change=False): ''' Note that even if commit is False, the db will still be committed to because this causes the location of files to change :param authors: A list of authors. ''' - self._set_authors(id, authors) + books_to_refresh = self._set_authors(id, authors, + allow_case_change=allow_case_change) self.dirtied([id], commit=False) if commit: self.conn.commit() self.set_path(id, index_is_id=True) if notify: self.notify('metadata', [id]) + return books_to_refresh def set_title_sort(self, id, title_sort_, notify=True, commit=True): if not title_sort_: @@ -2119,7 +2140,8 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): def commit(self): self.conn.commit() - def set_tags(self, id, tags, append=False, notify=True, commit=True): + def set_tags(self, id, tags, append=False, notify=True, commit=True, + allow_case_change=False): ''' @param tags: list of strings @param append: If True existing tags are not removed @@ -2129,7 +2151,9 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): self.conn.execute('DELETE FROM tags WHERE (SELECT COUNT(id) FROM books_tags_link WHERE tag=tags.id) < 1') otags = self.get_tags(id) tags = self.cleanup_tags(tags) + books_to_refresh = set() for tag in (set(tags)-otags): + case_changed = False tag = tag.strip() if not tag: continue @@ -2144,8 +2168,9 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): if idx > -1: etag = existing_tags[idx] tid = self.conn.get('SELECT id FROM tags WHERE name=?', (etag,), all=False) - if etag != tag: + if allow_case_change and etag != tag: self.conn.execute('UPDATE tags SET name=? WHERE id=?', (tag, tid)) + case_changed = True else: tid = self.conn.execute('INSERT INTO tags(name) VALUES(?)', (tag,)).lastrowid @@ -2153,6 +2178,10 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): (id, tid), all=False): self.conn.execute('INSERT INTO books_tags_link(book, tag) VALUES (?,?)', (id, tid)) + if case_changed: + bks = self.conn.get('SELECT book FROM books_tags_link WHERE tag=?', + (tid,)) + books_to_refresh |= set([bk[0] for bk in bks]) self.dirtied([id], commit=False) if commit: self.conn.commit() @@ -2160,6 +2189,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): self.data.set(id, self.FIELD_MAP['tags'], tags, row_is_id=True) if notify: self.notify('metadata', [id]) + return books_to_refresh def unapply_tags(self, book_id, tags, notify=True): for tag in tags: