Pull from trunk

This commit is contained in:
Kovid Goyal 2009-05-06 19:52:51 -07:00
commit 2ba35a8be8
3 changed files with 192 additions and 9 deletions

View File

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

View File

@ -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:

View 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