Disable the cover cache. This means that if you are running calibre on an underpowered machine, you might notice some slow down in the cover browser. On the other hand, calibre's memory consumption will be reduced.

This commit is contained in:
Kovid Goyal 2010-12-16 13:55:10 -07:00
parent 818c4249d2
commit 05ed274726
3 changed files with 6 additions and 158 deletions

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, FunctionDispatcher from calibre.gui2 import NONE, config, UNDEFINED_QDATE
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
@ -22,7 +22,7 @@ from calibre.utils.icu import sort_key, strcmp as icu_strcmp
from calibre.ebooks.metadata.meta import set_metadata as _set_metadata from calibre.ebooks.metadata.meta import set_metadata as _set_metadata
from calibre.utils.search_query_parser import SearchQueryParser from calibre.utils.search_query_parser import SearchQueryParser
from calibre.library.caches import _match, CONTAINS_MATCH, EQUALS_MATCH, \ from calibre.library.caches import _match, CONTAINS_MATCH, EQUALS_MATCH, \
REGEXP_MATCH, CoverCache, MetadataBackup REGEXP_MATCH, MetadataBackup
from calibre.library.cli import parse_series_string from calibre.library.cli import parse_series_string
from calibre import strftime, isbytestring, prepare_string_for_xml from calibre import strftime, isbytestring, prepare_string_for_xml
from calibre.constants import filesystem_encoding, DEBUG from calibre.constants import filesystem_encoding, DEBUG
@ -89,7 +89,6 @@ class BooksModel(QAbstractTableModel): # {{{
self.headers = {} self.headers = {}
self.alignment_map = {} self.alignment_map = {}
self.buffer_size = buffer self.buffer_size = buffer
self.cover_cache = None
self.metadata_backup = None self.metadata_backup = None
self.bool_yes_icon = QIcon(I('ok.png')) self.bool_yes_icon = QIcon(I('ok.png'))
self.bool_no_icon = QIcon(I('list_remove.png')) self.bool_no_icon = QIcon(I('list_remove.png'))
@ -113,10 +112,6 @@ class BooksModel(QAbstractTableModel): # {{{
def is_custom_column(self, cc_label): def is_custom_column(self, cc_label):
return cc_label in self.custom_columns return cc_label in self.custom_columns
def clear_caches(self):
if self.cover_cache:
self.cover_cache.clear_cache()
def read_config(self): def read_config(self):
self.use_roman_numbers = config['use_roman_numerals_for_series_number'] self.use_roman_numbers = config['use_roman_numerals_for_series_number']
@ -154,18 +149,8 @@ class BooksModel(QAbstractTableModel): # {{{
self.build_data_convertors() self.build_data_convertors()
self.reset() self.reset()
self.database_changed.emit(db) self.database_changed.emit(db)
if self.cover_cache is not None:
self.cover_cache.stop()
# Would like to to a join here, but the thread might be waiting to
# do something on the GUI thread. Deadlock.
self.cover_cache = CoverCache(db, FunctionDispatcher(self.db.cover))
self.cover_cache.start()
self.stop_metadata_backup() self.stop_metadata_backup()
self.start_metadata_backup() self.start_metadata_backup()
def refresh_cover(event, ids):
if event == 'cover' and self.cover_cache is not None:
self.cover_cache.refresh(ids)
db.add_listener(refresh_cover)
def start_metadata_backup(self): def start_metadata_backup(self):
self.metadata_backup = MetadataBackup(self.db) self.metadata_backup = MetadataBackup(self.db)
@ -225,7 +210,6 @@ class BooksModel(QAbstractTableModel): # {{{
def books_deleted(self): def books_deleted(self):
self.count_changed() self.count_changed()
self.clear_caches()
self.reset() self.reset()
def delete_books(self, indices): def delete_books(self, indices):
@ -254,7 +238,6 @@ class BooksModel(QAbstractTableModel): # {{{
return return
self.last_search = text self.last_search = text
if reset: if reset:
self.clear_caches()
self.reset() self.reset()
if self.last_search: if self.last_search:
# Do not issue search done for the null search. It is used to clear # Do not issue search done for the null search. It is used to clear
@ -269,7 +252,6 @@ class BooksModel(QAbstractTableModel): # {{{
label = self.column_map[col] label = self.column_map[col]
self.db.sort(label, ascending) self.db.sort(label, ascending)
if reset: if reset:
self.clear_caches()
self.reset() self.reset()
self.sorted_on = (label, order) self.sorted_on = (label, order)
self.sort_history.insert(0, self.sorted_on) self.sort_history.insert(0, self.sorted_on)
@ -357,26 +339,10 @@ class BooksModel(QAbstractTableModel): # {{{
data[name] = val data[name] = val
return data return data
def set_cache(self, idx):
l, r = 0, self.count()-1
if self.cover_cache is not None:
l = max(l, idx-self.buffer_size)
r = min(r, idx+self.buffer_size)
k = min(r-idx, idx-l)
ids = [idx]
for i in range(1, k):
ids.extend([idx-i, idx+i])
ids = ids + [i for i in range(l, r, 1) if i not in ids]
try:
ids = [self.db.id(i) for i in ids]
except IndexError:
return
self.cover_cache.set_cache(ids)
def current_changed(self, current, previous, emit_signal=True): def current_changed(self, current, previous, emit_signal=True):
if current.isValid(): if current.isValid():
idx = current.row() idx = current.row()
self.set_cache(idx)
data = self.get_book_display_info(idx) data = self.get_book_display_info(idx)
if emit_signal: if emit_signal:
self.new_bookdisplay_data.emit(data) self.new_bookdisplay_data.emit(data)
@ -533,13 +499,7 @@ class BooksModel(QAbstractTableModel): # {{{
def cover(self, row_number): def cover(self, row_number):
data = None data = None
try: try:
id = self.db.id(row_number) data = self.db.cover(row_number)
if self.cover_cache is not None:
img = self.cover_cache.cover(id)
if not img.isNull():
return img
if not data:
data = self.db.cover(row_number)
except IndexError: # Happens if database has not yet been refreshed except IndexError: # Happens if database has not yet been refreshed
pass pass

