Add thread to GUI for distributed metadata backup

This commit is contained in:
Kovid Goyal 2010-09-23 23:50:22 -06:00
parent 1ad0eebd56
commit f46d919c75
4 changed files with 45 additions and 9 deletions

View File

@ -21,7 +21,7 @@ from calibre.utils.date import dt_factory, qt_to_dt, isoformat
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 REGEXP_MATCH, CoverCache, 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 from calibre.constants import filesystem_encoding
@ -153,6 +153,9 @@ class BooksModel(QAbstractTableModel): # {{{
self.cover_cache.stop() self.cover_cache.stop()
self.cover_cache = CoverCache(db, FunctionDispatcher(self.db.cover)) self.cover_cache = CoverCache(db, FunctionDispatcher(self.db.cover))
self.cover_cache.start() self.cover_cache.start()
self.metadata_backup = MetadataBackup(db,
FunctionDispatcher(self.db.dump_metadata))
self.metadata_backup.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:
self.cover_cache.refresh(ids) self.cover_cache.refresh(ids)

View File

@ -551,6 +551,10 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, # {{{
cc = self.library_view.model().cover_cache cc = self.library_view.model().cover_cache
if cc is not None: if cc is not None:
cc.stop() cc.stop()
mb = self.library_view.model().metadata_backup
if mb is not None:
mb.stop()
self.hide_windows() self.hide_windows()
self.emailer.stop() self.emailer.stop()
try: try:

View File

@ -21,7 +21,31 @@ from calibre.utils.pyparsing import ParseException
from calibre.ebooks.metadata import title_sort from calibre.ebooks.metadata import title_sort
from calibre import fit_image from calibre import fit_image
class CoverCache(Thread): class MetadataBackup(Thread): # {{{
def __init__(self, db, dump_func):
Thread.__init__(self)
self.daemon = True
self.db = db
self.dump_func = dump_func
self.keep_running = True
def stop(self):
self.keep_running = False
def run(self):
while self.keep_running:
try:
id_ = self.db.dirtied_queue.get(True, 5)
except Empty:
continue
# If there is an exception is dump_func, we
# have no way of knowing
self.dump_func([id_])
# }}}
class CoverCache(Thread): # {{{
def __init__(self, db, cover_func): def __init__(self, db, cover_func):
Thread.__init__(self) Thread.__init__(self)
@ -90,6 +114,7 @@ class CoverCache(Thread):
for id_ in ids: for id_ in ids:
self.cache.pop(id_, None) self.cache.pop(id_, None)
self.load_queue.put(id_) 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
@ -107,7 +132,7 @@ def _match(query, value, matchkind):
pass pass
return False return False
class ResultCache(SearchQueryParser): class ResultCache(SearchQueryParser): # {{{
''' '''
Stores sorted and filtered metadata in memory. Stores sorted and filtered metadata in memory.
@ -694,4 +719,5 @@ class SortKeyGenerator(object):
# }}} # }}}
# }}}

View File

@ -9,6 +9,7 @@ The database used to store ebook metadata
import os, sys, shutil, cStringIO, glob, time, functools, traceback, re import os, sys, shutil, cStringIO, glob, time, functools, traceback, re
from itertools import repeat from itertools import repeat
from math import floor from math import floor
from Queue import Queue
from PyQt4.QtGui import QImage from PyQt4.QtGui import QImage
@ -127,7 +128,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
def __init__(self, library_path, row_factory=False): def __init__(self, library_path, row_factory=False):
self.field_metadata = FieldMetadata() self.field_metadata = FieldMetadata()
self.dirtied_cache = set([]) self.dirtied_queue = Queue()
if not os.path.exists(library_path): if not os.path.exists(library_path):
os.makedirs(library_path) os.makedirs(library_path)
self.listeners = set([]) self.listeners = set([])
@ -340,7 +341,8 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
loc=self.FIELD_MAP['sort'])) loc=self.FIELD_MAP['sort']))
d = self.conn.get('SELECT book FROM metadata_dirtied', all=True) d = self.conn.get('SELECT book FROM metadata_dirtied', all=True)
self.dirtied_cache.update(set([x[0] for x in d])) for x in d:
self.dirtied_queue.put(x[0])
self.refresh_ondevice = functools.partial(self.data.refresh_ondevice, self) self.refresh_ondevice = functools.partial(self.data.refresh_ondevice, self)
self.refresh() self.refresh()
@ -557,6 +559,8 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
def dump_metadata(self, book_ids, remove_from_dirtied=True, commit=True): def dump_metadata(self, book_ids, remove_from_dirtied=True, commit=True):
for book_id in book_ids: for book_id in book_ids:
if not self.data.has_id(book_id):
continue
mi = self.get_metadata(book_id, index_is_id=True, get_cover=True) mi = self.get_metadata(book_id, index_is_id=True, get_cover=True)
# Always set cover to cover.jpg. Even if cover doesn't exist, # Always set cover to cover.jpg. Even if cover doesn't exist,
# no harm done. This way no need to call dirtied when # no harm done. This way no need to call dirtied when
@ -569,18 +573,17 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
if remove_from_dirtied: if remove_from_dirtied:
self.conn.execute('DELETE FROM metadata_dirtied WHERE book=?', self.conn.execute('DELETE FROM metadata_dirtied WHERE book=?',
(book_id,)) (book_id,))
if book_id in self.dirtied_cache:
self.dirtied_cache.remove(book_id)
if commit: if commit:
self.conn.commit() self.conn.commit()
def dirtied(self, book_ids, commit=True): def dirtied(self, book_ids, commit=True):
self.conn.executemany( self.conn.executemany(
'INSERT OR REPLACE INTO metadata_dirtied VALUES (?)', 'INSERT OR REPLACE INTO metadata_dirtied (book) VALUES (?)',
[(x,) for x in book_ids]) [(x,) for x in book_ids])
if commit: if commit:
self.conn.commit() self.conn.commit()
self.dirtied.update(set(book_ids)) for x in book_ids:
self.dirtied_queue.put(x)
def get_metadata(self, idx, index_is_id=False, get_cover=False): def get_metadata(self, idx, index_is_id=False, get_cover=False):
''' '''