mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
tags manipulation API
This commit is contained in:
parent
96aa24f59f
commit
72d5185347
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
@ -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),
|
||||||
|
))
|
||||||
|
|
||||||
|
|
||||||
# }}}
|
# }}}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user