From 3523ecaf5ffb76ac5460f717753586e6ff9b64e7 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 6 May 2009 13:43:01 -0700 Subject: [PATCH 1/3] Fix #2402 (Get error when I plug in my kindle 2) --- src/calibre/gui2/main.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/calibre/gui2/main.py b/src/calibre/gui2/main.py index 09f95033f1..dd8875984d 100644 --- a/src/calibre/gui2/main.py +++ b/src/calibre/gui2/main.py @@ -1060,8 +1060,9 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI): #############################View book###################################### def view_format(self, row, format): - self._view_file(self.library_view.model().db.format(row, - format, as_file=True).name) + fmt_path = self.library_view.model().db.format_abspath(row, format) + if fmt_path: + self._view_file(fmt_path) def book_downloaded_for_viewing(self, job): if job.exception: From 4f643a17f680b4aeecb45656ae0b0a57e12bd53a Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 6 May 2009 15:19:30 -0700 Subject: [PATCH 2/3] Fix #2390 (Error window when a large number books cannot be transferred to a device because of incompatible formats is too large for dis) --- src/calibre/gui2/device.py | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/src/calibre/gui2/device.py b/src/calibre/gui2/device.py index 61869cce34..a29666090b 100644 --- a/src/calibre/gui2/device.py +++ b/src/calibre/gui2/device.py @@ -16,7 +16,7 @@ from calibre.gui2.dialogs.choose_format import ChooseFormatDialog from calibre.parallel import Job from calibre.devices.scanner import DeviceScanner from calibre.gui2 import config, error_dialog, Dispatcher, dynamic, \ - pixmap_to_data, warning_dialog + pixmap_to_data from calibre.ebooks.metadata import authors_to_string from calibre.gui2.dialogs.conversion_error import ConversionErrorDialog from calibre.devices.interface import Device @@ -26,6 +26,11 @@ from calibre.devices.errors import FreeSpaceError from calibre.utils.smtp import compose_mail, sendmail, extract_email_address, \ 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): def __init__(self, func, *args, **kwargs): @@ -478,11 +483,11 @@ class DeviceGUI(object): self.status_bar.showMessage(_('Sending email to')+' '+to, 3000) if bad: - bad = '\n'.join('
  • %s
  • '%(i,) for i in bad) - d = warning_dialog(self, _('No suitable formats'), - '

    '+ _('Could not email the following books ' - 'as no suitable formats were found:

    ')%(bad,)) - d.exec_() + bad = u'\n'.join(u'
  • %s
  • '%(i,) for i in bad) + details = u'

    '%bad + warning(_('No suitable formats'), + _('Could not email the following books ' + 'as no suitable formats were found:'), details, self) def emails_sent(self, results, remove=[]): errors, good = [], [] @@ -624,13 +629,13 @@ class DeviceGUI(object): self.upload_books(gf, names, good, on_card, memory=(_files, remove)) self.status_bar.showMessage(_('Sending books to device.'), 5000) if bad: - bad = '\n'.join('
  • %s
  • '%(i,) for i in bad) - d = warning_dialog(self, _('No suitable formats'), + bad = u'\n'.join(u'
  • %s
  • '%(i,) for i in bad) + details = u'

    '%bad + warning(_('No suitable formats'), _('Could not upload the following books to the device, ' 'as no suitable formats were found. Try changing the output ' 'format in the upper right corner next to the red heart and ' - 're-converting.
    ')%(bad,)) - d.exec_() + 're-converting.'), details, self) def upload_booklists(self): ''' From ecdb71c88d049c0c131f22c789343d2d9a150453 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 6 May 2009 19:46:03 -0700 Subject: [PATCH 3/3] Implement single click download of metadata and covers for all sleected books in the library. To access click the arrow next to the Edit metadata button. Fixes #2376 (Enhancement request: Additional buttons on main interface) --- src/calibre/gui2/main.py | 59 ++++++++++++++++++ src/calibre/gui2/metadata.py | 117 +++++++++++++++++++++++++++++++++++ 2 files changed, 176 insertions(+) create mode 100644 src/calibre/gui2/metadata.py 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 = '

      %s

    '%(''.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 + +