diff --git a/src/calibre/ebooks/metadata/book/__init__.py b/src/calibre/ebooks/metadata/book/__init__.py index fae858aabd..50e7b916ee 100644 --- a/src/calibre/ebooks/metadata/book/__init__.py +++ b/src/calibre/ebooks/metadata/book/__init__.py @@ -86,6 +86,8 @@ CALIBRE_METADATA_FIELDS = frozenset([ # a dict of user category names, where the value is a list of item names # from the book that are in that category 'user_categories', + # a dict of author to an associated hyperlink + 'author_link_map', ] ) diff --git a/src/calibre/ebooks/metadata/book/base.py b/src/calibre/ebooks/metadata/book/base.py index 382cb6c5a2..c28c65f7c9 100644 --- a/src/calibre/ebooks/metadata/book/base.py +++ b/src/calibre/ebooks/metadata/book/base.py @@ -34,6 +34,7 @@ NULL_VALUES = { 'authors' : [_('Unknown')], 'title' : _('Unknown'), 'user_categories' : {}, + 'author_link_map' : {}, 'language' : 'und' } diff --git a/src/calibre/ebooks/metadata/opf2.py b/src/calibre/ebooks/metadata/opf2.py index 80fb84633b..c1cd2a739f 100644 --- a/src/calibre/ebooks/metadata/opf2.py +++ b/src/calibre/ebooks/metadata/opf2.py @@ -538,7 +538,8 @@ class OPF(object): # {{{ user_categories = MetadataField('user_categories', is_dc=False, formatter=json.loads, renderer=dump_user_categories) - + author_link_map = MetadataField('author_link_map', is_dc=False, + formatter=json.loads) def __init__(self, stream, basedir=os.getcwdu(), unquote_urls=True, populate_spine=True): @@ -1039,7 +1040,7 @@ class OPF(object): # {{{ for attr in ('title', 'authors', 'author_sort', 'title_sort', 'publisher', 'series', 'series_index', 'rating', 'isbn', 'tags', 'category', 'comments', - 'pubdate', 'user_categories'): + 'pubdate', 'user_categories', 'author_link_map'): val = getattr(mi, attr, None) if val is not None and val != [] and val != (None, None): setattr(self, attr, val) @@ -1336,6 +1337,8 @@ def metadata_to_opf(mi, as_string=True): for tag in mi.tags: factory(DC('subject'), tag) meta = lambda n, c: factory('meta', name='calibre:'+n, content=c) + if getattr(mi, 'author_link_map', None) is not None: + meta('author_link_map', json.dumps(mi.author_link_map)) if mi.series: meta('series', mi.series) if mi.series_index is not None: diff --git a/src/calibre/gui2/book_details.py b/src/calibre/gui2/book_details.py index f94e179166..ef21773ae4 100644 --- a/src/calibre/gui2/book_details.py +++ b/src/calibre/gui2/book_details.py @@ -121,6 +121,16 @@ def render_data(mi, use_roman_numbers=True, all_fields=False): if links: ans.append((field, u'%s%s'%( _('Ids')+':', links))) + elif field == 'authors' and not isdevice: + authors = [] + for aut in mi.authors: + if mi.author_link_map[aut]: + authors.append(u'%s' % + (mi.author_link_map[aut], aut)) + else: + authors.append(aut) + ans.append((field, u'%s%s'%(name, + u' & '.join(authors)))) else: val = mi.format_field(field)[-1] if val is None: diff --git a/src/calibre/gui2/dialogs/edit_authors_dialog.py b/src/calibre/gui2/dialogs/edit_authors_dialog.py index a791551d27..1087c3cb82 100644 --- a/src/calibre/gui2/dialogs/edit_authors_dialog.py +++ b/src/calibre/gui2/dialogs/edit_authors_dialog.py @@ -33,7 +33,7 @@ class EditAuthorsDialog(QDialog, Ui_EditAuthorsDialog): # Set up the column headings self.table.setSelectionMode(QAbstractItemView.SingleSelection) - self.table.setColumnCount(2) + self.table.setColumnCount(3) self.down_arrow_icon = QIcon(I('arrow-down.png')) self.up_arrow_icon = QIcon(I('arrow-up.png')) self.blank_icon = QIcon(I('blank.png')) @@ -43,26 +43,33 @@ class EditAuthorsDialog(QDialog, Ui_EditAuthorsDialog): self.aus_col = QTableWidgetItem(_('Author sort')) self.table.setHorizontalHeaderItem(1, self.aus_col) self.aus_col.setIcon(self.up_arrow_icon) + self.aul_col = QTableWidgetItem(_('Link')) + self.table.setHorizontalHeaderItem(2, self.aul_col) + self.aus_col.setIcon(self.blank_icon) # Add the data self.authors = {} auts = db.get_authors_with_ids() self.table.setRowCount(len(auts)) select_item = None - for row, (id, author, sort) in enumerate(auts): + for row, (id, author, sort, link) in enumerate(auts): author = author.replace('|', ',') - self.authors[id] = (author, sort) + self.authors[id] = (author, sort, link) aut = tableItem(author) aut.setData(Qt.UserRole, id) sort = tableItem(sort) + link = tableItem(link) self.table.setItem(row, 0, aut) self.table.setItem(row, 1, sort) + self.table.setItem(row, 2, link) if id == id_to_select: if select_sort: select_item = sort else: select_item = aut self.table.resizeColumnsToContents() + if self.table.columnWidth(2) < 200: + self.table.setColumnWidth(2, 200) # set up the cellChanged signal only after the table is filled self.table.cellChanged.connect(self.cell_changed) @@ -236,9 +243,10 @@ class EditAuthorsDialog(QDialog, Ui_EditAuthorsDialog): id = self.table.item(row, 0).data(Qt.UserRole).toInt()[0] aut = unicode(self.table.item(row, 0).text()).strip() sort = unicode(self.table.item(row, 1).text()).strip() - orig_aut,orig_sort = self.authors[id] - if orig_aut != aut or orig_sort != sort: - self.result.append((id, orig_aut, aut, sort)) + link = unicode(self.table.item(row, 2).text()).strip() + orig_aut,orig_sort,orig_link = self.authors[id] + if orig_aut != aut or orig_sort != sort or orig_link != link: + self.result.append((id, orig_aut, aut, sort, link)) def do_recalc_author_sort(self): self.table.cellChanged.disconnect() @@ -276,6 +284,6 @@ class EditAuthorsDialog(QDialog, Ui_EditAuthorsDialog): c.setText(author_to_author_sort(aut)) item = c else: - item = self.table.item(row, 1) + item = self.table.item(row, col) self.table.setCurrentItem(item) self.table.scrollToItem(item) diff --git a/src/calibre/gui2/library/models.py b/src/calibre/gui2/library/models.py index 40d6e2b6cf..d4aeada313 100644 --- a/src/calibre/gui2/library/models.py +++ b/src/calibre/gui2/library/models.py @@ -90,6 +90,7 @@ class BooksModel(QAbstractTableModel): # {{{ self.ids_to_highlight_set = set() self.current_highlighted_idx = None self.highlight_only = False + self.current_row = -1 self.colors = frozenset([unicode(c) for c in QColor.colorNames()]) self.read_config() @@ -172,6 +173,7 @@ class BooksModel(QAbstractTableModel): # {{{ self.color_cache = defaultdict(dict) for row in rows: if row == current_row: + self.current_row = row self.new_bookdisplay_data.emit( self.get_book_display_info(row)) self.dataChanged.emit(self.index(row, 0), self.index(row, @@ -329,6 +331,8 @@ class BooksModel(QAbstractTableModel): # {{{ def refresh(self, reset=True): self.db.refresh(field=None) self.resort(reset=reset) + if self.current_row >= 0: + self.new_bookdisplay_data.emit(self.get_book_display_info(self.current_row)) def reset(self): self.color_cache = defaultdict(dict) @@ -368,12 +372,14 @@ class BooksModel(QAbstractTableModel): # {{{ def current_changed(self, current, previous, emit_signal=True): if current.isValid(): - idx = current.row() + self.current_row = idx = current.row() data = self.get_book_display_info(idx) if emit_signal: self.new_bookdisplay_data.emit(data) else: return data + else: + self.current_row = -1 def get_book_info(self, index): if isinstance(index, int): diff --git a/src/calibre/gui2/tag_view.py b/src/calibre/gui2/tag_view.py index 3b8c27866c..21309a1592 100644 --- a/src/calibre/gui2/tag_view.py +++ b/src/calibre/gui2/tag_view.py @@ -2081,12 +2081,14 @@ class TagBrowserMixin(object): # {{{ editor = EditAuthorsDialog(parent, db, id, select_sort) d = editor.exec_() if d: - for (id, old_author, new_author, new_sort) in editor.result: + for (id, old_author, new_author, new_sort, new_link) in editor.result: if old_author != new_author: # The id might change if the new author already exists id = db.rename_author(id, new_author) db.set_sort_field_for_author(id, unicode(new_sort), commit=False, notify=False) + db.set_link_field_for_author(id, unicode(new_link), + commit=False, notify=False) db.commit() self.library_view.model().refresh() self.tags_view.recount() diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py index 4c61438e35..9602f8ef1d 100644 --- a/src/calibre/library/database2.py +++ b/src/calibre/library/database2.py @@ -367,7 +367,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): 'uuid', 'has_cover', ('au_map', 'authors', 'author', - 'aum_sortconcat(link.id, authors.name, authors.sort)'), + 'aum_sortconcat(link.id, authors.name, authors.sort, authors.link)'), 'last_modified', '(SELECT identifiers_concat(type, val) FROM identifiers WHERE identifiers.book=books.id) identifiers', ] @@ -894,13 +894,17 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): aut_list = [] aum = [] aus = {} - for (author, author_sort) in aut_list: - aum.append(author.replace('|', ',')) - aus[author] = author_sort.replace('|', ',') + aul = {} + for (author, author_sort, link) in aut_list: + aut = author.replace('|', ',') + aum.append(aut) + aus[aut] = author_sort.replace('|', ',') + aul[aut] = link mi.title = row[fm['title']] mi.authors = aum mi.author_sort = row[fm['author_sort']] mi.author_sort_map = aus + mi.author_link_map = aul mi.comments = row[fm['comments']] mi.publisher = row[fm['publisher']] mi.timestamp = row[fm['timestamp']] @@ -2002,13 +2006,13 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): def authors_with_sort_strings(self, id, index_is_id=False): id = id if index_is_id else self.id(id) aut_strings = self.conn.get(''' - SELECT authors.id, authors.name, authors.sort + SELECT authors.id, authors.name, authors.sort, authors.link FROM authors, books_authors_link as bl WHERE bl.book=? and authors.id=bl.author ORDER BY bl.id''', (id,)) result = [] - for (id_, author, sort,) in aut_strings: - result.append((id_, author.replace('|', ','), sort)) + for (id_, author, sort, link) in aut_strings: + result.append((id_, author.replace('|', ','), sort, link)) return result # Given a book, return the author_sort string for authors of the book @@ -2048,7 +2052,8 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): aum = self.authors_with_sort_strings(id_, index_is_id=True) self.data.set(id_, self.FIELD_MAP['au_map'], - ':#:'.join([':::'.join((au.replace(',', '|'), aus)) for (_, au, aus) in aum]), + ':#:'.join([':::'.join((au.replace(',', '|'), aus, aul)) + for (_, au, aus, aul) in aum]), row_is_id=True) def _set_authors(self, id, authors, allow_case_change=False): @@ -2399,7 +2404,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): self.conn.commit() def get_authors_with_ids(self): - result = self.conn.get('SELECT id,name,sort FROM authors') + result = self.conn.get('SELECT id,name,sort,link FROM authors') if not result: return [] return result @@ -2410,6 +2415,13 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): (author,), all=False) return result + def set_link_field_for_author(self, aid, link, commit=True, notify=False): + if not link: + link = '' + self.conn.execute('UPDATE authors SET link=? WHERE id=?', (link.strip(), aid)) + if commit: + self.conn.commit() + def set_sort_field_for_author(self, old_id, new_sort, commit=True, notify=False): self.conn.execute('UPDATE authors SET sort=? WHERE id=?', \ (new_sort.strip(), old_id)) diff --git a/src/calibre/library/schema_upgrades.py b/src/calibre/library/schema_upgrades.py index 3fc9a2368a..3c64785178 100644 --- a/src/calibre/library/schema_upgrades.py +++ b/src/calibre/library/schema_upgrades.py @@ -600,4 +600,14 @@ class SchemaUpgrade(object): with open(os.path.join(bdir, fname), 'wb') as f: f.write(script) + def upgrade_version_20(self): + ''' + Add a link column to the authors table. + ''' + + script = ''' + ALTER TABLE authors ADD COLUMN link TEXT NON NULL DEFAULT ""; + ''' + self.conn.executescript(script) + diff --git a/src/calibre/library/sqlite.py b/src/calibre/library/sqlite.py index 96874d2c27..a2a85806f5 100644 --- a/src/calibre/library/sqlite.py +++ b/src/calibre/library/sqlite.py @@ -144,9 +144,9 @@ class AumSortedConcatenate(object): def __init__(self): self.ans = {} - def step(self, ndx, author, sort): + def step(self, ndx, author, sort, link): if author is not None: - self.ans[ndx] = author + ':::' + sort + self.ans[ndx] = ':::'.join((author, sort, link)) def finalize(self): keys = self.ans.keys() @@ -229,7 +229,7 @@ class DBThread(Thread): load_c_extensions(self.conn) self.conn.row_factory = sqlite.Row if self.row_factory else lambda cursor, row : list(row) self.conn.create_aggregate('concat', 1, Concatenate) - self.conn.create_aggregate('aum_sortconcat', 3, AumSortedConcatenate) + self.conn.create_aggregate('aum_sortconcat', 4, AumSortedConcatenate) self.conn.create_collation('PYNOCASE', partial(pynocase, encoding=encoding)) self.conn.create_function('title_sort', 1, title_sort)