View File

@ -583,9 +583,6 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{
while self.spare_servers: while self.spare_servers:
self.spare_servers.pop().close() self.spare_servers.pop().close()
self.device_manager.keep_going = False self.device_manager.keep_going = False
cc = self.library_view.model().cover_cache
if cc is not None:
cc.stop()
mb = self.library_view.model().metadata_backup mb = self.library_view.model().metadata_backup
if mb is not None: if mb is not None:
mb.stop() mb.stop()

View File

@ -9,10 +9,8 @@ __docformat__ = 'restructuredtext en'
import re, itertools, time, traceback import re, itertools, time, traceback
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
from Queue import Queue, Empty from Queue import Empty
from PyQt4.Qt import QImage, Qt
from calibre.utils.config import tweaks from calibre.utils.config import tweaks
from calibre.utils.date import parse_date, now, UNDEFINED_DATE from calibre.utils.date import parse_date, now, UNDEFINED_DATE
@ -20,7 +18,7 @@ from calibre.utils.search_query_parser import SearchQueryParser
from calibre.utils.pyparsing import ParseException from calibre.utils.pyparsing import ParseException
from calibre.ebooks.metadata import title_sort from calibre.ebooks.metadata import title_sort
from calibre.ebooks.metadata.opf2 import metadata_to_opf from calibre.ebooks.metadata.opf2 import metadata_to_opf
from calibre import fit_image, prints from calibre import prints
class MetadataBackup(Thread): # {{{ class MetadataBackup(Thread): # {{{
''' '''
@ -118,113 +116,6 @@ class MetadataBackup(Thread): # {{{
# }}} # }}}
class CoverCache(Thread): # {{{
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 = {}
self.lock = RLock()
self.allowed_ids = frozenset([])
self.null_image = QImage()
def stop(self):
self.keep_running = False
def _image_for_id(self, id_):
img = self.cover_func(id_, index_is_id=True, as_image=True)
if img is None:
img = QImage()
if not img.isNull():
scaled, nwidth, nheight = fit_image(img.width(),
img.height(), 600, 800)
if scaled:
img = img.scaled(nwidth, nheight, Qt.KeepAspectRatio,
Qt.SmoothTransformation)
return img
def run(self):
while self.keep_running:
try:
# The GUI puts the same ID into the queue many times. The code
# below emptys the queue, building a set of unique values. When
# the queue is empty, do the work
ids = set()
id_ = self.load_queue.get(True, 2)
ids.add(id_)
try:
while True:
# Give the gui some time to put values into the queue
id_ = self.load_queue.get(True, 0.5)
ids.add(id_)
except Empty:
pass
except:
# Happens during shutdown
break
except Empty:
continue
except:
#Happens during interpreter shutdown
break
if not self.keep_running:
break
for id_ in ids:
time.sleep(0.050) # Limit 20/second to not overwhelm the GUI
if not self.keep_running:
return
with self.lock:
if id_ not in self.allowed_ids:
continue
try:
img = self._image_for_id(id_)
except:
try:
traceback.print_exc()
except:
# happens during shutdown
break
continue
try:
with self.lock:
self.cache[id_] = img
except:
# Happens during interpreter shutdown
break
def set_cache(self, ids):
with self.lock:
self.allowed_ids = frozenset(ids)
already_loaded = set([])
for id in self.cache.keys():
if id in ids:
already_loaded.add(id)
else:
self.cache.pop(id)
for id_ in set(ids) - already_loaded:
self.load_queue.put(id_)
def cover(self, id_):
with self.lock:
return self.cache.get(id_, self.null_image)
def clear_cache(self):
with self.lock:
self.cache = {}
def refresh(self, ids):
with self.lock:
for id_ in ids:
cover = self.cache.pop(id_, None)
if cover is not None:
self.load_queue.put(id_)
# }}}
### Global utility function for get_match here and in gui2/library.py ### Global utility function for get_match here and in gui2/library.py
CONTAINS_MATCH = 0 CONTAINS_MATCH = 0
EQUALS_MATCH = 1 EQUALS_MATCH = 1