mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-08 18:54:09 -04:00
Pull from trunk
This commit is contained in:
commit
2ba35a8be8
@ -12,6 +12,7 @@ from PyQt4.Qt import QMenu, QAction, QActionGroup, QIcon, SIGNAL, QPixmap, \
|
|||||||
|
|
||||||
from calibre.customize.ui import available_input_formats, available_output_formats, \
|
from calibre.customize.ui import available_input_formats, available_output_formats, \
|
||||||
device_plugins
|
device_plugins
|
||||||
|
from calibre.devices.interface import DevicePlugin
|
||||||
from calibre.constants import iswindows
|
from calibre.constants import iswindows
|
||||||
from calibre.gui2.dialogs.choose_format import ChooseFormatDialog
|
from calibre.gui2.dialogs.choose_format import ChooseFormatDialog
|
||||||
from calibre.parallel import Job
|
from calibre.parallel import Job
|
||||||
@ -27,6 +28,11 @@ from calibre.devices.errors import FreeSpaceError
|
|||||||
from calibre.utils.smtp import compose_mail, sendmail, extract_email_address, \
|
from calibre.utils.smtp import compose_mail, sendmail, extract_email_address, \
|
||||||
config as email_config
|
config as email_config
|
||||||
|
|
||||||
|
def warning(title, msg, details, parent):
|
||||||
|
from calibre.gui2.widgets import WarningDialog
|
||||||
|
WarningDialog(title, msg, details, parent).exec_()
|
||||||
|
|
||||||
|
|
||||||
class DeviceJob(Job):
|
class DeviceJob(Job):
|
||||||
|
|
||||||
def __init__(self, func, *args, **kwargs):
|
def __init__(self, func, *args, **kwargs):
|
||||||
@ -541,7 +547,7 @@ class DeviceGUI(object):
|
|||||||
p.loadFromData(data)
|
p.loadFromData(data)
|
||||||
if not p.isNull():
|
if not p.isNull():
|
||||||
ht = self.device_manager.device_class.THUMBNAIL_HEIGHT \
|
ht = self.device_manager.device_class.THUMBNAIL_HEIGHT \
|
||||||
if self.device_manager else Device.THUMBNAIL_HEIGHT
|
if self.device_manager else DevicePlugin.THUMBNAIL_HEIGHT
|
||||||
p = p.scaledToHeight(ht, Qt.SmoothTransformation)
|
p = p.scaledToHeight(ht, Qt.SmoothTransformation)
|
||||||
return (p.width(), p.height(), pixmap_to_data(p))
|
return (p.width(), p.height(), pixmap_to_data(p))
|
||||||
|
|
||||||
|
@ -24,6 +24,7 @@ from calibre.gui2 import APP_UID, warning_dialog, choose_files, error_dialog, \
|
|||||||
max_available_height, config, info_dialog, \
|
max_available_height, config, info_dialog, \
|
||||||
available_width, GetMetadata
|
available_width, GetMetadata
|
||||||
from calibre.gui2.cover_flow import CoverFlow, DatabaseImages, pictureflowerror
|
from calibre.gui2.cover_flow import CoverFlow, DatabaseImages, pictureflowerror
|
||||||
|
from calibre.gui2.widgets import ProgressIndicator, WarningDialog
|
||||||
from calibre.gui2.dialogs.scheduler import Scheduler
|
from calibre.gui2.dialogs.scheduler import Scheduler
|
||||||
from calibre.gui2.update import CheckForUpdates
|
from calibre.gui2.update import CheckForUpdates
|
||||||
from calibre.gui2.dialogs.progress import ProgressDialog
|
from calibre.gui2.dialogs.progress import ProgressDialog
|
||||||
@ -74,6 +75,7 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
|
|||||||
Ui_MainWindow.__init__(self)
|
Ui_MainWindow.__init__(self)
|
||||||
self.setupUi(self)
|
self.setupUi(self)
|
||||||
self.setWindowTitle(__appname__)
|
self.setWindowTitle(__appname__)
|
||||||
|
self.progress_indicator = ProgressIndicator(self)
|
||||||
self.verbose = opts.verbose
|
self.verbose = opts.verbose
|
||||||
self.get_metadata = GetMetadata()
|
self.get_metadata = GetMetadata()
|
||||||
self.read_settings()
|
self.read_settings()
|
||||||
@ -169,6 +171,9 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
|
|||||||
md.addAction(_('Edit metadata individually'))
|
md.addAction(_('Edit metadata individually'))
|
||||||
md.addSeparator()
|
md.addSeparator()
|
||||||
md.addAction(_('Edit metadata in bulk'))
|
md.addAction(_('Edit metadata in bulk'))
|
||||||
|
md.addSeparator()
|
||||||
|
md.addAction(_('Download metadata and covers'))
|
||||||
|
md.addAction(_('Download only metadata'))
|
||||||
self.metadata_menu = md
|
self.metadata_menu = md
|
||||||
self.add_menu = QMenu()
|
self.add_menu = QMenu()
|
||||||
self.add_menu.addAction(_('Add books from a single directory'))
|
self.add_menu.addAction(_('Add books from a single directory'))
|
||||||
@ -195,6 +200,12 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
|
|||||||
partial(self.edit_metadata, bulk=False))
|
partial(self.edit_metadata, bulk=False))
|
||||||
QObject.connect(md.actions()[2], SIGNAL('triggered(bool)'),
|
QObject.connect(md.actions()[2], SIGNAL('triggered(bool)'),
|
||||||
partial(self.edit_metadata, bulk=True))
|
partial(self.edit_metadata, bulk=True))
|
||||||
|
QObject.connect(md.actions()[4], SIGNAL('triggered(bool)'),
|
||||||
|
partial(self.download_metadata, covers=True))
|
||||||
|
QObject.connect(md.actions()[5], SIGNAL('triggered(bool)'),
|
||||||
|
partial(self.download_metadata, covers=False))
|
||||||
|
|
||||||
|
|
||||||
self.save_menu = QMenu()
|
self.save_menu = QMenu()
|
||||||
self.save_menu.addAction(_('Save to disk'))
|
self.save_menu.addAction(_('Save to disk'))
|
||||||
self.save_menu.addAction(_('Save to disk in a single directory'))
|
self.save_menu.addAction(_('Save to disk in a single directory'))
|
||||||
@ -821,6 +832,54 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
|
|||||||
############################################################################
|
############################################################################
|
||||||
|
|
||||||
############################### Edit metadata ##############################
|
############################### Edit metadata ##############################
|
||||||
|
|
||||||
|
def download_metadata(self, checked, covers=True):
|
||||||
|
rows = self.library_view.selectionModel().selectedRows()
|
||||||
|
previous = self.library_view.currentIndex()
|
||||||
|
if not rows or len(rows) == 0:
|
||||||
|
d = error_dialog(self, _('Cannot download metadata'),
|
||||||
|
_('No books selected'))
|
||||||
|
d.exec_()
|
||||||
|
return
|
||||||
|
db = self.library_view.model().db
|
||||||
|
ids = [db.id(row.row()) for row in rows]
|
||||||
|
from calibre.gui2.metadata import DownloadMetadata
|
||||||
|
self._download_book_metadata = DownloadMetadata(db, ids, get_covers=covers)
|
||||||
|
self._download_book_metadata.start()
|
||||||
|
self.progress_indicator.start(
|
||||||
|
_('Downloading metadata for %d book(s)')%len(ids))
|
||||||
|
self._book_metadata_download_check = QTimer(self)
|
||||||
|
self.connect(self._book_metadata_download_check,
|
||||||
|
SIGNAL('timeout()'), self.book_metadata_download_check)
|
||||||
|
self._book_metadata_download_check.start(100)
|
||||||
|
|
||||||
|
def book_metadata_download_check(self):
|
||||||
|
if self._download_book_metadata.is_alive():
|
||||||
|
return
|
||||||
|
self._book_metadata_download_check.stop()
|
||||||
|
self.progress_indicator.stop()
|
||||||
|
cr = self.library_view.currentIndex().row()
|
||||||
|
x = self._download_book_metadata
|
||||||
|
self._download_book_metadata = None
|
||||||
|
if x.exception is None:
|
||||||
|
db = self.library_view.model().refresh_ids(
|
||||||
|
x.updated, cr)
|
||||||
|
if x.failures:
|
||||||
|
details = ['<li><b>%s:</b> %s</li>'%(title, reason) for title,
|
||||||
|
reason in x.failures.values()]
|
||||||
|
details = '<p><ul>%s</ul></p>'%(''.join(details))
|
||||||
|
WarningDialog(_('Failed to download some metadata'),
|
||||||
|
_('Failed to download metadata for the following:'),
|
||||||
|
details, self).exec_()
|
||||||
|
else:
|
||||||
|
err = _('<b>Failed to download metadata:')+\
|
||||||
|
'</b><br><pre>'+x.tb+'</pre>'
|
||||||
|
ConversionErrorDialog(self, _('Error'), err,
|
||||||
|
show=True)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def edit_metadata(self, checked, bulk=None):
|
def edit_metadata(self, checked, bulk=None):
|
||||||
'''
|
'''
|
||||||
Edit metadata of selected books in library.
|
Edit metadata of selected books in library.
|
||||||
@ -1081,8 +1140,9 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
|
|||||||
#############################View book######################################
|
#############################View book######################################
|
||||||
|
|
||||||
def view_format(self, row, format):
|
def view_format(self, row, format):
|
||||||
self._view_file(self.library_view.model().db.format(row,
|
fmt_path = self.library_view.model().db.format_abspath(row, format)
|
||||||
format, as_file=True).name)
|
if fmt_path:
|
||||||
|
self._view_file(fmt_path)
|
||||||
|
|
||||||
def book_downloaded_for_viewing(self, job):
|
def book_downloaded_for_viewing(self, job):
|
||||||
if job.exception:
|
if job.exception:
|
||||||
|
117
src/calibre/gui2/metadata.py
Normal file
117
src/calibre/gui2/metadata.py
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
|
||||||
|
from __future__ import with_statement
|
||||||
|
|
||||||
|
__license__ = 'GPL v3'
|
||||||
|
__copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>'
|
||||||
|
__docformat__ = 'restructuredtext en'
|
||||||
|
|
||||||
|
from threading import Thread
|
||||||
|
from Queue import Queue, Empty
|
||||||
|
|
||||||
|
|
||||||
|
from calibre.ebooks.metadata.fetch import search
|
||||||
|
from calibre.utils.config import prefs
|
||||||
|
from calibre.ebooks.metadata.library_thing import cover_from_isbn
|
||||||
|
|
||||||
|
class Worker(Thread):
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
Thread.__init__(self)
|
||||||
|
self.setDaemon(True)
|
||||||
|
self.jobs = Queue()
|
||||||
|
self.results = Queue()
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
while True:
|
||||||
|
isbn = self.jobs.get()
|
||||||
|
if not isbn:
|
||||||
|
break
|
||||||
|
cdata, _ = cover_from_isbn(isbn)
|
||||||
|
if cdata:
|
||||||
|
self.results.put((isbn, cdata))
|
||||||
|
|
||||||
|
def __enter__(self):
|
||||||
|
self.start()
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __exit__(self, *args):
|
||||||
|
self.jobs.put(False)
|
||||||
|
|
||||||
|
|
||||||
|
class DownloadMetadata(Thread):
|
||||||
|
|
||||||
|
def __init__(self, db, ids, get_covers):
|
||||||
|
Thread.__init__(self)
|
||||||
|
self.setDaemon(True)
|
||||||
|
self.metadata = {}
|
||||||
|
self.covers = {}
|
||||||
|
self.db = db
|
||||||
|
self.updated = set([])
|
||||||
|
self.get_covers = get_covers
|
||||||
|
self.worker = Worker()
|
||||||
|
for id in ids:
|
||||||
|
self.metadata[id] = db.get_metadata(id, index_is_id=True)
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
self.exception = self.tb = None
|
||||||
|
try:
|
||||||
|
self._run()
|
||||||
|
except Exception, e:
|
||||||
|
self.exception = e
|
||||||
|
import traceback
|
||||||
|
self.tb = traceback.format_exc()
|
||||||
|
|
||||||
|
def _run(self):
|
||||||
|
self.key = prefs['isbndb_com_key']
|
||||||
|
if not self.key:
|
||||||
|
self.key = None
|
||||||
|
self.fetched_metadata = {}
|
||||||
|
self.failures = {}
|
||||||
|
with self.worker:
|
||||||
|
for id, mi in self.metadata.items():
|
||||||
|
args = {}
|
||||||
|
if mi.isbn:
|
||||||
|
args['isbn'] = mi.isbn
|
||||||
|
else:
|
||||||
|
if not mi.title:
|
||||||
|
self.failures[id] = \
|
||||||
|
(str(id), _('Book has neither title nor ISBN'))
|
||||||
|
continue
|
||||||
|
args['title'] = mi.title
|
||||||
|
if mi.authors:
|
||||||
|
args['author'] = mi.authors[0]
|
||||||
|
if self.key:
|
||||||
|
args['isbndb_key'] = self.key
|
||||||
|
results, exceptions = search(**args)
|
||||||
|
if results:
|
||||||
|
fmi = results[0]
|
||||||
|
self.fetched_metadata[id] = fmi
|
||||||
|
if fmi.isbn and self.get_covers:
|
||||||
|
self.worker.jobs.put(fmi.isbn)
|
||||||
|
mi.smart_update(fmi)
|
||||||
|
else:
|
||||||
|
self.failures[id] = (mi.title,
|
||||||
|
_('No matches found for this book'))
|
||||||
|
self.commit_covers()
|
||||||
|
|
||||||
|
self.commit_covers(True)
|
||||||
|
for id in self.fetched_metadata:
|
||||||
|
self.db.set_metadata(id, self.metadata[id])
|
||||||
|
self.updated = set(self.fetched_metadata)
|
||||||
|
|
||||||
|
|
||||||
|
def commit_covers(self, all=False):
|
||||||
|
if all:
|
||||||
|
self.worker.jobs.put(False)
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
isbn, cdata = self.worker.results.get(False)
|
||||||
|
for id, mi in self.metadata.items():
|
||||||
|
if mi.isbn == isbn:
|
||||||
|
self.db.set_cover(id, cdata)
|
||||||
|
except Empty:
|
||||||
|
if not all or not self.worker.is_alive():
|
||||||
|
return
|
||||||
|
|
||||||
|
|
Loading…
x
Reference in New Issue
Block a user