mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-08 10:44:09 -04:00
Bulk download UI
This commit is contained in:
parent
2d9625f5b2
commit
a404e9827d
@ -89,7 +89,8 @@ class EditMetadataAction(InterfaceAction):
|
|||||||
db = self.gui.library_view.model().db
|
db = self.gui.library_view.model().db
|
||||||
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, Dispatcher(self.bulk_metadata_downloaded))
|
start_download(self.gui, ids,
|
||||||
|
Dispatcher(self.bulk_metadata_downloaded), identify, covers)
|
||||||
|
|
||||||
def bulk_metadata_downloaded(self, job):
|
def bulk_metadata_downloaded(self, job):
|
||||||
if job.failed:
|
if job.failed:
|
||||||
|
@ -10,18 +10,21 @@ __docformat__ = 'restructuredtext en'
|
|||||||
from functools import partial
|
from functools import partial
|
||||||
from itertools import izip
|
from itertools import izip
|
||||||
|
|
||||||
from PyQt4.Qt import (QIcon, QDialog, QVBoxLayout, QTextBrowser,
|
from PyQt4.Qt import (QIcon, QDialog, QVBoxLayout, QTextBrowser, QSize,
|
||||||
QDialogButtonBox, QApplication)
|
QDialogButtonBox, QApplication, QTimer, QLabel, QProgressBar)
|
||||||
|
|
||||||
from calibre.gui2.dialogs.message_box import MessageBox
|
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
|
||||||
|
|
||||||
def show_config(gui, parent):
|
def show_config(gui, parent):
|
||||||
from calibre.gui2.preferences import show_config_widget
|
from calibre.gui2.preferences import show_config_widget
|
||||||
show_config_widget('Sharing', 'Metadata download', parent=parent,
|
show_config_widget('Sharing', 'Metadata download', parent=parent,
|
||||||
gui=gui, never_shutdown=True)
|
gui=gui, never_shutdown=True)
|
||||||
|
|
||||||
def start_download(gui, ids, callback):
|
def start_download(gui, ids, callback, identify, covers):
|
||||||
q = MessageBox(MessageBox.QUESTION, _('Schedule download?'),
|
q = MessageBox(MessageBox.QUESTION, _('Schedule download?'),
|
||||||
'<p>'+_('The download of metadata for the <b>%d selected book(s)</b> will'
|
'<p>'+_('The download of metadata for the <b>%d selected book(s)</b> will'
|
||||||
' run in the background. Proceed?')%len(ids) +
|
' run in the background. Proceed?')%len(ids) +
|
||||||
@ -43,10 +46,10 @@ def start_download(gui, ids, callback):
|
|||||||
|
|
||||||
job = ThreadedJob('metadata bulk download',
|
job = ThreadedJob('metadata bulk download',
|
||||||
_('Download metadata for %d books')%len(ids),
|
_('Download metadata for %d books')%len(ids),
|
||||||
download, (ids, gui.current_db), {}, callback)
|
download, (ids, gui.current_db, identify, covers), {}, callback)
|
||||||
gui.job_manager.run_threaded_job(job)
|
gui.job_manager.run_threaded_job(job)
|
||||||
|
|
||||||
class ViewLog(QDialog):
|
class ViewLog(QDialog): # {{{
|
||||||
|
|
||||||
def __init__(self, html, parent=None):
|
def __init__(self, html, parent=None):
|
||||||
QDialog.__init__(self, parent)
|
QDialog.__init__(self, parent)
|
||||||
@ -64,8 +67,11 @@ class ViewLog(QDialog):
|
|||||||
self.bb.ActionRole)
|
self.bb.ActionRole)
|
||||||
self.copy_button.setIcon(QIcon(I('edit-copy.png')))
|
self.copy_button.setIcon(QIcon(I('edit-copy.png')))
|
||||||
self.copy_button.clicked.connect(self.copy_to_clipboard)
|
self.copy_button.clicked.connect(self.copy_to_clipboard)
|
||||||
|
l.addWidget(self.bb)
|
||||||
self.setModal(False)
|
self.setModal(False)
|
||||||
self.resize(self.sizeHint())
|
self.resize(QSize(500, 400))
|
||||||
|
self.setWindowTitle(_('Download log'))
|
||||||
|
self.setWindowIcon(QIcon(I('debug.png')))
|
||||||
self.show()
|
self.show()
|
||||||
|
|
||||||
def copy_to_clipboard(self):
|
def copy_to_clipboard(self):
|
||||||
@ -77,11 +83,118 @@ def view_log(job, parent):
|
|||||||
global _vl
|
global _vl
|
||||||
_vl = ViewLog(job.html_details, parent)
|
_vl = ViewLog(job.html_details, parent)
|
||||||
|
|
||||||
def apply(job, gui, q):
|
# }}}
|
||||||
|
|
||||||
|
class ApplyDialog(QDialog):
|
||||||
|
|
||||||
|
def __init__(self, id_map, gui):
|
||||||
|
QDialog.__init__(self, gui)
|
||||||
|
|
||||||
|
self.l = l = QVBoxLayout()
|
||||||
|
self.setLayout(l)
|
||||||
|
l.addWidget(QLabel(_('Applying downloaded metadata to your library')))
|
||||||
|
|
||||||
|
self.pb = QProgressBar(self)
|
||||||
|
l.addWidget(self.pb)
|
||||||
|
self.pb.setMinimum(0)
|
||||||
|
self.pb.setMaximum(len(id_map))
|
||||||
|
|
||||||
|
self.bb = QDialogButtonBox(QDialogButtonBox.Cancel)
|
||||||
|
self.bb.rejected.connect(self.reject)
|
||||||
|
self.bb.accepted.connect(self.accept)
|
||||||
|
l.addWidget(self.bb)
|
||||||
|
|
||||||
|
self.db = gui.current_db
|
||||||
|
self.id_map = list(id_map.iteritems())
|
||||||
|
self.current_idx = 0
|
||||||
|
|
||||||
|
self.failures = []
|
||||||
|
self.canceled = False
|
||||||
|
|
||||||
|
QTimer.singleShot(20, self.do_one)
|
||||||
|
self.exec_()
|
||||||
|
|
||||||
|
def do_one(self):
|
||||||
|
if self.canceled:
|
||||||
|
return
|
||||||
|
i, mi = self.id_map[self.current_idx]
|
||||||
|
try:
|
||||||
|
set_title = not mi.is_null('title')
|
||||||
|
set_authors = not mi.is_null('authors')
|
||||||
|
self.db.set_metadata(i, mi, commit=False, set_title=set_title,
|
||||||
|
set_authors=set_authors)
|
||||||
|
except:
|
||||||
|
import traceback
|
||||||
|
self.failures.append((i, traceback.format_exc()))
|
||||||
|
|
||||||
|
self.pb.setValue(self.pb.value()+1)
|
||||||
|
|
||||||
|
if self.current_idx >= len(self.id_map) - 1:
|
||||||
|
self.finalize()
|
||||||
|
else:
|
||||||
|
self.current_idx += 1
|
||||||
|
QTimer.singleShot(20, self.do_one)
|
||||||
|
|
||||||
|
def reject(self):
|
||||||
|
self.canceled = True
|
||||||
|
QDialog.reject(self)
|
||||||
|
|
||||||
|
def finalize(self):
|
||||||
|
if self.canceled:
|
||||||
|
return
|
||||||
|
if self.failures:
|
||||||
|
msg = []
|
||||||
|
for i, tb in self.failures:
|
||||||
|
title = self.db.title(i, index_is_id=True)
|
||||||
|
authors = self.db.authors(i, index_is_id=True)
|
||||||
|
if authors:
|
||||||
|
authors = [x.replace('|', ',') for x in authors.split(',')]
|
||||||
|
title += ' - ' + authors_to_string(authors)
|
||||||
|
msg.append(title+'\n\n'+tb+'\n'+('*'*80))
|
||||||
|
|
||||||
|
error_dialog(self, _('Some failures'),
|
||||||
|
_('Failed to apply updated metadata for some books'
|
||||||
|
' in your library. Click "Show Details" to see '
|
||||||
|
'details.'), det_msg='\n\n'.join(msg), show=True)
|
||||||
|
self.accept()
|
||||||
|
|
||||||
|
_amd = None
|
||||||
|
def apply_metadata(job, gui, q, result):
|
||||||
|
global _amd
|
||||||
q.vlb.clicked.disconnect()
|
q.vlb.clicked.disconnect()
|
||||||
q.finished.diconnect()
|
q.finished.disconnect()
|
||||||
|
if result != q.Accepted:
|
||||||
|
return
|
||||||
id_map, failed_ids = job.result
|
id_map, failed_ids = job.result
|
||||||
print (id_map)
|
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
|
||||||
|
|
||||||
|
_amd = ApplyDialog(id_map, gui)
|
||||||
|
|
||||||
def proceed(gui, job):
|
def proceed(gui, job):
|
||||||
id_map, failed_ids = job.result
|
id_map, failed_ids = job.result
|
||||||
@ -90,7 +203,7 @@ def proceed(gui, job):
|
|||||||
fmsg = _('Could not download metadata for %d of the books. Click'
|
fmsg = _('Could not download metadata for %d of the books. Click'
|
||||||
' "Show details" to see which books.')%len(failed_ids)
|
' "Show details" to see which books.')%len(failed_ids)
|
||||||
det_msg = '\n'.join([id_map[i].title for i in failed_ids])
|
det_msg = '\n'.join([id_map[i].title for i in failed_ids])
|
||||||
msg = '<p>' + _('Finished downloading metadata for %d books. '
|
msg = '<p>' + _('Finished downloading metadata for <b>%d book(s)</b>. '
|
||||||
'Proceed with updating the metadata in your library?')%len(id_map)
|
'Proceed with updating the metadata in your library?')%len(id_map)
|
||||||
q = MessageBox(MessageBox.QUESTION, _('Download complete'),
|
q = MessageBox(MessageBox.QUESTION, _('Download complete'),
|
||||||
msg + fmsg, det_msg=det_msg, show_copy_button=bool(failed_ids),
|
msg + fmsg, det_msg=det_msg, show_copy_button=bool(failed_ids),
|
||||||
@ -101,18 +214,26 @@ def proceed(gui, job):
|
|||||||
q.det_msg_toggle.setVisible(bool(failed_ids))
|
q.det_msg_toggle.setVisible(bool(failed_ids))
|
||||||
q.setModal(False)
|
q.setModal(False)
|
||||||
q.show()
|
q.show()
|
||||||
q.finished.connect(partial(job, gui, q))
|
q.finished.connect(partial(apply_metadata, job, gui, q))
|
||||||
|
|
||||||
|
|
||||||
def download(ids, db, log=None, abort=None, notifications=None):
|
def download(ids, db, identify, covers,
|
||||||
log('Starting metadata download for %d books'%len(ids))
|
log=None, abort=None, notifications=None):
|
||||||
ids = list(ids)
|
ids = list(ids)
|
||||||
metadata = [db.get_metadata(i, index_is_id=True, get_user_categories=False)
|
metadata = [db.get_metadata(i, index_is_id=True, get_user_categories=False)
|
||||||
for i in ids]
|
for i in ids]
|
||||||
failed_ids = set()
|
failed_ids = set()
|
||||||
ans = {}
|
ans = {}
|
||||||
|
count = 0
|
||||||
for i, mi in izip(ids, metadata):
|
for i, mi in izip(ids, metadata):
|
||||||
|
if abort.is_set():
|
||||||
|
log.error('Aborting...')
|
||||||
|
break
|
||||||
|
# TODO: Apply ignore_fields and set unchanged values to null values
|
||||||
ans[i] = mi
|
ans[i] = mi
|
||||||
|
count += 1
|
||||||
|
notifications.put((count/len(ids),
|
||||||
|
_('Downloaded %d of %d')%(count, len(ids))))
|
||||||
log('Download complete, with %d failures'%len(failed_ids))
|
log('Download complete, with %d failures'%len(failed_ids))
|
||||||
return (ans, failed_ids)
|
return (ans, failed_ids)
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user