Metadata download: Add an option to merge the downloaded comments into existing comments, instead of overwriting (Preferences->Metadata download). Fixes #1285319 [[Enhancment] Enable Undo in the comments field](https://bugs.launchpad.net/calibre/+bug/1285319)

This commit is contained in:
Kovid Goyal 2014-02-27 11:24:14 +05:30
parent 9173a0ee0f
commit 2fa6a3b999
6 changed files with 123 additions and 94 deletions

View File

@ -18,6 +18,7 @@ msprefs.defaults['wait_after_first_cover_result'] = 60 # seconds
msprefs.defaults['swap_author_names'] = False msprefs.defaults['swap_author_names'] = False
msprefs.defaults['fewer_tags'] = True msprefs.defaults['fewer_tags'] = True
msprefs.defaults['find_first_edition_date'] = False msprefs.defaults['find_first_edition_date'] = False
msprefs.defaults['append_comments'] = False
# Google covers are often poor quality (scans/errors) but they have high # Google covers are often poor quality (scans/errors) but they have high
# resolution, so they trump covers from better sources. So make sure they # resolution, so they trump covers from better sources. So make sure they

View File

@ -20,6 +20,9 @@ from calibre.ebooks.metadata.book.base import Metadata
from calibre.ebooks.metadata.opf2 import OPF, metadata_to_opf from calibre.ebooks.metadata.opf2 import OPF, metadata_to_opf
from calibre.utils.icu import sort_key from calibre.utils.icu import sort_key
from calibre.db.errors import NoSuchFormat from calibre.db.errors import NoSuchFormat
from calibre.library.comments import merge_comments
from calibre.ebooks.metadata.sources.prefs import msprefs
class EditMetadataAction(InterfaceAction): class EditMetadataAction(InterfaceAction):
@ -261,9 +264,9 @@ class EditMetadataAction(InterfaceAction):
if restrict_to_failed: if restrict_to_failed:
db.data.set_marked_ids(failed_ids) db.data.set_marked_ids(failed_ids)
self.apply_metadata_changes(id_map, self.apply_metadata_changes(
callback=partial(self.downloaded_metadata_applied, tdir, id_map, merge_comments=msprefs['append_comments'],
restrict_to_failed)) callback=partial(self.downloaded_metadata_applied, tdir, restrict_to_failed))
def downloaded_metadata_applied(self, tdir, restrict_to_failed, *args): def downloaded_metadata_applied(self, tdir, restrict_to_failed, *args):
if restrict_to_failed: if restrict_to_failed:
@ -607,7 +610,7 @@ class EditMetadataAction(InterfaceAction):
# Apply bulk metadata changes {{{ # Apply bulk metadata changes {{{
def apply_metadata_changes(self, id_map, title=None, msg='', callback=None, def apply_metadata_changes(self, id_map, title=None, msg='', callback=None,
merge_tags=True): merge_tags=True, merge_comments=False):
''' '''
Apply the metadata changes in id_map to the database synchronously Apply the metadata changes in id_map to the database synchronously
id_map must be a mapping of ids to Metadata objects. Set any fields you id_map must be a mapping of ids to Metadata objects. Set any fields you
@ -640,6 +643,7 @@ class EditMetadataAction(InterfaceAction):
self.apply_pd.setModal(True) self.apply_pd.setModal(True)
self.apply_pd.show() self.apply_pd.show()
self._am_merge_tags = merge_tags self._am_merge_tags = merge_tags
self._am_merge_comments = merge_comments
self.do_one_apply() self.do_one_apply()
def do_one_apply(self): def do_one_apply(self):
@ -684,6 +688,10 @@ class EditMetadataAction(InterfaceAction):
tags = [x.strip() for x in old_tags.split(',')] + ( tags = [x.strip() for x in old_tags.split(',')] + (
mi.tags if mi.tags else []) mi.tags if mi.tags else [])
mi.tags = list(set(tags)) mi.tags = list(set(tags))
if self._am_merge_comments:
old_comments = db.new_api.field_for('comments', book_id)
if old_comments and mi.comments and old_comments != mi.comments:
mi.comments = merge_comments(old_comments, mi.comments)
db.set_metadata(book_id, mi, commit=False, set_title=set_title, db.set_metadata(book_id, mi, commit=False, set_title=set_title,
set_authors=set_authors, notify=False) set_authors=set_authors, notify=False)
self.applied_ids.add(book_id) self.applied_ids.add(book_id)

View File

