Refactor proceed implementation for bulk metadata download

This commit is contained in:
Kovid Goyal 2011-04-22 15:16:26 -06:00
parent cf8db16141
commit 56ed5143a1
3 changed files with 141 additions and 116 deletions

View File

@ -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
# }}}

View File

@ -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([])

View File

@ -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'))