diff --git a/src/calibre/gui2/actions/edit_metadata.py b/src/calibre/gui2/actions/edit_metadata.py index b07be1904b..bd1e85d8e8 100644 --- a/src/calibre/gui2/actions/edit_metadata.py +++ b/src/calibre/gui2/actions/edit_metadata.py @@ -10,7 +10,7 @@ from functools import partial 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_bulk import MetadataBulkDialog from calibre.gui2.dialogs.confirm_delete import confirm @@ -79,6 +79,7 @@ class EditMetadataAction(InterfaceAction): self.qaction.setEnabled(enabled) self.action_merge.setEnabled(enabled) + # Download metadata {{{ def download_metadata(self, ids=None): if ids is None: rows = self.gui.library_view.selectionModel().selectedRows() @@ -89,14 +90,73 @@ class EditMetadataAction(InterfaceAction): ids = [db.id(row.row()) for row in rows] from calibre.gui2.metadata.bulk_download2 import start_download 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: self.gui.job_exception(job, dialog_title=_('Failed to download metadata')) return - from calibre.gui2.metadata.bulk_download2 import proceed - proceed(self.gui, job) + from calibre.gui2.metadata.bulk_download2 import get_job_details + 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 = '

' + _('Finished downloading metadata for %d book(s). ' + '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 += '

'+_('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'), '

'+ + _('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, set_social_metadata=None): @@ -141,6 +201,7 @@ class EditMetadataAction(InterfaceAction): x.updated, cr) if self.gui.cover_flow: self.gui.cover_flow.dataChanged() + # }}} def edit_metadata(self, checked, bulk=None): ''' @@ -467,8 +528,8 @@ class EditMetadataAction(InterfaceAction): self.gui.upload_collections(model.db, view=view, oncard=oncard) view.reset() - def apply_metadata_changes(self, id_map, - title=_('Applying changed metadata'), msg=''): + # Apply bulk metadata changes {{{ + def apply_metadata_changes(self, id_map, title=None, msg=''): ''' 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 @@ -476,6 +537,8 @@ class EditMetadataAction(InterfaceAction): that is to create a metadata object as Metadata(_('Unknown')) and then 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_current_idx = 0 self.apply_failures = [] @@ -549,3 +612,5 @@ class EditMetadataAction(InterfaceAction): self.apply_id_map = [] self.apply_pd = None + # }}} + diff --git a/src/calibre/gui2/dialogs/message_box.py b/src/calibre/gui2/dialogs/message_box.py index 945d50de4e..56e0065f54 100644 --- a/src/calibre/gui2/dialogs/message_box.py +++ b/src/calibre/gui2/dialogs/message_box.py @@ -6,13 +6,13 @@ __copyright__ = '2011, Kovid Goyal ' __docformat__ = 'restructuredtext en' -from PyQt4.Qt import QDialog, QIcon, QApplication, QSize, QKeySequence, \ - QAction, Qt +from PyQt4.Qt import (QDialog, QIcon, QApplication, QSize, QKeySequence, + QAction, Qt, pyqtSignal, QTextBrowser, QDialogButtonBox, QVBoxLayout) from calibre.constants import __version__ from calibre.gui2.dialogs.message_box_ui import Ui_Dialog -class MessageBox(QDialog, Ui_Dialog): +class MessageBox(QDialog, Ui_Dialog): # {{{ ERROR = 0 WARNING = 1 @@ -111,6 +111,68 @@ class MessageBox(QDialog, Ui_Dialog): self.det_msg_toggle.setVisible(bool(msg)) self.det_msg.setVisible(False) 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('

%s
' % 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__': app = QApplication([]) diff --git a/src/calibre/gui2/metadata/bulk_download2.py b/src/calibre/gui2/metadata/bulk_download2.py index 469f950577..2a307fc902 100644 --- a/src/calibre/gui2/metadata/bulk_download2.py +++ b/src/calibre/gui2/metadata/bulk_download2.py @@ -11,14 +11,10 @@ from functools import partial from itertools import izip from threading import Event -from PyQt4.Qt import (QIcon, QDialog, QVBoxLayout, QTextBrowser, QSize, - QDialogButtonBox, QApplication, QLabel, QGridLayout, QPixmap, Qt) +from PyQt4.Qt import (QIcon, QDialog, + QDialogButtonBox, QLabel, QGridLayout, QPixmap, Qt) -from calibre.gui2.dialogs.message_box import MessageBox 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.covers import download_cover 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) # }}} -class ViewLog(QDialog): # {{{ - - def __init__(self, html, parent=None): - QDialog.__init__(self, parent) - self.l = l = QVBoxLayout() - self.setLayout(l) - - self.tb = QTextBrowser(self) - self.tb.setHtml('
%s
' % 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'), '

'+ - _('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) +def get_job_details(job): id_map, failed_ids, failed_covers, title_map, all_failed = job.result det_msg = [] for i in failed_ids | failed_covers: @@ -191,31 +113,7 @@ def proceed(gui, job): title += (' ' + _('(Failed cover)')) det_msg.append(title) det_msg = '\n'.join(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 = '

'+_('Could not download metadata and/or covers for %d of the books. Click' - ' "Show details" to see which books.')%len(failed_ids) - msg = '

' + _('Finished downloading metadata for %d book(s). ' - '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() - -# }}} + return id_map, failed_ids, failed_covers, all_failed, det_msg def merge_result(oldmi, newmi): dummy = Metadata(_('Unknown'))