This commit is contained in:
Kovid Goyal 2011-07-11 22:36:40 -06:00
parent 9d041c7969
commit c51a171384
3 changed files with 129 additions and 15 deletions

View File

@ -7,9 +7,33 @@ __license__ = 'GPL v3'
__copyright__ = '2011, Kovid Goyal <kovid@kovidgoyal.net>' __copyright__ = '2011, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en' __docformat__ = 'restructuredtext en'
from functools import wraps
from calibre.db.locking import create_locks from calibre.db.locking import create_locks
from calibre.db.fields import create_field from calibre.db.fields import create_field
def api(f):
f.is_cache_api = True
return f
def read_api(f):
f = api(f)
f.is_read_api = True
return f
def write_api(f):
f = api(f)
f.is_read_api = False
return f
def wrap_simple(lock, func):
@wraps(func)
def ans(*args, **kwargs):
with lock:
return func(*args, **kwargs)
return ans
class Cache(object): class Cache(object):
def __init__(self, backend): def __init__(self, backend):
@ -17,14 +41,100 @@ class Cache(object):
self.fields = {} self.fields = {}
self.read_lock, self.write_lock = create_locks() self.read_lock, self.write_lock = create_locks()
# Implement locking for all simple read/write API methods
# An unlocked version of the method is stored with the name starting
# with a leading underscore. Use the unlocked versions when the lock
# has already been acquired.
for name in dir(self):
func = getattr(self, name)
ira = getattr(func, 'is_read_api', None)
if ira is not None:
# Save original function
setattr(self, '_'+name, func)
# Wrap it in a lock
lock = self.read_lock if ira else self.write_lock
setattr(self, name, wrap_simple(lock, func))
# Cache Layer API {{{ # Cache Layer API {{{
@api
def init(self): def init(self):
'''
Initialize this cache with data from the backend.
'''
with self.write_lock: with self.write_lock:
self.backend.read_tables() self.backend.read_tables()
for field, table in self.backend.tables.iteritems(): for field, table in self.backend.tables.iteritems():
self.fields[field] = create_field(field, table) self.fields[field] = create_field(field, table)
@read_api
def field_for(self, name, book_id, default_value=None):
'''
Return the value of the field ``name`` for the book identified by
``book_id``. If no such book exists or it has no defined value for the
field ``name`` or no such field exists, then ``default_value`` is returned.
The returned value for is_multiple fields are always tuples.
'''
try:
return self.fields[name].for_book(book_id, default_value=default_value)
except (KeyError, IndexError):
return default_value
@read_api
def field_ids_for(self, name, book_id):
'''
Return the ids (as a tuple) for the values that the field ``name`` has on the book
identified by ``book_id``. If there are no values, or no such book, or
no such field, an empty tuple is returned.
'''
try:
return self.fields[name].ids_for_book(book_id)
except (KeyError, IndexError):
return ()
@read_api
def books_for_field(self, name, item_id):
'''
Return all the books associated with the item identified by
``item_id``, where the item belongs to the field ``name``.
Returned value is a tuple of book ids, or the empty tuple if the item
or the field does not exist.
'''
try:
return self.fields[name].books_for(item_id)
except (KeyError, IndexError):
return ()
@read_api
def all_book_ids(self):
'''
Frozen set of all known book ids.
'''
return frozenset(self.fields['uuid'].iter_book_ids())
@read_api
def all_field_ids(self, name):
'''
Frozen set of ids for all values in the field ``name``.
'''
return frozenset(iter(self.fields[name]))
# }}} # }}}
# Testing {{{
def test(library_path):
from calibre.db.backend import DB
backend = DB(library_path)
cache = Cache(backend)
cache.init()
print ('All book ids:', cache.all_book_ids())
if __name__ == '__main__':
from calibre.utils.config import prefs
test(prefs['library_path'])
# }}}

View File

@ -63,6 +63,9 @@ class OneToOneField(Field):
def __iter__(self): def __iter__(self):
return self.table.book_col_map.iterkeys() return self.table.book_col_map.iterkeys()
def iter_book_ids(self):
return self.table.book_col_map.iterkeys()
class ManyToOneField(Field): class ManyToOneField(Field):
def for_book(self, book_id, default_value=None): def for_book(self, book_id, default_value=None):

View File

@ -110,8 +110,8 @@ class ManyToOneTable(Table):
self.col_book_map[row[1]].append(row[0]) self.col_book_map[row[1]].append(row[0])
self.book_col_map[row[0]] = row[1] self.book_col_map[row[0]] = row[1]
for key, val in self.col_book_map: for key in tuple(self.col_book_map.iterkeys()):
self.col_book_map[key] = tuple(val) self.col_book_map[key] = tuple(self.col_book_map[key])
class ManyToManyTable(ManyToOneTable): class ManyToManyTable(ManyToOneTable):
@ -134,20 +134,21 @@ class ManyToManyTable(ManyToOneTable):
self.book_col_map[row[0]] = [] self.book_col_map[row[0]] = []
self.book_col_map[row[0]].append(row[1]) self.book_col_map[row[0]].append(row[1])
for key, val in self.col_book_map: for key in tuple(self.col_book_map.iterkeys()):
self.col_book_map[key] = tuple(val) self.col_book_map[key] = tuple(self.col_book_map[key])
for key, val in self.book_col_map: for key in tuple(self.book_col_map.iterkeys()):
self.book_col_map[key] = tuple(val) self.book_col_map[key] = tuple(self.book_col_map[key])
class AuthorsTable(ManyToManyTable): class AuthorsTable(ManyToManyTable):
def read_id_maps(self, db): def read_id_maps(self, db):
self.alink_map = {} self.alink_map = {}
self.sort_map = {}
for row in db.conn.execute( for row in db.conn.execute(
'SELECT id, name, sort, link FROM authors'): 'SELECT id, name, sort, link FROM authors'):
self.id_map[row[0]] = row[1] self.id_map[row[0]] = row[1]
self.extra_map[row[0]] = (row[2] if row[2] else self.sort_map[row[0]] = (row[2] if row[2] else
author_to_author_sort(row[1])) author_to_author_sort(row[1]))
self.alink_map[row[0]] = row[3] self.alink_map[row[0]] = row[3]
@ -166,11 +167,11 @@ class FormatsTable(ManyToManyTable):
self.book_col_map[row[0]] = [] self.book_col_map[row[0]] = []
self.book_col_map[row[0]].append((row[1], row[2])) self.book_col_map[row[0]].append((row[1], row[2]))
for key, val in self.col_book_map: for key in tuple(self.col_book_map.iterkeys()):
self.col_book_map[key] = tuple(val) self.col_book_map[key] = tuple(self.col_book_map[key])
for key, val in self.book_col_map: for key in tuple(self.book_col_map.iterkeys()):
self.book_col_map[key] = tuple(val) self.book_col_map[key] = tuple(self.book_col_map[key])
class IdentifiersTable(ManyToManyTable): class IdentifiersTable(ManyToManyTable):
@ -187,9 +188,9 @@ class IdentifiersTable(ManyToManyTable):
self.book_col_map[row[0]] = [] self.book_col_map[row[0]] = []
self.book_col_map[row[0]].append((row[1], row[2])) self.book_col_map[row[0]].append((row[1], row[2]))
for key, val in self.col_book_map: for key in tuple(self.col_book_map.iterkeys()):
self.col_book_map[key] = tuple(val) self.col_book_map[key] = tuple(self.col_book_map[key])
for key, val in self.book_col_map: for key in tuple(self.book_col_map.iterkeys()):
self.book_col_map[key] = tuple(val) self.book_col_map[key] = tuple(self.book_col_map[key])