remove_items() API

This commit is contained in:
Kovid Goyal 2013-07-13 12:02:17 +05:30
parent 9ea6e0d2a9
commit b2163e3846
4 changed files with 91 additions and 1 deletions

View File

@ -265,8 +265,10 @@ class Cache(object):
for name, field in self.fields.iteritems():
if name[0] == '#' and name.endswith('_index'):
field.series_field = self.fields[name[:-len('_index')]]
self.fields[name[:-len('_index')]].index_field = field
elif name == 'series_index':
field.series_field = self.fields['series']
self.fields['series'].index_field = field
elif name == 'authors':
field.author_sort_field = self.fields['author_sort']
elif name == 'title':
@ -1179,6 +1181,18 @@ class Cache(object):
else:
table.remove_books(book_ids, self.backend)
@write_api
def remove_items(self, field, item_ids):
''' Delete all items in the specified field with the specified ids. Returns the set of affected book ids. '''
field = self.fields[field]
affected_books = field.table.remove_items(item_ids, self.backend)
if affected_books:
if hasattr(field, 'index_field'):
self._set_field(field.index_field.name, {bid:1.0 for bid in affected_books})
else:
self._mark_as_dirty(affected_books)
return affected_books
@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

View File

@ -204,6 +204,21 @@ class ManyToOneTable(Table):
[(x,) for x in clean])
return clean
def remove_items(self, item_ids, db):
affected_books = set()
for item_id in item_ids:
val = self.id_map.pop(item_id, null)
if val is null:
continue
book_ids = self.col_book_map.pop(item_id, set())
for book_id in book_ids:
self.book_col_map.pop(book_id, None)
affected_books.update(book_ids)
item_ids = tuple((x,) for x in item_ids)
db.conn.executemany('DELETE FROM {0} WHERE {1}=?'.format(self.link_table, self.metadata['link_column']), item_ids)
db.conn.executemany('DELETE FROM {0} WHERE id=?'.format(self.metadata['table']), item_ids)
return affected_books
class ManyToManyTable(ManyToOneTable):
'''
@ -250,6 +265,21 @@ class ManyToManyTable(ManyToOneTable):
[(x,) for x in clean])
return clean
def remove_items(self, item_ids, db):
affected_books = set()
for item_id in item_ids:
val = self.id_map.pop(item_id, null)
if val is null:
continue
book_ids = self.col_book_map.pop(item_id, set())
for book_id in book_ids:
self.book_col_map[book_id] = tuple(x for x in self.book_col_map.get(book_id, ()) if x != item_id)
affected_books.update(book_ids)
item_ids = tuple((x,) for x in item_ids)
db.conn.executemany('DELETE FROM {0} WHERE {1}=?'.format(self.link_table, self.metadata['link_column']), item_ids)
db.conn.executemany('DELETE FROM {0} WHERE id=?'.format(self.metadata['table']), item_ids)
return affected_books
class AuthorsTable(ManyToManyTable):
def read_id_maps(self, db):
@ -274,6 +304,9 @@ class AuthorsTable(ManyToManyTable):
self.asort_map.pop(item_id, None)
return clean
def remove_items(self, item_ids, db):
raise ValueError('Direct removal of authors is not allowed')
class FormatsTable(ManyToManyTable):
do_clean_on_remove = False
@ -331,6 +364,9 @@ class FormatsTable(ManyToManyTable):
return {book_id:zero_max(book_id) for book_id in formats_map}
def remove_items(self, item_ids, db):
raise NotImplementedError('Cannot delete a format directly')
def update_fmt(self, book_id, fmt, fname, size, db):
fmts = list(self.book_col_map.get(book_id, []))
try:
@ -381,4 +417,6 @@ class IdentifiersTable(ManyToManyTable):
clean.add(item_id)
return clean
def remove_items(self, item_ids, db):
raise NotImplementedError('Direct deletion of identifiers is not implemented')

View File

@ -293,7 +293,7 @@ class LegacyTest(BaseTest):
'clean_user_categories', 'cleanup_tags', 'books_list_filter', 'conn', 'connect', 'construct_file_name',
'construct_path_name', 'clear_dirtied', 'commit_dirty_cache', 'initialize_database', 'initialize_dynamic',
'run_import_plugins', 'vacuum', 'set_path', 'row', 'row_factory', 'rows', 'rmtree', 'series_index_pat',
'import_old_database', 'dirtied_lock', 'dirtied_cache', 'dirty_queue_length',
'import_old_database', 'dirtied_lock', 'dirtied_cache', 'dirty_queue_length', 'dirty_books_referencing',
}
SKIP_ARGSPEC = {
'__init__', 'get_next_series_num_for', 'has_book', 'author_sort_from_authors',

View File

@ -436,3 +436,41 @@ class WritingTest(BaseTest):
self.assertFalse(cache.has_conversion_options(all_ids))
# }}}
def test_remove_items(self): # {{{
' Test removal of many-(many,one) items '
cache = self.init_cache()
tmap = cache.get_id_map('tags')
self.assertEqual(cache.remove_items('tags', tmap), {1, 2})
tmap = cache.get_id_map('#tags')
t = {v:k for k, v in tmap.iteritems()}['My Tag Two']
self.assertEqual(cache.remove_items('#tags', (t,)), {1, 2})
smap = cache.get_id_map('series')
self.assertEqual(cache.remove_items('series', smap), {1, 2})
smap = cache.get_id_map('#series')
s = {v:k for k, v in smap.iteritems()}['My Series Two']
self.assertEqual(cache.remove_items('#series', (s,)), {1})
for c in (cache, self.init_cache()):
self.assertFalse(c.get_id_map('tags'))
self.assertFalse(c.all_field_names('tags'))
for bid in c.all_book_ids():
self.assertFalse(c.field_for('tags', bid))
self.assertEqual(len(c.get_id_map('#tags')), 1)
self.assertEqual(c.all_field_names('#tags'), {'My Tag One'})
for bid in c.all_book_ids():
self.assertIn(c.field_for('#tags', bid), ((), ('My Tag One',)))
for bid in (1, 2):
self.assertEqual(c.field_for('series_index', bid), 1.0)
self.assertFalse(c.get_id_map('series'))
self.assertFalse(c.all_field_names('series'))
for bid in c.all_book_ids():
self.assertFalse(c.field_for('series', bid))
self.assertEqual(c.field_for('series_index', 1), 1.0)
self.assertEqual(c.all_field_names('#series'), {'My Series One'})
for bid in c.all_book_ids():
self.assertIn(c.field_for('#series', bid), (None, 'My Series One'))
# }}}