mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Refactor proceed implementation for bulk metadata download
This commit is contained in:
parent
cf8db16141
commit
56ed5143a1
@ -10,7 +10,7 @@ from functools import partial
|
|||||||
|
|
||||||
from PyQt4.Qt import Qt, QMenu, QModelIndex, QTimer
|
from PyQt4.Qt import Qt, QMenu, QModelIndex, QTimer
|
||||||
|
|
||||||
from calibre.gui2 import error_dialog, config, Dispatcher
|
from calibre.gui2 import error_dialog, config, Dispatcher, question_dialog
|
||||||
from calibre.gui2.dialogs.metadata_single import MetadataSingleDialog
|
from calibre.gui2.dialogs.metadata_single import MetadataSingleDialog
|
||||||
from calibre.gui2.dialogs.metadata_bulk import MetadataBulkDialog
|
from calibre.gui2.dialogs.metadata_bulk import MetadataBulkDialog
|
||||||
from calibre.gui2.dialogs.confirm_delete import confirm
|
from calibre.gui2.dialogs.confirm_delete import confirm
|
||||||
@ -79,6 +79,7 @@ class EditMetadataAction(InterfaceAction):
|
|||||||
self.qaction.setEnabled(enabled)
|
self.qaction.setEnabled(enabled)
|
||||||
self.action_merge.setEnabled(enabled)
|
self.action_merge.setEnabled(enabled)
|
||||||
|
|
||||||
|
# Download metadata {{{
|
||||||
def download_metadata(self, ids=None):
|
def download_metadata(self, ids=None):
|
||||||
if ids is None:
|
if ids is None:
|
||||||
rows = self.gui.library_view.selectionModel().selectedRows()
|
rows = self.gui.library_view.selectionModel().selectedRows()
|
||||||
@ -89,14 +90,73 @@ class EditMetadataAction(InterfaceAction):
|
|||||||
ids = [db.id(row.row()) for row in rows]
|
ids = [db.id(row.row()) for row in rows]
|
||||||
from calibre.gui2.metadata.bulk_download2 import start_download
|
from calibre.gui2.metadata.bulk_download2 import start_download
|
||||||
start_download(self.gui, ids,
|
start_download(self.gui, ids,
|
||||||
Dispatcher(self.bulk_metadata_downloaded))
|
Dispatcher(self.metadata_downloaded))
|
||||||
|
|
||||||
def bulk_metadata_downloaded(self, job):
|
def metadata_downloaded(self, job):
|
||||||
if job.failed:
|
if job.failed:
|
||||||
self.gui.job_exception(job, dialog_title=_('Failed to download metadata'))
|
self.gui.job_exception(job, dialog_title=_('Failed to download metadata'))
|
||||||
return
|
return
|
||||||
from calibre.gui2.metadata.bulk_download2 import proceed
|
from calibre.gui2.metadata.bulk_download2 import get_job_details
|
||||||
proceed(self.gui, job)
|
id_map, failed_ids, failed_covers, all_failed, det_msg = \
|
||||||
|
get_job_details(job)
|
||||||
|
if all_failed:
|
||||||
|
return error_dialog(self.gui, _('Download failed'),
|
||||||
|
_('Failed to download metadata or covers for any of the %d'
|
||||||
|
' book(s).') % len(id_map), det_msg=det_msg, show=True)
|
||||||
|
|
||||||
|
self.gui.status_bar.show_message(_('Metadata download completed'), 3000)
|
||||||
|
|
||||||
|
msg = '<p>' + _('Finished downloading metadata for <b>%d book(s)</b>. '
|
||||||
|
'Proceed with updating the metadata in your library?')%len(id_map)
|
||||||
|
|
||||||
|
show_copy_button = False
|
||||||
|
if failed_ids or failed_covers:
|
||||||
|
show_copy_button = True
|
||||||
|
msg += '<p>'+_('Could not download metadata and/or covers for %d of the books. Click'
|
||||||
|
' "Show details" to see which books.')%len(failed_ids)
|
||||||
|
|
||||||
|
payload = (id_map, failed_ids, failed_covers)
|
||||||
|
from calibre.gui2.dialogs.message_box import ProceedNotification
|
||||||
|
p = ProceedNotification(payload, job.html_details,
|
||||||
|
_('Download log'), _('Download complete'), msg,
|
||||||
|
det_msg=det_msg, show_copy_button=show_copy_button,
|
||||||
|
parent=self.gui)
|
||||||
|
p.proceed.connect(self.apply_downloaded_metadata)
|
||||||
|
p.show()
|
||||||
|
|
||||||
|
def apply_downloaded_metadata(self, payload):
|
||||||
|
id_map, failed_ids, failed_covers = payload
|
||||||
|
id_map = dict([(k, v) for k, v in id_map.iteritems() if k not in
|
||||||
|
failed_ids])
|
||||||
|
if not id_map:
|
||||||
|
return
|
||||||
|
|
||||||
|
modified = set()
|
||||||
|
db = self.gui.current_db
|
||||||
|
|
||||||
|
for i, mi in id_map.iteritems():
|
||||||
|
lm = db.metadata_last_modified(i, index_is_id=True)
|
||||||
|
if lm > mi.last_modified:
|
||||||
|
title = db.title(i, index_is_id=True)
|
||||||
|
authors = db.authors(i, index_is_id=True)
|
||||||
|
if authors:
|
||||||
|
authors = [x.replace('|', ',') for x in authors.split(',')]
|
||||||
|
title += ' - ' + authors_to_string(authors)
|
||||||
|
modified.add(title)
|
||||||
|
|
||||||
|
if modified:
|
||||||
|
from calibre.utils.icu import lower
|
||||||
|
|
||||||
|
modified = sorted(modified, key=lower)
|
||||||
|
if not question_dialog(self.gui, _('Some books changed'), '<p>'+
|
||||||
|
_('The metadata for some books in your library has'
|
||||||
|
' changed since you started the download. If you'
|
||||||
|
' proceed, some of those changes may be overwritten. '
|
||||||
|
'Click "Show details" to see the list of changed books. '
|
||||||
|
'Do you want to proceed?'), det_msg='\n'.join(modified)):
|
||||||
|
return
|
||||||
|
|
||||||
|
self.apply_metadata_changes(id_map)
|
||||||
|
|
||||||
def download_metadata_old(self, checked, covers=True, set_metadata=True,
|
def download_metadata_old(self, checked, covers=True, set_metadata=True,
|
||||||
set_social_metadata=None):
|
set_social_metadata=None):
|
||||||
@ -141,6 +201,7 @@ class EditMetadataAction(InterfaceAction):
|
|||||||
x.updated, cr)
|
x.updated, cr)
|
||||||
if self.gui.cover_flow:
|
if self.gui.cover_flow:
|
||||||
self.gui.cover_flow.dataChanged()
|
self.gui.cover_flow.dataChanged()
|
||||||
|
# }}}
|
||||||
|
|
||||||
def edit_metadata(self, checked, bulk=None):
|
def edit_metadata(self, checked, bulk=None):
|
||||||
'''
|
'''
|
||||||
@ -467,8 +528,8 @@ class EditMetadataAction(InterfaceAction):
|
|||||||
self.gui.upload_collections(model.db, view=view, oncard=oncard)
|
self.gui.upload_collections(model.db, view=view, oncard=oncard)
|
||||||
view.reset()
|
view.reset()
|
||||||
|
|
||||||
def apply_metadata_changes(self, id_map,
|
# Apply bulk metadata changes {{{
|
||||||
title=_('Applying changed metadata'), msg=''):
|
def apply_metadata_changes(self, id_map, title=None, msg=''):
|
||||||
'''
|
'''
|
||||||
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
|
||||||
@ -476,6 +537,8 @@ class EditMetadataAction(InterfaceAction):
|
|||||||
that is to create a metadata object as Metadata(_('Unknown')) and then
|
that is to create a metadata object as Metadata(_('Unknown')) and then
|
||||||
only set the fields you want changed on this object.
|
only set the fields you want changed on this object.
|
||||||
'''
|
'''
|
||||||
|
if title is None:
|
||||||
|
title = _('Applying changed metadata')
|
||||||
self.apply_id_map = list(id_map.iteritems())
|
self.apply_id_map = list(id_map.iteritems())
|
||||||
self.apply_current_idx = 0
|
self.apply_current_idx = 0
|
||||||
self.apply_failures = []
|
self.apply_failures = []
|
||||||
@ -549,3 +612,5 @@ class EditMetadataAction(InterfaceAction):
|
|||||||
self.apply_id_map = []
|
self.apply_id_map = []
|
||||||
self.apply_pd = None
|
self.apply_pd = None
|
||||||
|
|
||||||
|
# }}}
|
||||||
|
|
||||||
|
@ -6,13 +6,13 @@ __copyright__ = '2011, Kovid Goyal <kovid@kovidgoyal.net>'
|
|||||||
__docformat__ = 'restructuredtext en'
|
__docformat__ = 'restructuredtext en'
|
||||||
|
|
||||||
|
|
||||||
from PyQt4.Qt import QDialog, QIcon, QApplication, QSize, QKeySequence, \
|
from PyQt4.Qt import (QDialog, QIcon, QApplication, QSize, QKeySequence,
|
||||||
QAction, Qt
|
QAction, Qt, pyqtSignal, QTextBrowser, QDialogButtonBox, QVBoxLayout)
|
||||||
|
|
||||||
from calibre.constants import __version__
|
from calibre.constants import __version__
|
||||||
from calibre.gui2.dialogs.message_box_ui import Ui_Dialog
|
from calibre.gui2.dialogs.message_box_ui import Ui_Dialog
|
||||||
|
|
||||||
class MessageBox(QDialog, Ui_Dialog):
|
class MessageBox(QDialog, Ui_Dialog): # {{{
|
||||||
|
|
||||||
ERROR = 0
|
ERROR = 0
|
||||||
WARNING = 1
|
WARNING = 1
|
||||||
@ -111,6 +111,68 @@ class MessageBox(QDialog, Ui_Dialog):
|
|||||||
self.det_msg_toggle.setVisible(bool(msg))
|
self.det_msg_toggle.setVisible(bool(msg))
|
||||||
self.det_msg.setVisible(False)
|
self.det_msg.setVisible(False)
|
||||||
self.do_resize()
|
self.do_resize()
|
||||||
|
# }}}
|
||||||
|
|
||||||
|
class ViewLog(QDialog): # {{{
|
||||||
|
|
||||||
|
def __init__(self, title, html, parent=None):
|
||||||
|
QDialog.__init__(self, parent)
|
||||||
|
self.l = l = QVBoxLayout()
|
||||||
|
self.setLayout(l)
|
||||||
|
|
||||||
|
self.tb = QTextBrowser(self)
|
||||||
|
self.tb.setHtml('<pre style="font-family: monospace">%s</pre>' % html)
|
||||||
|
l.addWidget(self.tb)
|
||||||
|
|
||||||
|
self.bb = QDialogButtonBox(QDialogButtonBox.Ok)
|
||||||
|
self.bb.accepted.connect(self.accept)
|
||||||
|
self.bb.rejected.connect(self.reject)
|
||||||
|
self.copy_button = self.bb.addButton(_('Copy to clipboard'),
|
||||||
|
self.bb.ActionRole)
|
||||||
|
self.copy_button.setIcon(QIcon(I('edit-copy.png')))
|
||||||
|
self.copy_button.clicked.connect(self.copy_to_clipboard)
|
||||||
|
l.addWidget(self.bb)
|
||||||
|
self.setModal(False)
|
||||||
|
self.resize(QSize(700, 500))
|
||||||
|
self.setWindowTitle(title)
|
||||||
|
self.setWindowIcon(QIcon(I('debug.png')))
|
||||||
|
self.show()
|
||||||
|
|
||||||
|
def copy_to_clipboard(self):
|
||||||
|
txt = self.tb.toPlainText()
|
||||||
|
QApplication.clipboard().setText(txt)
|
||||||
|
# }}}
|
||||||
|
|
||||||
|
class ProceedNotification(MessageBox):
|
||||||
|
|
||||||
|
proceed = pyqtSignal(object)
|
||||||
|
|
||||||
|
def __init__(self, payload, html_log, log_viewer_title, title, msg, det_msg='', show_copy_button=False, parent=None):
|
||||||
|
MessageBox.__init__(self, MessageBox.QUESTION, title, msg,
|
||||||
|
det_msg=det_msg, show_copy_button=show_copy_button,
|
||||||
|
parent=parent)
|
||||||
|
self.payload = payload
|
||||||
|
self.html_log = html_log
|
||||||
|
self.log_viewer_title = log_viewer_title
|
||||||
|
self.finished.connect(self.do_proceed)
|
||||||
|
|
||||||
|
self.vlb = self.bb.addButton(_('View log'), self.bb.ActionRole)
|
||||||
|
self.vlb.setIcon(QIcon(I('debug.png')))
|
||||||
|
self.vlb.clicked.connect(self.show_log)
|
||||||
|
self.det_msg_toggle.setVisible(bool(det_msg))
|
||||||
|
self.setModal(False)
|
||||||
|
|
||||||
|
def show_log(self):
|
||||||
|
self.log_viewer = ViewLog(self.log_viewer_title, self.html_log,
|
||||||
|
parent=self)
|
||||||
|
|
||||||
|
def do_proceed(self, result):
|
||||||
|
if result == self.Accepted:
|
||||||
|
self.proceed.emit(self.payload)
|
||||||
|
try:
|
||||||
|
self.proceed.disconnect()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
app = QApplication([])
|
app = QApplication([])
|
||||||
|
@ -11,14 +11,10 @@ from functools import partial
|
|||||||
from itertools import izip
|
from itertools import izip
|
||||||
from threading import Event
|
from threading import Event
|
||||||
|
|
||||||
from PyQt4.Qt import (QIcon, QDialog, QVBoxLayout, QTextBrowser, QSize,
|
from PyQt4.Qt import (QIcon, QDialog,
|
||||||
QDialogButtonBox, QApplication, QLabel, QGridLayout, QPixmap, Qt)
|
QDialogButtonBox, QLabel, QGridLayout, QPixmap, Qt)
|
||||||
|
|
||||||
from calibre.gui2.dialogs.message_box import MessageBox
|
|
||||||
from calibre.gui2.threaded_jobs import ThreadedJob
|
from calibre.gui2.threaded_jobs import ThreadedJob
|
||||||
from calibre.utils.icu import lower
|
|
||||||
from calibre.ebooks.metadata import authors_to_string
|
|
||||||
from calibre.gui2 import question_dialog, error_dialog
|
|
||||||
from calibre.ebooks.metadata.sources.identify import identify, msprefs
|
from calibre.ebooks.metadata.sources.identify import identify, msprefs
|
||||||
from calibre.ebooks.metadata.sources.covers import download_cover
|
from calibre.ebooks.metadata.sources.covers import download_cover
|
||||||
from calibre.ebooks.metadata.book.base import Metadata
|
from calibre.ebooks.metadata.book.base import Metadata
|
||||||
@ -106,81 +102,7 @@ def start_download(gui, ids, callback):
|
|||||||
gui.status_bar.show_message(_('Metadata download started'), 3000)
|
gui.status_bar.show_message(_('Metadata download started'), 3000)
|
||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
class ViewLog(QDialog): # {{{
|
def get_job_details(job):
|
||||||
|
|
||||||
def __init__(self, html, parent=None):
|
|
||||||
QDialog.__init__(self, parent)
|
|
||||||
self.l = l = QVBoxLayout()
|
|
||||||
self.setLayout(l)
|
|
||||||
|
|
||||||
self.tb = QTextBrowser(self)
|
|
||||||
self.tb.setHtml('<pre style="font-family: monospace">%s</pre>' % html)
|
|
||||||
l.addWidget(self.tb)
|
|
||||||
|
|
||||||
self.bb = QDialogButtonBox(QDialogButtonBox.Ok)
|
|
||||||
self.bb.accepted.connect(self.accept)
|
|
||||||
self.bb.rejected.connect(self.reject)
|
|
||||||
self.copy_button = self.bb.addButton(_('Copy to clipboard'),
|
|
||||||
self.bb.ActionRole)
|
|
||||||
self.copy_button.setIcon(QIcon(I('edit-copy.png')))
|
|
||||||
self.copy_button.clicked.connect(self.copy_to_clipboard)
|
|
||||||
l.addWidget(self.bb)
|
|
||||||
self.setModal(False)
|
|
||||||
self.resize(QSize(700, 500))
|
|
||||||
self.setWindowTitle(_('Download log'))
|
|
||||||
self.setWindowIcon(QIcon(I('debug.png')))
|
|
||||||
self.show()
|
|
||||||
|
|
||||||
def copy_to_clipboard(self):
|
|
||||||
txt = self.tb.toPlainText()
|
|
||||||
QApplication.clipboard().setText(txt)
|
|
||||||
|
|
||||||
_vl = None
|
|
||||||
def view_log(job, parent):
|
|
||||||
global _vl
|
|
||||||
_vl = ViewLog(job.html_details, parent)
|
|
||||||
|
|
||||||
# }}}
|
|
||||||
|
|
||||||
# Apply metadata {{{
|
|
||||||
def apply_metadata(job, gui, q, result):
|
|
||||||
q.vlb.clicked.disconnect()
|
|
||||||
q.finished.disconnect()
|
|
||||||
if result != q.Accepted:
|
|
||||||
return
|
|
||||||
id_map, failed_ids, failed_covers, title_map, all_failed = job.result
|
|
||||||
id_map = dict([(k, v) for k, v in id_map.iteritems() if k not in
|
|
||||||
failed_ids])
|
|
||||||
if not id_map:
|
|
||||||
return
|
|
||||||
|
|
||||||
modified = set()
|
|
||||||
db = gui.current_db
|
|
||||||
|
|
||||||
for i, mi in id_map.iteritems():
|
|
||||||
lm = db.metadata_last_modified(i, index_is_id=True)
|
|
||||||
if lm > mi.last_modified:
|
|
||||||
title = db.title(i, index_is_id=True)
|
|
||||||
authors = db.authors(i, index_is_id=True)
|
|
||||||
if authors:
|
|
||||||
authors = [x.replace('|', ',') for x in authors.split(',')]
|
|
||||||
title += ' - ' + authors_to_string(authors)
|
|
||||||
modified.add(title)
|
|
||||||
|
|
||||||
if modified:
|
|
||||||
modified = sorted(modified, key=lower)
|
|
||||||
if not question_dialog(gui, _('Some books changed'), '<p>'+
|
|
||||||
_('The metadata for some books in your library has'
|
|
||||||
' changed since you started the download. If you'
|
|
||||||
' proceed, some of those changes may be overwritten. '
|
|
||||||
'Click "Show details" to see the list of changed books. '
|
|
||||||
'Do you want to proceed?'), det_msg='\n'.join(modified)):
|
|
||||||
return
|
|
||||||
|
|
||||||
gui.iactions['Edit Metadata'].apply_metadata_changes(id_map)
|
|
||||||
|
|
||||||
def proceed(gui, job):
|
|
||||||
gui.status_bar.show_message(_('Metadata download completed'), 3000)
|
|
||||||
id_map, failed_ids, failed_covers, title_map, all_failed = job.result
|
id_map, failed_ids, failed_covers, title_map, all_failed = job.result
|
||||||
det_msg = []
|
det_msg = []
|
||||||
for i in failed_ids | failed_covers:
|
for i in failed_ids | failed_covers:
|
||||||
@ -191,31 +113,7 @@ def proceed(gui, job):
|
|||||||
title += (' ' + _('(Failed cover)'))
|
title += (' ' + _('(Failed cover)'))
|
||||||
det_msg.append(title)
|
det_msg.append(title)
|
||||||
det_msg = '\n'.join(det_msg)
|
det_msg = '\n'.join(det_msg)
|
||||||
|
return id_map, failed_ids, failed_covers, all_failed, det_msg
|
||||||
if all_failed:
|
|
||||||
q = error_dialog(gui, _('Download failed'),
|
|
||||||
_('Failed to download metadata or covers for any of the %d'
|
|
||||||
' book(s).') % len(id_map), det_msg=det_msg)
|
|
||||||
else:
|
|
||||||
fmsg = ''
|
|
||||||
if failed_ids or failed_covers:
|
|
||||||
fmsg = '<p>'+_('Could not download metadata and/or covers for %d of the books. Click'
|
|
||||||
' "Show details" to see which books.')%len(failed_ids)
|
|
||||||
msg = '<p>' + _('Finished downloading metadata for <b>%d book(s)</b>. '
|
|
||||||
'Proceed with updating the metadata in your library?')%len(id_map)
|
|
||||||
q = MessageBox(MessageBox.QUESTION, _('Download complete'),
|
|
||||||
msg + fmsg, det_msg=det_msg, show_copy_button=bool(failed_ids),
|
|
||||||
parent=gui)
|
|
||||||
q.finished.connect(partial(apply_metadata, job, gui, q))
|
|
||||||
|
|
||||||
q.vlb = q.bb.addButton(_('View log'), q.bb.ActionRole)
|
|
||||||
q.vlb.setIcon(QIcon(I('debug.png')))
|
|
||||||
q.vlb.clicked.connect(partial(view_log, job, q))
|
|
||||||
q.det_msg_toggle.setVisible(bool(failed_ids | failed_covers))
|
|
||||||
q.setModal(False)
|
|
||||||
q.show()
|
|
||||||
|
|
||||||
# }}}
|
|
||||||
|
|
||||||
def merge_result(oldmi, newmi):
|
def merge_result(oldmi, newmi):
|
||||||
dummy = Metadata(_('Unknown'))
|
dummy = Metadata(_('Unknown'))
|
||||||
|
Loading…
x
Reference in New Issue
Block a user