tags manipulation API

This commit is contained in:
Kovid Goyal 2013-07-14 14:53:14 +05:30
parent 96aa24f59f
commit 72d5185347
2 changed files with 90 additions and 1 deletions

View File

@ -9,7 +9,8 @@ __copyright__ = '2013, Kovid Goyal <kovid at kovidgoyal.net>'
import os, traceback, types import os, traceback, types
from future_builtins import zip from future_builtins import zip
from calibre import force_unicode from calibre import force_unicode, isbytestring
from calibre.constants import preferred_encoding
from calibre.db import _get_next_series_num_for_list, _get_series_values from calibre.db import _get_next_series_num_for_list, _get_series_values
from calibre.db.adding import ( from calibre.db.adding import (
find_books_in_directory, import_book_directory_multiple, find_books_in_directory, import_book_directory_multiple,
@ -21,6 +22,19 @@ from calibre.db.view import View
from calibre.db.write import clean_identifier from calibre.db.write import clean_identifier
from calibre.utils.date import utcnow from calibre.utils.date import utcnow
def cleanup_tags(tags):
tags = [x.strip().replace(',', ';') for x in tags if x.strip()]
tags = [x.decode(preferred_encoding, 'replace')
if isbytestring(x) else x for x in tags]
tags = [u' '.join(x.split()) for x in tags]
ans, seen = [], set([])
for tag in tags:
if tag.lower() not in seen:
seen.add(tag.lower())
ans.append(tag)
return ans
class LibraryDatabase(object): class LibraryDatabase(object):
''' Emulate the old LibraryDatabase2 interface ''' ''' Emulate the old LibraryDatabase2 interface '''
@ -371,6 +385,44 @@ class LibraryDatabase(object):
if notify: if notify:
self.notify('metadata', [book_id]) self.notify('metadata', [book_id])
def remove_all_tags(self, ids, notify=False, commit=True):
self.new_api.set_field('tags', {book_id:() for book_id in ids})
if notify:
self.notify('metadata', ids)
def bulk_modify_tags(self, ids, add=[], remove=[], notify=False):
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))
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)
if notify:
self.notify('metadata', ids)
def unapply_tags(self, book_id, tags, notify=True):
self.bulk_modify_tags((book_id,), remove=tags, notify=notify)
def is_tag_used(self, tag):
return icu_lower(tag) in {icu_lower(x) for x in self.new_api.all_field_names('tags')}
def delete_tag(self, tag):
with self.new_api.write_lock:
tag_map = {icu_lower(v):k for k, v in self.new_api._get_id_map('tags').iteritems()}
tid = tag_map.get(icu_lower(tag), None)
if tid is not None:
self.new_api._remove_items('tags', (tid,))
# Private interface {{{ # Private interface {{{
def __iter__(self): def __iter__(self):
for row in self.data.iterall(): for row in self.data.iterall():
@ -399,6 +451,7 @@ for prop in ('author_sort', 'authors', 'comment', 'comments', 'publisher',
setattr(LibraryDatabase, prop, MT(getter(prop))) setattr(LibraryDatabase, prop, MT(getter(prop)))
LibraryDatabase.has_cover = MT(lambda self, book_id:self.new_api.field_for('cover', book_id)) LibraryDatabase.has_cover = MT(lambda self, book_id:self.new_api.field_for('cover', book_id))
LibraryDatabase.get_tags = MT(lambda self, book_id:set(self.new_api.field_for('tags', book_id)))
LibraryDatabase.get_identifiers = MT( LibraryDatabase.get_identifiers = MT(
lambda self, index, index_is_id=False: self.new_api.field_for('identifiers', index if index_is_id else self.data.index_to_id(index))) lambda self, index, index_is_id=False: self.new_api.field_for('identifiers', index if index_is_id else self.data.index_to_id(index)))
# }}} # }}}
@ -524,3 +577,4 @@ LibraryDatabase.commit = MT(lambda self:None)
del MT del MT

View File

@ -404,6 +404,26 @@ class LegacyTest(BaseTest):
def test_legacy_setters(self): # {{{ def test_legacy_setters(self): # {{{
'Test methods that are directly equivalent in the old and new interface' 'Test methods that are directly equivalent in the old and new interface'
from calibre.ebooks.metadata.book.base import Metadata from calibre.ebooks.metadata.book.base import Metadata
ndb = self.init_legacy(self.cloned_library)
db = self.init_old(self.cloned_library)
run_funcs(self, db, ndb, (
('get_tags', 0), ('get_tags', 1), ('get_tags', 2),
('is_tag_used', 'News'), ('is_tag_used', 'xchkjgfh'),
('bulk_modify_tags', (1,), ['t1'], ['News']),
('bulk_modify_tags', (2,), ['t1'], ['Tag One', 'Tag Two']),
('bulk_modify_tags', (3,), ['t1', 't2', 't3']),
(db.clean,),
('@all_tags',),
('@tags', 0), ('@tags', 1), ('@tags', 2),
('unapply_tags', 1, ['t1']),
('unapply_tags', 2, ['xxxx']),
('unapply_tags', 3, ['t2', 't3']),
(db.clean,),
('@all_tags',),
('@tags', 0), ('@tags', 1), ('@tags', 2),
))
ndb = self.init_legacy(self.cloned_library) ndb = self.init_legacy(self.cloned_library)
db = self.init_old(self.cloned_library) db = self.init_old(self.cloned_library)
@ -479,5 +499,20 @@ class LegacyTest(BaseTest):
('#tags', 0), ('#tags', 1), ('#tags', 2), ('#tags', 0), ('#tags', 1), ('#tags', 2),
('authors', 0), ('authors', 1), ('authors', 2), ('authors', 0), ('authors', 1), ('authors', 2),
('publisher', 0), ('publisher', 1), ('publisher', 2), ('publisher', 0), ('publisher', 1), ('publisher', 2),
('delete_tag', 'T1'), ('delete_tag', 'T2'), ('delete_tag', 'Tag one'), ('delete_tag', 'News'),
(db.clean,), (db.refresh,),
('@all_tags',),
('#tags', 0), ('#tags', 1), ('#tags', 2),
)) ))
ndb = self.init_legacy(self.cloned_library)
db = self.init_old(self.cloned_library)
run_funcs(self, db, ndb, (
('remove_all_tags', (1, 2, 3)),
(db.clean,),
('@all_tags',),
('@tags', 0), ('@tags', 1), ('@tags', 2),
))
# }}} # }}}