mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
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:
parent
4ff11b5171
commit
52f85d3ef4
@ -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
|
||||
|
@ -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:
|
||||
|
@ -6,7 +6,7 @@ __license__ = 'GPL v3'
|
||||
__copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
import re, itertools
|
||||
import re, itertools, time
|
||||
from itertools import repeat
|
||||
from datetime import timedelta
|
||||
from threading import Thread, RLock
|
||||
@ -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,8 @@ 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)
|
||||
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():
|
||||
|
Loading…
x
Reference in New Issue
Block a user