diff --git a/src/calibre/gui2/main.py b/src/calibre/gui2/main.py index dd8875984d..6d7fd63378 100644 --- a/src/calibre/gui2/main.py +++ b/src/calibre/gui2/main.py @@ -24,6 +24,7 @@ from calibre.gui2 import APP_UID, warning_dialog, choose_files, error_dialog, \ max_available_height, config, info_dialog, \ available_width, GetMetadata 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.update import CheckForUpdates from calibre.gui2.dialogs.progress import ProgressDialog @@ -75,6 +76,7 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI): Ui_MainWindow.__init__(self) self.setupUi(self) self.setWindowTitle(__appname__) + self.progress_indicator = ProgressIndicator(self) self.verbose = opts.verbose self.get_metadata = GetMetadata() self.read_settings() @@ -170,6 +172,9 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI): md.addAction(_('Edit metadata individually')) md.addSeparator() md.addAction(_('Edit metadata in bulk')) + md.addSeparator() + md.addAction(_('Download metadata and covers')) + md.addAction(_('Download only metadata')) self.metadata_menu = md self.add_menu = QMenu() self.add_menu.addAction(_('Add books from a single directory')) @@ -196,6 +201,12 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI): partial(self.edit_metadata, bulk=False)) QObject.connect(md.actions()[2], SIGNAL('triggered(bool)'), 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.addAction(_('Save to disk')) self.save_menu.addAction(_('Save to disk in a single directory')) @@ -824,6 +835,54 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI): ############################################################################ ############################### 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 = ['
  • %s: %s
  • '%(title, reason) for title, + reason in x.failures.values()] + details = '

    '%(''.join(details)) + WarningDialog(_('Failed to download some metadata'), + _('Failed to download metadata for the following:'), + details, self).exec_() + else: + err = _('Failed to download metadata:')+\ + '
    '+x.tb+'
    ' + ConversionErrorDialog(self, _('Error'), err, + show=True) + + + + def edit_metadata(self, checked, bulk=None): ''' Edit metadata of selected books in library. diff --git a/src/calibre/gui2/metadata.py b/src/calibre/gui2/metadata.py new file mode 100644 index 0000000000..607fd6a41b --- /dev/null +++ b/src/calibre/gui2/metadata.py @@ -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 ' +__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 + +