@ -27,6 +27,7 @@ from calibre.utils.config import tweaks
from calibre.ebooks.metadata.book.base import Metadata from calibre.ebooks.metadata.book.base import Metadata
from calibre.utils.localization import canonicalize_lang from calibre.utils.localization import canonicalize_lang
from calibre.utils.date import local_tz from calibre.utils.date import local_tz
from calibre.library.comments import merge_comments as merge_two_comments
BASE_TITLE = _('Edit Metadata') BASE_TITLE = _('Edit Metadata')
@ -372,7 +373,7 @@ class MetadataSingleDialogBase(ResizableDialog):
show=True) show=True)
return return
def update_from_mi(self, mi, update_sorts=True, merge_tags=True): def update_from_mi(self, mi, update_sorts=True, merge_tags=True, merge_comments=False):
if not mi.is_null('title'): if not mi.is_null('title'):
self.title.current_val = mi.title self.title.current_val = mi.title
if update_sorts: if update_sorts:
@ -415,7 +416,12 @@ class MetadataSingleDialogBase(ResizableDialog):
if langs: if langs:
self.languages.current_val = langs self.languages.current_val = langs
if mi.comments and mi.comments.strip(): if mi.comments and mi.comments.strip():
self.comments.current_val = mi.comments val = mi.comments
if val and merge_comments:
cval = self.comments.current_val
if cval:
val = merge_two_comments(cval, val)
self.comments.current_val = val
def fetch_metadata(self, *args): def fetch_metadata(self, *args):
d = FullFetch(self.cover.pixmap(), self) d = FullFetch(self.cover.pixmap(), self)
@ -437,7 +443,7 @@ class MetadataSingleDialogBase(ResizableDialog):
# update_from_mi from changing the pubdate # update_from_mi from changing the pubdate
mi.pubdate = datetime(pd.year, pd.month, pd.day, mi.pubdate = datetime(pd.year, pd.month, pd.day,
tzinfo=local_tz) tzinfo=local_tz)
self.update_from_mi(mi) self.update_from_mi(mi, merge_comments=msprefs['append_comments'])
if d.cover_pixmap is not None: if d.cover_pixmap is not None:
self.cover.current_val = pixmap_to_data(d.cover_pixmap) self.cover.current_val = pixmap_to_data(d.cover_pixmap)

View File

@ -297,6 +297,7 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
r('swap_author_names', msprefs) r('swap_author_names', msprefs)
r('fewer_tags', msprefs) r('fewer_tags', msprefs)
r('find_first_edition_date', msprefs) r('find_first_edition_date', msprefs)
r('append_comments', msprefs)
self.configure_plugin_button.clicked.connect(self.configure_plugin) self.configure_plugin_button.clicked.connect(self.configure_plugin)
self.sources_model = SourcesModel(self) self.sources_model = SourcesModel(self)

View File

