Implement custom book data API

This commit is contained in:
Kovid Goyal 2013-07-12 10:23:38 +05:30
parent 90599f9c46
commit 6c9a6c1020
4 changed files with 125 additions and 0 deletions

View File

@ -1175,5 +1175,46 @@ class DB(object):
self.rmtree(parent, permanent=permanent) self.rmtree(parent, permanent=permanent)
self.conn.executemany( self.conn.executemany(
'DELETE FROM books WHERE id=?', [(x,) for x in path_map]) 'DELETE FROM books WHERE id=?', [(x,) for x in path_map])
def add_custom_data(self, name, val_map, delete_first):
if delete_first:
self.conn.execute('DELETE FROM books_plugin_data WHERE name=?', (name, ))
self.conn.executemany(
'INSERT OR REPLACE INTO books_plugin_data (book, name, val) VALUES (?, ?, ?)',
[(book_id, name, json.dumps(val, default=to_json))
for book_id, val in val_map.iteritems()])
def get_custom_book_data(self, name, book_ids, default=None):
book_ids = frozenset(book_ids)
def safe_load(val):
try:
return json.loads(val, object_hook=from_json)
except:
return default
if len(book_ids) == 1:
bid = next(iter(book_ids))
ans = {book_id:safe_load(val) for book_id, val in
self.conn.execute('SELECT book, val FROM books_plugin_data WHERE book=? AND name=?', (bid, name))}
return ans or {bid:default}
ans = {}
for book_id, val in self.conn.execute(
'SELECT book, val FROM books_plugin_data WHERE name=?', (name,)):
if not book_ids or book_id in book_ids:
val = safe_load(val)
ans[book_id] = val
return ans
def delete_custom_book_data(self, name, book_ids):
if book_ids:
self.conn.executemany('DELETE FROM books_plugin_data WHERE book=? AND name=?',
[(book_id, name) for book_id in book_ids])
else:
self.conn.execute('DELETE FROM books_plugin_data WHERE name=?', (name,))
def get_ids_for_custom_book_data(self, name):
return frozenset(r[0] for r in self.conn.execute('SELECT book FROM books_plugin_data WHERE name=?', (name,)))
# }}} # }}}

View File

@ -1147,6 +1147,36 @@ class Cache(object):
else: else:
table.remove_books(book_ids, self.backend) table.remove_books(book_ids, self.backend)
@write_api
def add_custom_book_data(self, name, val_map, delete_first=False):
''' Add data for name where val_map is a map of book_ids to values. If
delete_first is True, all previously stored data for name will be
removed. '''
missing = frozenset(val_map) - self._all_book_ids()
if missing:
raise ValueError('add_custom_book_data: no such book_ids: %d'%missing)
self.backend.add_custom_data(name, val_map, delete_first)
@read_api
def get_custom_book_data(self, name, book_ids=(), default=None):
''' Get data for name. By default returns data for all book_ids, pass
in a list of book ids if you only want some data. Returns a map of
book_id to values. If a particular value could not be decoded, uses
default for it. '''
return self.backend.get_custom_book_data(name, book_ids, default)
@write_api
def delete_custom_book_data(self, name, book_ids=()):
''' Delete data for name. By default deletes all data, if you only want
to delete data for some book ids, pass in a list of book ids. '''
self.backend.delete_custom_book_data(name, book_ids)
@read_api
def get_ids_for_custom_book_data(self, name):
''' Return the set of book ids for which name has data. '''
return self.backend.get_ids_for_custom_book_data(name)
# }}} # }}}
class SortKey(object): # {{{ class SortKey(object): # {{{

View File

@ -216,6 +216,29 @@ class LibraryDatabase(object):
# }}} # }}}
# Custom data {{{
def add_custom_book_data(self, book_id, name, val):
self.new_api.add_custom_book_data(name, {book_id:val})
def add_multiple_custom_book_data(self, name, val_map, delete_first=False):
self.new_api.add_custom_book_data(name, val_map, delete_first=delete_first)
def get_custom_book_data(self, book_id, name, default=None):
return self.new_api.get_custom_book_data(name, book_ids={book_id}, default=default).get(book_id, default)
def get_all_custom_book_data(self, name, default=None):
return self.new_api.get_custom_book_data(name, default=default)
def delete_custom_book_data(self, book_id, name):
self.new_api.delete_custom_book_data(name, book_ids=(book_id,))
def delete_all_custom_book_data(self, name):
self.new_api.delete_custom_book_data(name)
def get_ids_for_custom_book_data(self, name):
return list(self.new_api.get_ids_for_custom_book_data(name))
# }}}
# Private interface {{{ # Private interface {{{
def __iter__(self): def __iter__(self):

View File

@ -245,3 +245,34 @@ class LegacyTest(BaseTest):
# }}} # }}}
def test_legacy_custom_data(self): # {{{
'Test the API for custom data storage'
legacy, old = self.init_legacy(self.cloned_library), self.init_old(self.cloned_library)
for name in ('name1', 'name2', 'name3'):
T = partial(ET, 'add_custom_book_data', old=old, legacy=legacy)
T((1, name, 'val1'))(self)
T((2, name, 'val2'))(self)
T((3, name, 'val3'))(self)
T = partial(ET, 'get_ids_for_custom_book_data', old=old, legacy=legacy)
T((name,))(self)
T = partial(ET, 'get_custom_book_data', old=old, legacy=legacy)
T((1, name, object()))
T((9, name, object()))
T = partial(ET, 'get_all_custom_book_data', old=old, legacy=legacy)
T((name, object()))
T((name+'!', object()))
T = partial(ET, 'delete_custom_book_data', old=old, legacy=legacy)
T((name, 1))
T = partial(ET, 'get_all_custom_book_data', old=old, legacy=legacy)
T((name, object()))
T = partial(ET, 'delete_all_custom_book_data', old=old, legacy=legacy)
T((name))
T = partial(ET, 'get_all_custom_book_data', old=old, legacy=legacy)
T((name, object()))
T = partial(ET, 'add_multiple_custom_book_data', old=old, legacy=legacy)
T(('n', {1:'val1', 2:'val2'}))(self)
T = partial(ET, 'get_all_custom_book_data', old=old, legacy=legacy)
T(('n', object()))
old.close()
# }}}