From c51a1713843daf6bd008c458a57479e95d794363 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 11 Jul 2011 22:36:40 -0600 Subject: [PATCH] ... --- src/calibre/db/cache.py | 110 +++++++++++++++++++++++++++++++++++++++ src/calibre/db/fields.py | 3 ++ src/calibre/db/tables.py | 31 +++++------ 3 files changed, 129 insertions(+), 15 deletions(-) diff --git a/src/calibre/db/cache.py b/src/calibre/db/cache.py index 33487f1f6f..6406bba019 100644 --- a/src/calibre/db/cache.py +++ b/src/calibre/db/cache.py @@ -7,9 +7,33 @@ __license__ = 'GPL v3' __copyright__ = '2011, Kovid Goyal ' __docformat__ = 'restructuredtext en' +from functools import wraps + from calibre.db.locking import create_locks 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): def __init__(self, backend): @@ -17,14 +41,100 @@ class Cache(object): self.fields = {} 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 {{{ + @api def init(self): + ''' + Initialize this cache with data from the backend. + ''' with self.write_lock: self.backend.read_tables() for field, table in self.backend.tables.iteritems(): 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']) + +# }}} diff --git a/src/calibre/db/fields.py b/src/calibre/db/fields.py index 1c36deda2f..483813d80a 100644 --- a/src/calibre/db/fields.py +++ b/src/calibre/db/fields.py @@ -63,6 +63,9 @@ class OneToOneField(Field): def __iter__(self): return self.table.book_col_map.iterkeys() + def iter_book_ids(self): + return self.table.book_col_map.iterkeys() + class ManyToOneField(Field): def for_book(self, book_id, default_value=None): diff --git a/src/calibre/db/tables.py b/src/calibre/db/tables.py index 29cc1b2bc8..c02c8ed9b7 100644 --- a/src/calibre/db/tables.py +++ b/src/calibre/db/tables.py @@ -110,8 +110,8 @@ class ManyToOneTable(Table): self.col_book_map[row[1]].append(row[0]) self.book_col_map[row[0]] = row[1] - for key, val in self.col_book_map: - self.col_book_map[key] = tuple(val) + for key in tuple(self.col_book_map.iterkeys()): + self.col_book_map[key] = tuple(self.col_book_map[key]) class ManyToManyTable(ManyToOneTable): @@ -134,20 +134,21 @@ class ManyToManyTable(ManyToOneTable): self.book_col_map[row[0]] = [] self.book_col_map[row[0]].append(row[1]) - for key, val in self.col_book_map: - self.col_book_map[key] = tuple(val) + for key in tuple(self.col_book_map.iterkeys()): + self.col_book_map[key] = tuple(self.col_book_map[key]) - for key, val in self.book_col_map: - self.book_col_map[key] = tuple(val) + for key in tuple(self.book_col_map.iterkeys()): + self.book_col_map[key] = tuple(self.book_col_map[key]) class AuthorsTable(ManyToManyTable): def read_id_maps(self, db): self.alink_map = {} + self.sort_map = {} for row in db.conn.execute( 'SELECT id, name, sort, link FROM authors'): 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])) 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]].append((row[1], row[2])) - for key, val in self.col_book_map: - self.col_book_map[key] = tuple(val) + for key in tuple(self.col_book_map.iterkeys()): + self.col_book_map[key] = tuple(self.col_book_map[key]) - for key, val in self.book_col_map: - self.book_col_map[key] = tuple(val) + for key in tuple(self.book_col_map.iterkeys()): + self.book_col_map[key] = tuple(self.book_col_map[key]) class IdentifiersTable(ManyToManyTable): @@ -187,9 +188,9 @@ class IdentifiersTable(ManyToManyTable): self.book_col_map[row[0]] = [] self.book_col_map[row[0]].append((row[1], row[2])) - for key, val in self.col_book_map: - self.col_book_map[key] = tuple(val) + for key in tuple(self.col_book_map.iterkeys()): + self.col_book_map[key] = tuple(self.col_book_map[key]) - for key, val in self.book_col_map: - self.book_col_map[key] = tuple(val) + for key in tuple(self.book_col_map.iterkeys()): + self.book_col_map[key] = tuple(self.book_col_map[key])