@ -21,55 +21,49 @@
<widget class="QStackedWidget" name="stack"> <widget class="QStackedWidget" name="stack">
<widget class="QWidget" name="page"> <widget class="QWidget" name="page">
<layout class="QGridLayout" name="gridLayout"> <layout class="QGridLayout" name="gridLayout">
<item row="0" column="0" rowspan="8"> <item row="3" column="1" colspan="2">
<widget class="QGroupBox" name="groupBox"> <widget class="QCheckBox" name="opt_find_first_edition_date">
<property name="title">
<string>Metadata sources</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QLabel" name="label">
<property name="text"> <property name="text">
<string>Disable any metadata sources you do not want by unchecking them. You can also set the cover priority. Covers from sources that have a higher (smaller) priority will be preferred when bulk downloading metadata. <string>Use published date of &quot;first edition&quot; (from worldcat.org)</string>
</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property> </property>
</widget> </widget>
</item> </item>
<item> <item row="7" column="1">
<widget class="QTableView" name="sources_view"> <widget class="QLabel" name="label_3">
<property name="selectionMode">
<enum>QAbstractItemView::SingleSelection</enum>
</property>
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectRows</enum>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_5">
<property name="text"> <property name="text">
<string>Sources with a red X next to their names must be configured before they will be used. </string> <string>Max. &amp;time to wait after first match is found:</string>
</property> </property>
<property name="wordWrap"> <property name="buddy">
<bool>true</bool> <cstring>opt_wait_after_first_identify_result</cstring>
</property> </property>
</widget> </widget>
</item> </item>
<item> <item row="8" column="1">
<widget class="QPushButton" name="configure_plugin_button"> <widget class="QLabel" name="label_4">
<property name="text"> <property name="text">
<string>Configure selected source</string> <string>Max. time to wait after first &amp;cover is found:</string>
</property> </property>
<property name="icon"> <property name="buddy">
<iconset resource="../../../../resources/images.qrc"> <cstring>opt_wait_after_first_cover_result</cstring>
<normaloff>:/images/plugins.png</normaloff>:/images/plugins.png</iconset>
</property> </property>
</widget> </widget>
</item> </item>
</layout> <item row="8" column="2">
<widget class="QSpinBox" name="opt_wait_after_first_cover_result">
<property name="suffix">
<string> secs</string>
</property>
</widget>
</item>
<item row="4" column="1" colspan="2">
<widget class="QCheckBox" name="opt_fewer_tags">
<property name="toolTip">
<string>&lt;p&gt;Different metadata sources have different sets of tags for the same book. If this option is checked, then calibre will use the smaller tag sets. These tend to be more like genres, while the larger tag sets tend to describe the books content.
&lt;p&gt;Note that this option will only make a practical difference if one of the metadata sources has a genre like tag set for the book you are searching for. Most often, they all have large tag sets.</string>
</property>
<property name="text">
<string>Prefer &amp;fewer tags</string>
</property>
</widget> </widget>
</item> </item>
<item row="0" column="1" colspan="2"> <item row="0" column="1" colspan="2">
@ -139,7 +133,7 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="5" column="1"> <item row="6" column="1">
<widget class="QLabel" name="label_2"> <widget class="QLabel" name="label_2">
<property name="text"> <property name="text">
<string>Max. number of &amp;tags to download:</string> <string>Max. number of &amp;tags to download:</string>
@ -149,58 +143,74 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="5" column="2"> <item row="6" column="2">
<widget class="QSpinBox" name="opt_max_tags"/> <widget class="QSpinBox" name="opt_max_tags"/>
</item> </item>
<item row="6" column="1"> <item row="7" column="2">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Max. &amp;time to wait after first match is found:</string>
</property>
<property name="buddy">
<cstring>opt_wait_after_first_identify_result</cstring>
</property>
</widget>
</item>
<item row="6" column="2">
<widget class="QSpinBox" name="opt_wait_after_first_identify_result"> <widget class="QSpinBox" name="opt_wait_after_first_identify_result">
<property name="suffix"> <property name="suffix">
<string> secs</string> <string> secs</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="7" column="1"> <item row="0" column="0" rowspan="9">
<widget class="QLabel" name="label_4"> <widget class="QGroupBox" name="groupBox">
<property name="title">
<string>Metadata sources</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QLabel" name="label">
<property name="text"> <property name="text">
<string>Max. time to wait after first &amp;cover is found:</string> <string>Disable any metadata sources you do not want by unchecking them. You can also set the cover priority. Covers from sources that have a higher (smaller) priority will be preferred when bulk downloading metadata.
</string>
</property> </property>
<property name="buddy"> <property name="wordWrap">
<cstring>opt_wait_after_first_cover_result</cstring> <bool>true</bool>
</property> </property>
</widget> </widget>
</item> </item>
<item row="7" column="2"> <item>
<widget class="QSpinBox" name="opt_wait_after_first_cover_result"> <widget class="QTableView" name="sources_view">
<property name="suffix"> <property name="selectionMode">
<string> secs</string> <enum>QAbstractItemView::SingleSelection</enum>
</property>
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectRows</enum>
</property> </property>
</widget> </widget>
</item> </item>
<item row="4" column="1" colspan="2"> <item>
<widget class="QCheckBox" name="opt_fewer_tags"> <widget class="QLabel" name="label_5">
<property name="text">
<string>Sources with a red X next to their names must be configured before they will be used. </string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="configure_plugin_button">
<property name="text">
<string>Configure selected source</string>
</property>
<property name="icon">
<iconset resource="../../../../resources/images.qrc">
<normaloff>:/images/plugins.png</normaloff>:/images/plugins.png</iconset>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="5" column="1" colspan="2">
<widget class="QCheckBox" name="opt_append_comments">
<property name="toolTip"> <property name="toolTip">
<string>&lt;p&gt;Different metadata sources have different sets of tags for the same book. If this option is checked, then calibre will use the smaller tag sets. These tend to be more like genres, while the larger tag sets tend to describe the books content. <string>&lt;p&gt;When downloading comments, append the downloaded comments to any existing comment, instead of overwriting them.</string>
&lt;p&gt;Note that this option will only make a practical difference if one of the metadata sources has a genre like tag set for the book you are searching for. Most often, they all have large tag sets.</string>
</property> </property>
<property name="text"> <property name="text">
<string>Prefer &amp;fewer tags</string> <string>Append &amp;comments to existing</string>
</property>
</widget>
</item>
<item row="3" column="1" colspan="2">
<widget class="QCheckBox" name="opt_find_first_edition_date">
<property name="text">
<string>Use published date of &quot;first edition&quot; (from worldcat.org)</string>
</property> </property>
</widget> </widget>
</item> </item>

View File

@ -131,6 +131,9 @@ def comments_to_html(comments):
return result.renderContents(encoding=None) return result.renderContents(encoding=None)
def merge_comments(one, two):
return comments_to_html(one) + '\n\n' + comments_to_html(two)
def sanitize_comments_html(html): def sanitize_comments_html(html):
text = html2text(html) text = html2text(html)
md = Markdown(safe_mode='remove') md = Markdown(safe_mode='remove')