Allow changing the case of authors and tags via the edit metadata dialog and the book list

This commit is contained in:
Kovid Goyal 2011-02-02 17:56:15 -07:00
commit f62451d918
6 changed files with 77 additions and 24 deletions

View File

@ -160,6 +160,7 @@ class EditMetadataAction(InterfaceAction):
break break
changed.add(d.id) changed.add(d.id)
self.gui.library_view.model().refresh_ids(list(d.books_to_refresh))
if d.row_delta == 0: if d.row_delta == 0:
break break
current_row += d.row_delta current_row += d.row_delta

View File

@ -622,6 +622,7 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog):
self.original_author = unicode(self.authors.text()).strip() self.original_author = unicode(self.authors.text()).strip()
self.original_title = unicode(self.title.text()).strip() self.original_title = unicode(self.title.text()).strip()
self.books_to_refresh = set()
self.show() self.show()
@ -779,7 +780,8 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog):
_('You have changed the tags. In order to use the tags' _('You have changed the tags. In order to use the tags'
' editor, you must either discard or apply these ' ' editor, you must either discard or apply these '
'changes. Apply changes?'), show_copy_button=False): '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()) self.original_tags = unicode(self.tags.text())
else: else:
self.tags.setText(self.original_tags) self.tags.setText(self.original_tags)
@ -886,9 +888,9 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog):
break break
def apply_tags(self, commit=False, notify=False): def apply_tags(self, commit=False, notify=False):
self.db.set_tags(self.id, [x.strip() for x in return self.db.set_tags(self.id, [x.strip() for x in
unicode(self.tags.text()).split(',')], unicode(self.tags.text()).split(',')],
notify=notify, commit=commit) notify=notify, commit=commit, allow_case_change=True)
def next_triggered(self, row_delta, *args): def next_triggered(self, row_delta, *args):
self.row_delta = row_delta 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) self.db.set_title_sort(self.id, ts, notify=False, commit=False)
au = unicode(self.authors.text()).strip() au = unicode(self.authors.text()).strip()
if au and au != self.original_author: 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() aus = unicode(self.author_sort.text()).strip()
if aus: if aus:
self.db.set_author_sort(self.id, aus, notify=False, commit=False) 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) notify=False, commit=False)
self.db.set_rating(self.id, 2*self.rating.value(), notify=False, self.db.set_rating(self.id, 2*self.rating.value(), notify=False,
commit=False) commit=False)
self.apply_tags() self.books_to_refresh |= self.apply_tags()
self.db.set_publisher(self.id, self.db.set_publisher(self.id,
unicode(self.publisher.currentText()).strip(), unicode(self.publisher.currentText()).strip(),
notify=False, commit=False) notify=False, commit=False)

View File

@ -819,6 +819,7 @@ class BooksModel(QAbstractTableModel): # {{{
value.toDate() if column in ('timestamp', 'pubdate') else \ value.toDate() if column in ('timestamp', 'pubdate') else \
unicode(value.toString()) unicode(value.toString())
id = self.db.id(row) id = self.db.id(row)
books_to_refresh = set([id])
if column == 'rating': if column == 'rating':
val = 0 if val < 0 else 5 if val > 5 else val val = 0 if val < 0 else 5 if val > 5 else val
val *= 2 val *= 2
@ -850,8 +851,9 @@ class BooksModel(QAbstractTableModel): # {{{
return False return False
self.db.set_pubdate(id, qt_to_dt(val, as_utc=False)) self.db.set_pubdate(id, qt_to_dt(val, as_utc=False))
else: else:
self.db.set(row, column, val) books_to_refresh |= self.db.set(row, column, val,
self.refresh_ids([id], row) allow_case_change=True)
self.refresh_ids(list(books_to_refresh), row)
self.dataChanged.emit(index, index) self.dataChanged.emit(index, index)
return True return True

View File

@ -156,6 +156,7 @@ class AuthorsEdit(MultiCompleteComboBox):
def __init__(self, parent): def __init__(self, parent):
self.dialog = parent self.dialog = parent
self.books_to_refresh = set([])
MultiCompleteComboBox.__init__(self, parent) MultiCompleteComboBox.__init__(self, parent)
self.setToolTip(self.TOOLTIP) self.setToolTip(self.TOOLTIP)
self.setWhatsThis(self.TOOLTIP) self.setWhatsThis(self.TOOLTIP)
@ -166,6 +167,7 @@ class AuthorsEdit(MultiCompleteComboBox):
return _('Unknown') return _('Unknown')
def initialize(self, db, id_): def initialize(self, db, id_):
self.books_to_refresh = set([])
all_authors = db.all_authors() all_authors = db.all_authors()
all_authors.sort(key=lambda x : sort_key(x[1])) all_authors.sort(key=lambda x : sort_key(x[1]))
for i in all_authors: for i in all_authors:
@ -185,7 +187,8 @@ class AuthorsEdit(MultiCompleteComboBox):
def commit(self, db, id_): def commit(self, db, id_):
authors = self.current_val 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 return True
@dynamic_property @dynamic_property
@ -824,6 +827,7 @@ class TagsEdit(MultiCompleteLineEdit): # {{{
def __init__(self, parent): def __init__(self, parent):
MultiCompleteLineEdit.__init__(self, parent) MultiCompleteLineEdit.__init__(self, parent)
self.books_to_refresh = set([])
self.setToolTip(self.TOOLTIP) self.setToolTip(self.TOOLTIP)
self.setWhatsThis(self.TOOLTIP) self.setWhatsThis(self.TOOLTIP)
@ -838,6 +842,7 @@ class TagsEdit(MultiCompleteLineEdit): # {{{
return property(fget=fget, fset=fset) return property(fget=fget, fset=fset)
def initialize(self, db, id_): def initialize(self, db, id_):
self.books_to_refresh = set([])
tags = db.tags(id_, index_is_id=True) tags = db.tags(id_, index_is_id=True)
tags = tags.split(',') if tags else [] tags = tags.split(',') if tags else []
self.current_val = tags self.current_val = tags
@ -866,7 +871,9 @@ class TagsEdit(MultiCompleteLineEdit): # {{{
def commit(self, db, id_): 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 return True
# }}} # }}}

