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['fewer_tags'] = True
msprefs.defaults['find_first_edition_date'] = False
msprefs.defaults['append_comments'] = False
# Google covers are often poor quality (scans/errors) but they have high
# 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.utils.icu import sort_key
from calibre.db.errors import NoSuchFormat
from calibre.library.comments import merge_comments
from calibre.ebooks.metadata.sources.prefs import msprefs
class EditMetadataAction(InterfaceAction):
@ -261,9 +264,9 @@ class EditMetadataAction(InterfaceAction):
if restrict_to_failed:
db.data.set_marked_ids(failed_ids)
self.apply_metadata_changes(id_map,
callback=partial(self.downloaded_metadata_applied, tdir,
restrict_to_failed))
self.apply_metadata_changes(
id_map, merge_comments=msprefs['append_comments'],
callback=partial(self.downloaded_metadata_applied, tdir, restrict_to_failed))
def downloaded_metadata_applied(self, tdir, restrict_to_failed, *args):
if restrict_to_failed:
@ -607,7 +610,7 @@ class EditMetadataAction(InterfaceAction):
# Apply bulk metadata changes {{{
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
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.show()
self._am_merge_tags = merge_tags
self._am_merge_comments = merge_comments
self.do_one_apply()
def do_one_apply(self):
@ -684,6 +688,10 @@ class EditMetadataAction(InterfaceAction):
tags = [x.strip() for x in old_tags.split(',')] + (
mi.tags if mi.tags else [])
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,
set_authors=set_authors, notify=False)
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.utils.localization import canonicalize_lang
from calibre.utils.date import local_tz
from calibre.library.comments import merge_comments as merge_two_comments
BASE_TITLE = _('Edit Metadata')
@ -372,7 +373,7 @@ class MetadataSingleDialogBase(ResizableDialog):
show=True)
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'):
self.title.current_val = mi.title
if update_sorts:
@ -415,7 +416,12 @@ class MetadataSingleDialogBase(ResizableDialog):
if langs:
self.languages.current_val = langs
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):
d = FullFetch(self.cover.pixmap(), self)
@ -437,7 +443,7 @@ class MetadataSingleDialogBase(ResizableDialog):
# update_from_mi from changing the pubdate
mi.pubdate = datetime(pd.year, pd.month, pd.day,
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:
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('fewer_tags', msprefs)
r('find_first_edition_date', msprefs)
r('append_comments', msprefs)
self.configure_plugin_button.clicked.connect(self.configure_plugin)
self.sources_model = SourcesModel(self)

View File

@ -21,55 +21,49 @@
<widget class="QStackedWidget" name="stack">
<widget class="QWidget" name="page">
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0" rowspan="8">
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>Metadata sources</string>
<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>
</widget>
</item>
<item row="7" column="1">
<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="8" column="1">
<widget class="QLabel" name="label_4">
<property name="text">
<string>Max. time to wait after first &amp;cover is found:</string>
</property>
<property name="buddy">
<cstring>opt_wait_after_first_cover_result</cstring>
</property>
</widget>
</item>
<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>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QLabel" name="label">
<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>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QTableView" name="sources_view">
<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">
<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="0" column="1" colspan="2">
@ -139,7 +133,7 @@
</property>
</widget>
</item>
<item row="5" column="1">
<item row="6" column="1">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Max. number of &amp;tags to download:</string>
@ -149,58 +143,74 @@
</property>
</widget>
</item>
<item row="5" column="2">
<item row="6" column="2">
<widget class="QSpinBox" name="opt_max_tags"/>
</item>
<item row="6" column="1">
<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">
<item row="7" column="2">
<widget class="QSpinBox" name="opt_wait_after_first_identify_result">
<property name="suffix">
<string> secs</string>
</property>
</widget>
</item>
<item row="7" column="1">
<widget class="QLabel" name="label_4">
<property name="text">
<string>Max. time to wait after first &amp;cover is found:</string>
</property>
<property name="buddy">
<cstring>opt_wait_after_first_cover_result</cstring>
<item row="0" column="0" rowspan="9">
<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">
<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 name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QTableView" name="sources_view">
<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">
<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="7" 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">
<item row="5" column="1" colspan="2">
<widget class="QCheckBox" name="opt_append_comments">
<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>
<string>&lt;p&gt;When downloading comments, append the downloaded comments to any existing comment, instead of overwriting them.</string>
</property>
<property name="text">
<string>Prefer &amp;fewer tags</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>
<string>Append &amp;comments to existing</string>
</property>
</widget>
</item>

View File

@ -131,6 +131,9 @@ def comments_to_html(comments):
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):
text = html2text(html)
md = Markdown(safe_mode='remove')