Prevent cross-thread lock errors by having the cover cache get the image on the GUI thread.

This commit is contained in:
Charles Haley 2010-09-21 14:13:03 +01:00
parent acec240ef8
commit ff73865d9e
3 changed files with 36 additions and 5 deletions

View File

@ -1,7 +1,7 @@
__license__ = 'GPL v3'
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
""" The GUI """
import os, sys
import os, sys, Queue
from threading import RLock
from PyQt4.Qt import QVariant, QFileInfo, QObject, SIGNAL, QBuffer, Qt, \
@ -296,6 +296,34 @@ class Dispatcher(QObject):
def dispatch(self, args, kwargs):
self.func(*args, **kwargs)
class FunctionDispatcher(QObject):
'''
Convenience class to use Qt signals with arbitrary python functions.
By default, ensures that a function call always happens in the
thread this Dispatcher was created in.
'''
dispatch_signal = pyqtSignal(object, object, object)
def __init__(self, func, queued=True, parent=None):
QObject.__init__(self, parent)
self.func = func
typ = Qt.QueuedConnection
if not queued:
typ = Qt.AutoConnection if queued is None else Qt.DirectConnection
self.dispatch_signal.connect(self.dispatch, type=typ)
def __call__(self, *args, **kwargs):
q = Queue.Queue()
self.dispatch_signal.emit(q, args, kwargs)
return q.get()
def dispatch(self, q, args, kwargs):
try:
res = self.func(*args, **kwargs)
except:
res = None
q.put(res)
class GetMetadata(QObject):
'''
Convenience class to ensure that metadata readers are used only in the

View File

@ -12,7 +12,7 @@ from operator import attrgetter
from PyQt4.Qt import QAbstractTableModel, Qt, pyqtSignal, QIcon, QImage, \
QModelIndex, QVariant, QDate
from calibre.gui2 import NONE, config, UNDEFINED_QDATE
from calibre.gui2 import NONE, config, UNDEFINED_QDATE, FunctionDispatcher
from calibre.utils.pyparsing import ParseException
from calibre.ebooks.metadata import fmt_sidx, authors_to_string, string_to_authors
from calibre.ptempfile import PersistentTemporaryFile
@ -151,7 +151,7 @@ class BooksModel(QAbstractTableModel): # {{{
self.database_changed.emit(db)
if self.cover_cache is not None:
self.cover_cache.stop()
self.cover_cache = CoverCache(db)
self.cover_cache = CoverCache(db, FunctionDispatcher(self.db.cover))
self.cover_cache.start()
def refresh_cover(event, ids):
if event == 'cover' and self.cover_cache is not None:

View File

@ -23,10 +23,11 @@ from calibre import fit_image
class CoverCache(Thread):
def __init__(self, db):
def __init__(self, db, cover_func):
Thread.__init__(self)
self.daemon = True
self.db = db
self.cover_func = cover_func
self.load_queue = Queue()
self.keep_running = True
self.cache = {}
@ -37,7 +38,9 @@ class CoverCache(Thread):
self.keep_running = False
def _image_for_id(self, id_):
img = self.db.cover(id_, index_is_id=True, as_image=True)
import time
time.sleep(0.050) # Limit 20/second to not overwhelm the GUI
img = self.cover_func(id_, index_is_id=True, as_image=True)
if img is None:
img = QImage()
if not img.isNull():