From de5237c4d5c3e26cec9de2cfcd38de0e02d483e8 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 17 Jul 2013 16:24:42 +0530 Subject: [PATCH] CC set_* API --- src/calibre/db/legacy.py | 72 ++++++++++++++++++++++++++++++++-- src/calibre/db/tests/legacy.py | 49 ++++++++++++++++++++++- 2 files changed, 116 insertions(+), 5 deletions(-) diff --git a/src/calibre/db/legacy.py b/src/calibre/db/legacy.py index eb0debe758..b814b1e23e 100644 --- a/src/calibre/db/legacy.py +++ b/src/calibre/db/legacy.py @@ -441,26 +441,39 @@ class LibraryDatabase(object): if notify: self.notify('metadata', ids) - def bulk_modify_tags(self, ids, add=[], remove=[], notify=False): + def _do_bulk_modify(self, field, ids, add, remove, notify): add = cleanup_tags(add) remove = cleanup_tags(remove) remove = set(remove) - set(add) if not ids or (not add and not remove): return + remove = {icu_lower(x) for x in remove} with self.new_api.write_lock: val_map = {} for book_id in ids: - tags = list(self.new_api._field_for('tags', book_id)) + tags = list(self.new_api._field_for(field, book_id)) existing = {icu_lower(x) for x in tags} tags.extend(t for t in add if icu_lower(t) not in existing) tags = tuple(t for t in tags if icu_lower(t) not in remove) val_map[book_id] = tags - self.new_api._set_field('tags', val_map, allow_case_change=False) + self.new_api._set_field(field, val_map, allow_case_change=False) if notify: self.notify('metadata', ids) + def bulk_modify_tags(self, ids, add=[], remove=[], notify=False): + self._do_bulk_modify('tags', ids, add, remove, notify) + + def set_custom_bulk_multiple(self, ids, add=[], remove=[], label=None, num=None, notify=False): + data = self.backend.custom_field_metadata(label, num) + if not data['editable']: + raise ValueError('Column %r is not editable'%data['label']) + if data['datatype'] != 'text' or not data['is_multiple']: + raise ValueError('Column %r is not text/multiple'%data['label']) + field = self.custom_field_name(label, num) + self._do_bulk_modify(field, ids, add, remove, notify) + def unapply_tags(self, book_id, tags, notify=True): self.bulk_modify_tags((book_id,), remove=tags, notify=notify) @@ -589,6 +602,57 @@ class LibraryDatabase(object): return [] return list(self.new_api.remove_items(field, (item_id,))) + def set_custom(self, book_id, val, label=None, num=None, append=False, + notify=True, extra=None, commit=True, allow_case_change=False): + field = self.custom_field_name(label, num) + data = self.backend.custom_field_metadata(label, num) + if data['datatype'] == 'composite': + return set() + if not data['editable']: + raise ValueError('Column %r is not editable'%data['label']) + if data['datatype'] == 'enumeration' and ( + val and val not in data['display']['enum_values']): + return set() + with self.new_api.write_lock: + if append and data['is_multiple']: + current = self.new_api._field_for(field, book_id) + existing = {icu_lower(x) for x in current} + val = current + tuple(x for x in self.new_api.fields[field].writer.adapter(val) if icu_lower(x) not in existing) + affected_books = self.new_api._set_field(field, {book_id:val}, allow_case_change=allow_case_change) + else: + affected_books = self.new_api._set_field(field, {book_id:val}, allow_case_change=allow_case_change) + if data['datatype'] == 'series': + extra = 1.0 if extra is None else extra + self.new_api._set_field(field + '_index', {book_id:extra}) + if notify and affected_books: + self.notify('metadata', list(affected_books)) + return affected_books + + def set_custom_bulk(self, ids, val, label=None, num=None, + append=False, notify=True, extras=None): + if extras is not None and len(extras) != len(ids): + raise ValueError('Length of ids and extras is not the same') + field = self.custom_field_name(label, num) + data = self.backend.custom_field_metadata(label, num) + if data['datatype'] == 'composite': + return set() + if data['datatype'] == 'enumeration' and ( + val and val not in data['display']['enum_values']): + return + if not data['editable']: + raise ValueError('Column %r is not editable'%data['label']) + + if append: + for book_id in ids: + self.set_custom(book_id, val, label=label, num=num, append=True, notify=False) + else: + with self.new_api.write_lock: + self.new_api._set_field(field, {book_id:val for book_id in ids}, allow_case_change=False) + if extras is not None: + self.new_api._set_field(field + '_index', {book_id:val for book_id, val in zip(ids, extras)}) + if notify: + self.notify('metadata', list(ids)) + # Private interface {{{ def __iter__(self): for row in self.data.iterall(): @@ -777,3 +841,5 @@ LibraryDatabase.commit = MT(lambda self:None) del MT + + diff --git a/src/calibre/db/tests/legacy.py b/src/calibre/db/tests/legacy.py index 83325c1615..6765392638 100644 --- a/src/calibre/db/tests/legacy.py +++ b/src/calibre/db/tests/legacy.py @@ -48,10 +48,10 @@ def run_funcs(self, db, ndb, funcs): meth(*args) else: fmt = lambda x:x - if meth[0] in {'!', '@', '#', '+', '$'}: + if meth[0] in {'!', '@', '#', '+', '$', '-'}: if meth[0] != '+': fmt = {'!':dict, '@':lambda x:frozenset(x or ()), '#':lambda x:set((x or '').split(',')), - '$':lambda x:set(tuple(y) for y in x)}[meth[0]] + '$':lambda x:set(tuple(y) for y in x), '-':lambda x:None}[meth[0]] else: fmt = args[-1] args = args[:-1] @@ -644,5 +644,50 @@ class LegacyTest(BaseTest): for label in ('tags', 'authors', 'series'): run_funcs(self, db, ndb, [('get_custom_and_extra', idx, label) for idx in range(3)]) db.close() + + ndb = self.init_legacy(self.cloned_library) + db = self.init_old(self.cloned_library) + # Test setting + run_funcs(self, db, ndb, ( + ('-set_custom', 1, 't1 & t2', 'authors'), + ('-set_custom', 1, 't3 & t4', 'authors', None, True), + ('-set_custom', 3, 'test one & test Two', 'authors'), + ('-set_custom', 1, 'ijfkghkjdf', 'enum'), + ('-set_custom', 3, 'One', 'enum'), + ('-set_custom', 3, 'xxx', 'formats'), + ('-set_custom', 1, 'my tag two', 'tags', None, False, False, None, True, True), + (db.clean,), (db.refresh,), + ('all_custom', 'series'), ('all_custom', 'tags'), ('all_custom', 'authors'), + )) + for label in ('tags', 'series', 'authors', 'comments', 'rating', 'date', 'yesno', 'isbn', 'enum', 'formats', 'float', 'comp_tags'): + for func in ('get_custom', 'get_custom_extra', 'get_custom_and_extra'): + run_funcs(self, db, ndb, [(func, idx, label) for idx in range(3)]) + db.close() + + ndb = self.init_legacy(self.cloned_library) + db = self.init_old(self.cloned_library) + # Test setting bulk + run_funcs(self, db, ndb, ( + ('set_custom_bulk', (1,2,3), 't1 & t2', 'authors'), + ('set_custom_bulk', (1,2,3), 'a series', 'series', None, False, False, (9, 10, 11)), + ('set_custom_bulk', (1,2,3), 't1', 'tags', None, True), + (db.clean,), (db.refresh,), + ('all_custom', 'series'), ('all_custom', 'tags'), ('all_custom', 'authors'), + )) + for label in ('tags', 'series', 'authors', 'comments', 'rating', 'date', 'yesno', 'isbn', 'enum', 'formats', 'float', 'comp_tags'): + for func in ('get_custom', 'get_custom_extra', 'get_custom_and_extra'): + run_funcs(self, db, ndb, [(func, idx, label) for idx in range(3)]) + db.close() + + ndb = self.init_legacy(self.cloned_library) + db = self.init_old(self.cloned_library) + # Test bulk multiple + run_funcs(self, db, ndb, ( + ('set_custom_bulk_multiple', (1,2,3), ['t1'], ['My Tag One'], 'tags'), + (db.clean,), (db.refresh,), + ('all_custom', 'tags'), + ('get_custom', 0, 'tags'), ('get_custom', 1, 'tags'), ('get_custom', 2, 'tags'), + )) + db.close() # }}}