View File

@ -31,6 +31,8 @@ class MetadataSingleDialogBase(ResizableDialog):
def __init__(self, db, parent=None): def __init__(self, db, parent=None):
self.db = db self.db = db
self.changed = set([]) self.changed = set([])
self.books_to_refresh = set([])
self.rows_to_refresh = set([])
ResizableDialog.__init__(self, parent) ResizableDialog.__init__(self, parent)
def setupUi(self, *args): # {{{ def setupUi(self, *args): # {{{
@ -192,6 +194,7 @@ class MetadataSingleDialogBase(ResizableDialog):
def __call__(self, id_): def __call__(self, id_):
self.book_id = id_ self.book_id = id_
self.books_to_refresh = set([])
for widget in self.basic_metadata_widgets: for widget in self.basic_metadata_widgets:
widget.initialize(self.db, id_) widget.initialize(self.db, id_)
for widget in self.custom_metadata_widgets: for widget in self.custom_metadata_widgets:
@ -295,6 +298,8 @@ class MetadataSingleDialogBase(ResizableDialog):
try: try:
if not widget.commit(self.db, self.book_id): if not widget.commit(self.db, self.book_id):
return False return False
self.books_to_refresh |= getattr(widget, 'books_to_refresh',
set([]))
except IOError, err: except IOError, err:
if err.errno == 13: # Permission denied if err.errno == 13: # Permission denied
import traceback import traceback
@ -352,6 +357,9 @@ class MetadataSingleDialogBase(ResizableDialog):
self.prev_button.setToolTip(tip) self.prev_button.setToolTip(tip)
self.prev_button.setVisible(prev is not None) self.prev_button.setVisible(prev is not None)
self(self.db.id(self.row_list[self.current_row])) 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): def break_cycles(self):
# Break any reference cycles that could prevent python # 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): def edit_metadata(db, row_list, current_row, parent=None, view_slot=None):
d = MetadataSingleDialog(db, parent) d = MetadataSingleDialog(db, parent)
d.start(row_list, current_row, view_slot=view_slot) d.start(row_list, current_row, view_slot=view_slot)
return d.changed return d.changed, d.rows_to_refresh
if __name__ == '__main__': if __name__ == '__main__':
from PyQt4.Qt import QApplication from PyQt4.Qt import QApplication

View File

