Cover cache: load images only in the GUI thread to prevent stale files being leftover by set_path due to Windows file locking

This commit is contained in:
Kovid Goyal 2010-09-21 11:56:09 -06:00
parent 4ff11b5171
commit 52f85d3ef4
3 changed files with 36 additions and 6 deletions

View File

@ -1,7 +1,7 @@
__license__ = 'GPL v3' __license__ = 'GPL v3'
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>' __copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
""" The GUI """ """ The GUI """
import os, sys import os, sys, Queue
from threading import RLock from threading import RLock
from PyQt4.Qt import QVariant, QFileInfo, QObject, SIGNAL, QBuffer, Qt, \ from PyQt4.Qt import QVariant, QFileInfo, QObject, SIGNAL, QBuffer, Qt, \
@ -296,6 +296,34 @@ class Dispatcher(QObject):
def dispatch(self, args, kwargs): def dispatch(self, args, kwargs):
self.func(*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): class GetMetadata(QObject):
''' '''
Convenience class to ensure that metadata readers are used only in the 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, \ from PyQt4.Qt import QAbstractTableModel, Qt, pyqtSignal, QIcon, QImage, \
QModelIndex, QVariant, QDate 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.utils.pyparsing import ParseException
from calibre.ebooks.metadata import fmt_sidx, authors_to_string, string_to_authors from calibre.ebooks.metadata import fmt_sidx, authors_to_string, string_to_authors
from calibre.ptempfile import PersistentTemporaryFile from calibre.ptempfile import PersistentTemporaryFile
@ -151,7 +151,7 @@ class BooksModel(QAbstractTableModel): # {{{
self.database_changed.emit(db) self.database_changed.emit(db)
if self.cover_cache is not None: if self.cover_cache is not None:
self.cover_cache.stop() self.cover_cache.stop()
self.cover_cache = CoverCache(db) self.cover_cache = CoverCache(db, FunctionDispatcher(self.db.cover))
self.cover_cache.start() self.cover_cache.start()
def refresh_cover(event, ids): def refresh_cover(event, ids):
if event == 'cover' and self.cover_cache is not None: if event == 'cover' and self.cover_cache is not None:

View File

@ -6,7 +6,7 @@ __license__ = 'GPL v3'
__copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>' __copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en' __docformat__ = 'restructuredtext en'
import re, itertools import re, itertools, time
from itertools import repeat from itertools import repeat
from datetime import timedelta from datetime import timedelta
from threading import Thread, RLock from threading import Thread, RLock
@ -23,10 +23,11 @@ from calibre import fit_image
class CoverCache(Thread): class CoverCache(Thread):
def __init__(self, db): def __init__(self, db, cover_func):
Thread.__init__(self) Thread.__init__(self)
self.daemon = True self.daemon = True
self.db = db self.db = db
self.cover_func = cover_func
self.load_queue = Queue() self.load_queue = Queue()
self.keep_running = True self.keep_running = True
self.cache = {} self.cache = {}
@ -37,7 +38,8 @@ class CoverCache(Thread):
self.keep_running = False self.keep_running = False
def _image_for_id(self, id_): def _image_for_id(self, id_):
img = self.db.cover(id_, index_is_id=True, as_image=True) 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: if img is None:
img = QImage() img = QImage()
if not img.isNull(): if not img.isNull():