mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
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:
parent
9173a0ee0f
commit
2fa6a3b999
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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 "first edition" (from worldcat.org)</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="1">
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>Max. &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 &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><p>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.
|
||||
<p>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 &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 &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. &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 &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><p>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.
|
||||
<p>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><p>When downloading comments, append the downloaded comments to any existing comment, instead of overwriting them.</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Prefer &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 "first edition" (from worldcat.org)</string>
|
||||
<string>Append &comments to existing</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
@ -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')
|
||||
|
Loading…
x
Reference in New Issue
Block a user