@ -1479,17 +1479,19 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
return float(tweaks['series_index_auto_increment']) return float(tweaks['series_index_auto_increment'])
return 1.0 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 Convenience method for setting the title, authors, publisher or rating
''' '''
id = self.data[row][0] id = self.data[row][0]
col = {'title':1, 'authors':2, 'publisher':3, 'rating':4, 'tags':7}[column] col = {'title':1, 'authors':2, 'publisher':3, 'rating':4, 'tags':7}[column]
books_to_refresh = set()
self.data.set(row, col, val) self.data.set(row, col, val)
if column == 'authors': if column == 'authors':
val = string_to_authors(val) 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': elif column == 'title':
self.set_title(id, val, notify=False) self.set_title(id, val, notify=False)
elif column == 'publisher': elif column == 'publisher':
@ -1497,11 +1499,13 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
elif column == 'rating': elif column == 'rating':
self.set_rating(id, val, notify=False) self.set_rating(id, val, notify=False)
elif column == 'tags': elif column == 'tags':
self.set_tags(id, [x.strip() for x in val.split(',') if x.strip()], books_to_refresh |= \
append=False, notify=False) 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.data.refresh_ids(self, [id])
self.set_path(id, True) self.set_path(id, True)
self.notify('metadata', [id]) self.notify('metadata', [id])
return books_to_refresh
def set_metadata(self, id, mi, ignore_errors=False, def set_metadata(self, id, mi, ignore_errors=False,
set_title=True, set_authors=True, commit=True): set_title=True, set_authors=True, commit=True):
@ -1627,28 +1631,41 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
result.append(r) result.append(r)
return ' & '.join(result).replace('|', ',') return ' & '.join(result).replace('|', ',')
def _set_authors(self, id, authors): def _set_authors(self, id, authors, allow_case_change=False):
if not authors: if not authors:
authors = [_('Unknown')] authors = [_('Unknown')]
self.conn.execute('DELETE FROM books_authors_link WHERE book=?',(id,)) self.conn.execute('DELETE FROM books_authors_link WHERE book=?',(id,))
books_to_refresh = set()
for a in authors: for a in authors:
if not a: if not a:
continue continue
a = a.strip().replace(',', '|') a = a.strip().replace(',', '|')
if not isinstance(a, unicode): if not isinstance(a, unicode):
a = a.decode(preferred_encoding, 'replace') a = a.decode(preferred_encoding, 'replace')
author = self.conn.get('SELECT id from authors WHERE name=?', (a,), all=False) aus = self.conn.get('SELECT id, name from authors WHERE name=?', (a,))
if author: if aus:
aid = author author_id, name = aus[0]
else:
author_id, name = (None, None)
if author_id:
aid = author_id
# Handle change of case # 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: else:
aid = self.conn.execute('INSERT INTO authors(name) VALUES (?)', (a,)).lastrowid aid = self.conn.execute('INSERT INTO authors(name) VALUES (?)', (a,)).lastrowid
case_change = False
try: try:
self.conn.execute('INSERT INTO books_authors_link(book, author) VALUES (?,?)', self.conn.execute('INSERT INTO books_authors_link(book, author) VALUES (?,?)',
(id, aid)) (id, aid))
except IntegrityError: # Sometimes books specify the same author twice in their metadata except IntegrityError: # Sometimes books specify the same author twice in their metadata
pass 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) ss = self.author_sort_from_book(id, index_is_id=True)
self.conn.execute('UPDATE books SET author_sort=? WHERE id=?', self.conn.execute('UPDATE books SET author_sort=? WHERE id=?',
(ss, id)) (ss, id))
@ -1660,21 +1677,25 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
self.data.set(id, self.FIELD_MAP['au_map'], self.data.set(id, self.FIELD_MAP['au_map'],
':#:'.join([':::'.join((au.replace(',', '|'), aus)) for (au, aus) in aum]), ':#:'.join([':::'.join((au.replace(',', '|'), aus)) for (au, aus) in aum]),
row_is_id=True) 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 Note that even if commit is False, the db will still be committed to
because this causes the location of files to change because this causes the location of files to change
:param authors: A list of authors. :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) self.dirtied([id], commit=False)
if commit: if commit:
self.conn.commit() self.conn.commit()
self.set_path(id, index_is_id=True) self.set_path(id, index_is_id=True)
if notify: if notify:
self.notify('metadata', [id]) self.notify('metadata', [id])
return books_to_refresh
def set_title_sort(self, id, title_sort_, notify=True, commit=True): def set_title_sort(self, id, title_sort_, notify=True, commit=True):
if not title_sort_: if not title_sort_:
@ -2119,7 +2140,8 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
def commit(self): def commit(self):
self.conn.commit() 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 tags: list of strings
@param append: If True existing tags are not removed @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') self.conn.execute('DELETE FROM tags WHERE (SELECT COUNT(id) FROM books_tags_link WHERE tag=tags.id) < 1')
otags = self.get_tags(id) otags = self.get_tags(id)
tags = self.cleanup_tags(tags) tags = self.cleanup_tags(tags)
books_to_refresh = set()
for tag in (set(tags)-otags): for tag in (set(tags)-otags):
case_changed = False
tag = tag.strip() tag = tag.strip()
if not tag: if not tag:
continue continue
@ -2144,8 +2168,9 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
if idx > -1: if idx > -1:
etag = existing_tags[idx] etag = existing_tags[idx]
tid = self.conn.get('SELECT id FROM tags WHERE name=?', (etag,), all=False) 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)) self.conn.execute('UPDATE tags SET name=? WHERE id=?', (tag, tid))
case_changed = True
else: else:
tid = self.conn.execute('INSERT INTO tags(name) VALUES(?)', (tag,)).lastrowid tid = self.conn.execute('INSERT INTO tags(name) VALUES(?)', (tag,)).lastrowid
@ -2153,6 +2178,10 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
(id, tid), all=False): (id, tid), all=False):
self.conn.execute('INSERT INTO books_tags_link(book, tag) VALUES (?,?)', self.conn.execute('INSERT INTO books_tags_link(book, tag) VALUES (?,?)',
(id, tid)) (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) self.dirtied([id], commit=False)
if commit: if commit:
self.conn.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) self.data.set(id, self.FIELD_MAP['tags'], tags, row_is_id=True)
if notify: if notify:
self.notify('metadata', [id]) self.notify('metadata', [id])
return books_to_refresh
def unapply_tags(self, book_id, tags, notify=True): def unapply_tags(self, book_id, tags, notify=True):
for tag in tags: for tag in tags: