mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-08 10:44:09 -04:00
Merge branch 'kovidgoyal/master'
This commit is contained in:
commit
d27197820f
@ -117,4 +117,5 @@ Various things that require other things before they can be migrated:
|
|||||||
5. In the new API refresh() does not re-read from disk. That might break a
|
5. In the new API refresh() does not re-read from disk. That might break a
|
||||||
few things, for example content server reloading on db change as well as
|
few things, for example content server reloading on db change as well as
|
||||||
dump/restore of db?
|
dump/restore of db?
|
||||||
|
6. grep the sources for TODO
|
||||||
'''
|
'''
|
||||||
|
@ -7,7 +7,6 @@ __license__ = 'GPL v3'
|
|||||||
__copyright__ = '2013, Kovid Goyal <kovid at kovidgoyal.net>'
|
__copyright__ = '2013, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||||
|
|
||||||
import os, traceback, types
|
import os, traceback, types
|
||||||
from functools import partial
|
|
||||||
from future_builtins import zip
|
from future_builtins import zip
|
||||||
|
|
||||||
from calibre import force_unicode
|
from calibre import force_unicode
|
||||||
@ -19,6 +18,7 @@ from calibre.db.backend import DB
|
|||||||
from calibre.db.cache import Cache
|
from calibre.db.cache import Cache
|
||||||
from calibre.db.categories import CATEGORY_SORTS
|
from calibre.db.categories import CATEGORY_SORTS
|
||||||
from calibre.db.view import View
|
from calibre.db.view import View
|
||||||
|
from calibre.db.write import clean_identifier
|
||||||
from calibre.utils.date import utcnow
|
from calibre.utils.date import utcnow
|
||||||
|
|
||||||
class LibraryDatabase(object):
|
class LibraryDatabase(object):
|
||||||
@ -52,76 +52,8 @@ class LibraryDatabase(object):
|
|||||||
|
|
||||||
self.get_property = self.data.get_property
|
self.get_property = self.data.get_property
|
||||||
|
|
||||||
for prop in (
|
|
||||||
'author_sort', 'authors', 'comment', 'comments',
|
|
||||||
'publisher', 'rating', 'series', 'series_index', 'tags',
|
|
||||||
'title', 'timestamp', 'uuid', 'pubdate', 'ondevice',
|
|
||||||
'metadata_last_modified', 'languages',
|
|
||||||
):
|
|
||||||
fm = {'comment':'comments', 'metadata_last_modified':
|
|
||||||
'last_modified', 'title_sort':'sort'}.get(prop, prop)
|
|
||||||
setattr(self, prop, partial(self.get_property,
|
|
||||||
loc=self.FIELD_MAP[fm]))
|
|
||||||
|
|
||||||
MT = lambda func: types.MethodType(func, self, LibraryDatabase)
|
|
||||||
|
|
||||||
for meth in ('get_next_series_num_for', 'has_book', 'author_sort_from_authors'):
|
|
||||||
setattr(self, meth, getattr(self.new_api, meth))
|
|
||||||
|
|
||||||
# Legacy API to get information about many-(one, many) fields
|
|
||||||
for field in ('authors', 'tags', 'publisher', 'series'):
|
|
||||||
def getter(field):
|
|
||||||
def func(self):
|
|
||||||
return self.new_api.all_field_names(field)
|
|
||||||
return func
|
|
||||||
name = field[:-1] if field in {'authors', 'tags'} else field
|
|
||||||
setattr(self, 'all_%s_names' % name, MT(getter(field)))
|
|
||||||
self.all_formats = MT(lambda self:self.new_api.all_field_names('formats'))
|
|
||||||
|
|
||||||
for func, field in {'all_authors':'authors', 'all_titles':'title', 'all_tags2':'tags', 'all_series':'series', 'all_publishers':'publisher'}.iteritems():
|
|
||||||
setattr(self, func, partial(self.field_id_map, field))
|
|
||||||
self.all_tags = MT(lambda self: list(self.all_tag_names()))
|
|
||||||
self.get_authors_with_ids = MT(
|
|
||||||
lambda self: [[aid, adata['name'], adata['sort'], adata['link']] for aid, adata in self.new_api.author_data().iteritems()])
|
|
||||||
for field in ('tags', 'series', 'publishers', 'ratings', 'languages'):
|
|
||||||
def getter(field):
|
|
||||||
fname = field[:-1] if field in {'publishers', 'ratings'} else field
|
|
||||||
def func(self):
|
|
||||||
return [[tid, tag] for tid, tag in self.new_api.get_id_map(fname).iteritems()]
|
|
||||||
return func
|
|
||||||
setattr(self, 'get_%s_with_ids' % field,
|
|
||||||
MT(getter(field)))
|
|
||||||
for field in ('author', 'tag', 'series'):
|
|
||||||
def getter(field):
|
|
||||||
field = field if field == 'series' else (field+'s')
|
|
||||||
def func(self, item_id):
|
|
||||||
return self.new_api.get_item_name(field, item_id)
|
|
||||||
return func
|
|
||||||
setattr(self, '%s_name' % field, MT(getter(field)))
|
|
||||||
for field in ('publisher', 'series', 'tag'):
|
|
||||||
def getter(field):
|
|
||||||
fname = 'tags' if field == 'tag' else field
|
|
||||||
def func(self, item_id):
|
|
||||||
self.new_api.remove_items(fname, (item_id,))
|
|
||||||
return func
|
|
||||||
setattr(self, 'delete_%s_using_id' % field, MT(getter(field)))
|
|
||||||
|
|
||||||
# Legacy field API
|
|
||||||
for func in (
|
|
||||||
'standard_field_keys', 'custom_field_keys', 'all_field_keys',
|
|
||||||
'searchable_fields', 'sortable_field_keys',
|
|
||||||
'search_term_to_field_key', 'custom_field_metadata',
|
|
||||||
'all_metadata'):
|
|
||||||
setattr(self, func, getattr(self.field_metadata, func))
|
|
||||||
self.metadata_for_field = self.field_metadata.get
|
|
||||||
|
|
||||||
self.last_update_check = self.last_modified()
|
self.last_update_check = self.last_modified()
|
||||||
self.book_on_device_func = None
|
self.book_on_device_func = None
|
||||||
# Cleaning is not required anymore
|
|
||||||
self.clean = self.clean_custom = MT(lambda self:None)
|
|
||||||
self.clean_standard_field = MT(lambda self, field, commit=False:None)
|
|
||||||
# apsw operates in autocommit mode
|
|
||||||
self.commit = MT(lambda self:None)
|
|
||||||
|
|
||||||
def close(self):
|
def close(self):
|
||||||
self.backend.close()
|
self.backend.close()
|
||||||
@ -409,6 +341,36 @@ class LibraryDatabase(object):
|
|||||||
finally:
|
finally:
|
||||||
self.notify('metadata', [book_id])
|
self.notify('metadata', [book_id])
|
||||||
|
|
||||||
|
def set_identifier(self, book_id, typ, val, notify=True, commit=True):
|
||||||
|
with self.new_api.write_lock:
|
||||||
|
identifiers = self.new_api._field_for('identifiers', book_id)
|
||||||
|
typ, val = clean_identifier(typ, val)
|
||||||
|
if typ:
|
||||||
|
identifiers[typ] = val
|
||||||
|
self.new_api._set_field('identifiers', {book_id:identifiers})
|
||||||
|
self.notify('metadata', [book_id])
|
||||||
|
|
||||||
|
def set_isbn(self, book_id, isbn, notify=True, commit=True):
|
||||||
|
self.set_identifier(book_id, 'isbn', isbn, notify=notify, commit=commit)
|
||||||
|
|
||||||
|
def set_tags(self, book_id, tags, append=False, notify=True, commit=True, allow_case_change=False):
|
||||||
|
tags = tags or []
|
||||||
|
with self.new_api.write_lock:
|
||||||
|
if append:
|
||||||
|
otags = self.new_api._field_for('tags', book_id)
|
||||||
|
existing = {icu_lower(x) for x in otags}
|
||||||
|
tags = list(otags) + [x for x in tags if icu_lower(x) not in existing]
|
||||||
|
ret = self.new_api._set_field('tags', {book_id:tags}, allow_case_change=allow_case_change)
|
||||||
|
if notify:
|
||||||
|
self.notify('metadata', [book_id])
|
||||||
|
return ret
|
||||||
|
|
||||||
|
def set_metadata(self, book_id, mi, ignore_errors=False, set_title=True,
|
||||||
|
set_authors=True, commit=True, force_changes=False, notify=True):
|
||||||
|
self.new_api.set_metadata(book_id, mi, ignore_errors=ignore_errors, set_title=set_title, set_authors=set_authors, force_changes=force_changes)
|
||||||
|
if notify:
|
||||||
|
self.notify('metadata', [book_id])
|
||||||
|
|
||||||
# Private interface {{{
|
# Private interface {{{
|
||||||
def __iter__(self):
|
def __iter__(self):
|
||||||
for row in self.data.iterall():
|
for row in self.data.iterall():
|
||||||
@ -422,3 +384,143 @@ class LibraryDatabase(object):
|
|||||||
|
|
||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
|
MT = lambda func: types.MethodType(func, None, LibraryDatabase)
|
||||||
|
|
||||||
|
# Legacy getter API {{{
|
||||||
|
for prop in ('author_sort', 'authors', 'comment', 'comments', 'publisher',
|
||||||
|
'rating', 'series', 'series_index', 'tags', 'title', 'title_sort',
|
||||||
|
'timestamp', 'uuid', 'pubdate', 'ondevice', 'metadata_last_modified', 'languages',):
|
||||||
|
def getter(prop):
|
||||||
|
fm = {'comment':'comments', 'metadata_last_modified':
|
||||||
|
'last_modified', 'title_sort':'sort'}.get(prop, prop)
|
||||||
|
def func(self, index, index_is_id=False):
|
||||||
|
return self.get_property(index, index_is_id=index_is_id, loc=self.FIELD_MAP[fm])
|
||||||
|
return func
|
||||||
|
setattr(LibraryDatabase, prop, MT(getter(prop)))
|
||||||
|
|
||||||
|
LibraryDatabase.has_cover = MT(lambda self, book_id:self.new_api.field_for('cover', book_id))
|
||||||
|
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)))
|
||||||
|
# }}}
|
||||||
|
|
||||||
|
# Legacy setter API {{{
|
||||||
|
for field in (
|
||||||
|
'!authors', 'author_sort', 'comment', 'has_cover', 'identifiers', 'languages',
|
||||||
|
'pubdate', '!publisher', 'rating', '!series', 'series_index', 'timestamp', 'uuid',
|
||||||
|
'title', 'title_sort',
|
||||||
|
):
|
||||||
|
def setter(field):
|
||||||
|
has_case_change = field.startswith('!')
|
||||||
|
field = {'comment':'comments', 'title_sort':'sort'}.get(field, field)
|
||||||
|
if has_case_change:
|
||||||
|
field = field[1:]
|
||||||
|
acc = field == 'series'
|
||||||
|
def func(self, book_id, val, notify=True, commit=True, allow_case_change=acc):
|
||||||
|
ret = self.new_api.set_field(field, {book_id:val}, allow_case_change=allow_case_change)
|
||||||
|
if notify:
|
||||||
|
self.notify([book_id])
|
||||||
|
return ret
|
||||||
|
elif field == 'has_cover':
|
||||||
|
def func(self, book_id, val):
|
||||||
|
self.new_api.set_field('cover', {book_id:bool(val)})
|
||||||
|
else:
|
||||||
|
null_field = field in {'title', 'sort', 'uuid'}
|
||||||
|
retval = (True if field == 'sort' else None)
|
||||||
|
def func(self, book_id, val, notify=True, commit=True):
|
||||||
|
if not val and null_field:
|
||||||
|
return (False if field == 'sort' else None)
|
||||||
|
ret = self.new_api.set_field(field, {book_id:val})
|
||||||
|
if notify:
|
||||||
|
self.notify([book_id])
|
||||||
|
return ret if field == 'languages' else retval
|
||||||
|
return func
|
||||||
|
setattr(LibraryDatabase, 'set_%s' % field.replace('!', ''), MT(setter(field)))
|
||||||
|
# }}}
|
||||||
|
|
||||||
|
# Legacy API to get information about many-(one, many) fields {{{
|
||||||
|
for field in ('authors', 'tags', 'publisher', 'series'):
|
||||||
|
def getter(field):
|
||||||
|
def func(self):
|
||||||
|
return self.new_api.all_field_names(field)
|
||||||
|
return func
|
||||||
|
name = field[:-1] if field in {'authors', 'tags'} else field
|
||||||
|
setattr(LibraryDatabase, 'all_%s_names' % name, MT(getter(field)))
|
||||||
|
LibraryDatabase.all_formats = MT(lambda self:self.new_api.all_field_names('formats'))
|
||||||
|
|
||||||
|
for func, field in {'all_authors':'authors', 'all_titles':'title', 'all_tags2':'tags', 'all_series':'series', 'all_publishers':'publisher'}.iteritems():
|
||||||
|
def getter(field):
|
||||||
|
def func(self):
|
||||||
|
return self.field_id_map(field)
|
||||||
|
return func
|
||||||
|
setattr(LibraryDatabase, func, MT(getter(field)))
|
||||||
|
|
||||||
|
LibraryDatabase.all_tags = MT(lambda self: list(self.all_tag_names()))
|
||||||
|
LibraryDatabase.get_all_identifier_types = MT(lambda self: list(self.new_api.fields['identifiers'].table.all_identifier_types()))
|
||||||
|
LibraryDatabase.get_authors_with_ids = MT(
|
||||||
|
lambda self: [[aid, adata['name'], adata['sort'], adata['link']] for aid, adata in self.new_api.author_data().iteritems()])
|
||||||
|
|
||||||
|
for field in ('tags', 'series', 'publishers', 'ratings', 'languages'):
|
||||||
|
def getter(field):
|
||||||
|
fname = field[:-1] if field in {'publishers', 'ratings'} else field
|
||||||
|
def func(self):
|
||||||
|
return [[tid, tag] for tid, tag in self.new_api.get_id_map(fname).iteritems()]
|
||||||
|
return func
|
||||||
|
setattr(LibraryDatabase, 'get_%s_with_ids' % field, MT(getter(field)))
|
||||||
|
|
||||||
|
for field in ('author', 'tag', 'series'):
|
||||||
|
def getter(field):
|
||||||
|
field = field if field == 'series' else (field+'s')
|
||||||
|
def func(self, item_id):
|
||||||
|
return self.new_api.get_item_name(field, item_id)
|
||||||
|
return func
|
||||||
|
setattr(LibraryDatabase, '%s_name' % field, MT(getter(field)))
|
||||||
|
|
||||||
|
for field in ('publisher', 'series', 'tag'):
|
||||||
|
def getter(field):
|
||||||
|
fname = 'tags' if field == 'tag' else field
|
||||||
|
def func(self, item_id):
|
||||||
|
self.new_api.remove_items(fname, (item_id,))
|
||||||
|
return func
|
||||||
|
setattr(LibraryDatabase, 'delete_%s_using_id' % field, MT(getter(field)))
|
||||||
|
# }}}
|
||||||
|
|
||||||
|
# Legacy field API {{{
|
||||||
|
for func in (
|
||||||
|
'standard_field_keys', '!custom_field_keys', 'all_field_keys',
|
||||||
|
'searchable_fields', 'sortable_field_keys',
|
||||||
|
'search_term_to_field_key', '!custom_field_metadata',
|
||||||
|
'all_metadata'):
|
||||||
|
def getter(func):
|
||||||
|
if func.startswith('!'):
|
||||||
|
func = func[1:]
|
||||||
|
def meth(self, include_composites=True):
|
||||||
|
return getattr(self.field_metadata, func)(include_composites=include_composites)
|
||||||
|
elif func == 'search_term_to_field_key':
|
||||||
|
def meth(self, term):
|
||||||
|
return self.field_metadata.search_term_to_field_key(term)
|
||||||
|
else:
|
||||||
|
def meth(self):
|
||||||
|
return getattr(self.field_metadata, func)()
|
||||||
|
return meth
|
||||||
|
setattr(LibraryDatabase, func.replace('!', ''), MT(getter(func)))
|
||||||
|
LibraryDatabase.metadata_for_field = MT(lambda self, field:self.field_metadata.get(field))
|
||||||
|
|
||||||
|
# }}}
|
||||||
|
|
||||||
|
# Miscellaneous API {{{
|
||||||
|
for meth in ('get_next_series_num_for', 'has_book', 'author_sort_from_authors'):
|
||||||
|
def getter(meth):
|
||||||
|
def func(self, x):
|
||||||
|
return getattr(self.new_api, meth)(x)
|
||||||
|
return func
|
||||||
|
setattr(LibraryDatabase, meth, MT(getter(meth)))
|
||||||
|
|
||||||
|
# Cleaning is not required anymore
|
||||||
|
LibraryDatabase.clean = LibraryDatabase.clean_custom = MT(lambda self:None)
|
||||||
|
LibraryDatabase.clean_standard_field = MT(lambda self, field, commit=False:None)
|
||||||
|
# apsw operates in autocommit mode
|
||||||
|
LibraryDatabase.commit = MT(lambda self:None)
|
||||||
|
# }}}
|
||||||
|
|
||||||
|
del MT
|
||||||
|
|
||||||
|
@ -420,3 +420,6 @@ class IdentifiersTable(ManyToManyTable):
|
|||||||
def remove_items(self, item_ids, db):
|
def remove_items(self, item_ids, db):
|
||||||
raise NotImplementedError('Direct deletion of identifiers is not implemented')
|
raise NotImplementedError('Direct deletion of identifiers is not implemented')
|
||||||
|
|
||||||
|
def all_identifier_types(self):
|
||||||
|
return frozenset(k for k, v in self.col_book_map.iteritems() if v)
|
||||||
|
|
||||||
|
@ -47,11 +47,15 @@ def run_funcs(self, db, ndb, funcs):
|
|||||||
meth(*args)
|
meth(*args)
|
||||||
else:
|
else:
|
||||||
fmt = lambda x:x
|
fmt = lambda x:x
|
||||||
if meth[0] in {'!', '@', '#'}:
|
if meth[0] in {'!', '@', '#', '+'}:
|
||||||
fmt = {'!':dict, '@':frozenset, '#':lambda x:set((x or '').split(','))}[meth[0]]
|
if meth[0] != '+':
|
||||||
|
fmt = {'!':dict, '@':lambda x:frozenset(x or ()), '#':lambda x:set((x or '').split(','))}[meth[0]]
|
||||||
|
else:
|
||||||
|
fmt = args[-1]
|
||||||
|
args = args[:-1]
|
||||||
meth = meth[1:]
|
meth = meth[1:]
|
||||||
self.assertEqual(fmt(getattr(db, meth)(*args)), fmt(getattr(ndb, meth)(*args)),
|
res1, res2 = fmt(getattr(db, meth)(*args)), fmt(getattr(ndb, meth)(*args))
|
||||||
'The method: %s() returned different results for argument %s' % (meth, args))
|
self.assertEqual(res1, res2, 'The method: %s() returned different results for argument %s' % (meth, args))
|
||||||
|
|
||||||
class LegacyTest(BaseTest):
|
class LegacyTest(BaseTest):
|
||||||
|
|
||||||
@ -129,7 +133,7 @@ class LegacyTest(BaseTest):
|
|||||||
def test_legacy_getters(self): # {{{
|
def test_legacy_getters(self): # {{{
|
||||||
' Test various functions to get individual bits of metadata '
|
' Test various functions to get individual bits of metadata '
|
||||||
old = self.init_old()
|
old = self.init_old()
|
||||||
getters = ('path', 'abspath', 'title', 'authors', 'series',
|
getters = ('path', 'abspath', 'title', 'title_sort', 'authors', 'series',
|
||||||
'publisher', 'author_sort', 'authors', 'comments',
|
'publisher', 'author_sort', 'authors', 'comments',
|
||||||
'comment', 'publisher', 'rating', 'series_index', 'tags',
|
'comment', 'publisher', 'rating', 'series_index', 'tags',
|
||||||
'timestamp', 'uuid', 'pubdate', 'ondevice',
|
'timestamp', 'uuid', 'pubdate', 'ondevice',
|
||||||
@ -164,6 +168,7 @@ class LegacyTest(BaseTest):
|
|||||||
'!all_authors':[()],
|
'!all_authors':[()],
|
||||||
'!all_tags2':[()],
|
'!all_tags2':[()],
|
||||||
'@all_tags':[()],
|
'@all_tags':[()],
|
||||||
|
'@get_all_identifier_types':[()],
|
||||||
'!all_publishers':[()],
|
'!all_publishers':[()],
|
||||||
'!all_titles':[()],
|
'!all_titles':[()],
|
||||||
'!all_series':[()],
|
'!all_series':[()],
|
||||||
@ -327,9 +332,10 @@ class LegacyTest(BaseTest):
|
|||||||
'construct_path_name', 'clear_dirtied', 'commit_dirty_cache', 'initialize_database', 'initialize_dynamic',
|
'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',
|
'run_import_plugins', 'vacuum', 'set_path', 'row', 'row_factory', 'rows', 'rmtree', 'series_index_pat',
|
||||||
'import_old_database', 'dirtied_lock', 'dirtied_cache', 'dirty_queue_length', 'dirty_books_referencing',
|
'import_old_database', 'dirtied_lock', 'dirtied_cache', 'dirty_queue_length', 'dirty_books_referencing',
|
||||||
|
'windows_check_if_files_in_use', 'get_metadata_for_dump', 'get_a_dirtied_book',
|
||||||
}
|
}
|
||||||
SKIP_ARGSPEC = {
|
SKIP_ARGSPEC = {
|
||||||
'__init__', 'get_next_series_num_for', 'has_book', 'author_sort_from_authors',
|
'__init__',
|
||||||
}
|
}
|
||||||
|
|
||||||
missing = []
|
missing = []
|
||||||
@ -397,6 +403,67 @@ 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
|
||||||
|
ndb = self.init_legacy(self.cloned_library)
|
||||||
|
db = self.init_old(self.cloned_library)
|
||||||
|
|
||||||
|
run_funcs(self, db, ndb, (
|
||||||
|
('set_authors', 1, ('author one',),), ('set_authors', 2, ('author two',), True, True, True),
|
||||||
|
('set_author_sort', 3, 'new_aus'),
|
||||||
|
('set_comment', 1, ''), ('set_comment', 2, None), ('set_comment', 3, '<p>a comment</p>'),
|
||||||
|
('set_has_cover', 1, True), ('set_has_cover', 2, True), ('set_has_cover', 3, 1),
|
||||||
|
('set_identifiers', 2, {'test':'', 'a':'b'}), ('set_identifiers', 3, {'id':'1', 'url':'http://acme.com'}), ('set_identifiers', 1, {}),
|
||||||
|
('set_languages', 1, ('en',)),
|
||||||
|
('set_languages', 2, ()),
|
||||||
|
('set_languages', 3, ('deu', 'spa', 'fra')),
|
||||||
|
('set_pubdate', 1, None), ('set_pubdate', 2, '2011-1-7'),
|
||||||
|
('set_series', 1, 'a series one'), ('set_series', 2, 'another series [7]'), ('set_series', 3, 'a third series'),
|
||||||
|
('set_publisher', 1, 'publisher two'), ('set_publisher', 2, None), ('set_publisher', 3, 'a third puB'),
|
||||||
|
('set_rating', 1, 2.3), ('set_rating', 2, 0), ('set_rating', 3, 8),
|
||||||
|
('set_timestamp', 1, None), ('set_timestamp', 2, '2011-1-7'),
|
||||||
|
('set_uuid', 1, None), ('set_uuid', 2, 'a test uuid'),
|
||||||
|
('set_title', 1, 'title two'), ('set_title', 2, None), ('set_title', 3, 'The Test Title'),
|
||||||
|
('set_tags', 1, ['a1', 'a2'], True), ('set_tags', 2, ['b1', 'tag one'], False, False, False, True), ('set_tags', 3, ['A1']),
|
||||||
|
(db.refresh,),
|
||||||
|
('title', 0), ('title', 1), ('title', 2),
|
||||||
|
('title_sort', 0), ('title_sort', 1), ('title_sort', 2),
|
||||||
|
('authors', 0), ('authors', 1), ('authors', 2),
|
||||||
|
('author_sort', 0), ('author_sort', 1), ('author_sort', 2),
|
||||||
|
('has_cover', 3), ('has_cover', 1), ('has_cover', 2),
|
||||||
|
('get_identifiers', 0), ('get_identifiers', 1), ('get_identifiers', 2),
|
||||||
|
('pubdate', 0), ('pubdate', 1), ('pubdate', 2),
|
||||||
|
('timestamp', 0), ('timestamp', 1), ('timestamp', 2),
|
||||||
|
('publisher', 0), ('publisher', 1), ('publisher', 2),
|
||||||
|
('rating', 0), ('+rating', 1, lambda x: x or 0), ('rating', 2),
|
||||||
|
('series', 0), ('series', 1), ('series', 2),
|
||||||
|
('series_index', 0), ('series_index', 1), ('series_index', 2),
|
||||||
|
('uuid', 0), ('uuid', 1), ('uuid', 2),
|
||||||
|
('@tags', 0), ('@tags', 1), ('@tags', 2),
|
||||||
|
('@all_tags',),
|
||||||
|
('@get_all_identifier_types',),
|
||||||
|
|
||||||
|
('set_title_sort', 1, 'Title Two'), ('set_title_sort', 2, None), ('set_title_sort', 3, 'The Test Title_sort'),
|
||||||
|
('set_series_index', 1, 2.3), ('set_series_index', 2, 0), ('set_series_index', 3, 8),
|
||||||
|
('set_identifier', 1, 'moose', 'val'), ('set_identifier', 2, 'test', ''), ('set_identifier', 3, '', ''),
|
||||||
|
(db.refresh,),
|
||||||
|
('series_index', 0), ('series_index', 1), ('series_index', 2),
|
||||||
|
('title_sort', 0), ('title_sort', 1), ('title_sort', 2),
|
||||||
|
('get_identifiers', 0), ('get_identifiers', 1), ('get_identifiers', 2),
|
||||||
|
('@get_all_identifier_types',),
|
||||||
|
|
||||||
|
('set_metadata', 1, Metadata('title', ('a1',)), False, False, False, True, True),
|
||||||
|
('set_metadata', 3, Metadata('title', ('a1',))),
|
||||||
|
(db.refresh,),
|
||||||
|
('title', 0), ('title', 1), ('title', 2),
|
||||||
|
('title_sort', 0), ('title_sort', 1), ('title_sort', 2),
|
||||||
|
('authors', 0), ('authors', 1), ('authors', 2),
|
||||||
|
('author_sort', 0), ('author_sort', 1), ('author_sort', 2),
|
||||||
|
('@tags', 0), ('@tags', 1), ('@tags', 2),
|
||||||
|
('@all_tags',),
|
||||||
|
('@get_all_identifier_types',),
|
||||||
|
))
|
||||||
|
db.close()
|
||||||
|
|
||||||
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)
|
||||||
|
|
||||||
@ -405,7 +472,7 @@ class LegacyTest(BaseTest):
|
|||||||
('set', 0, 'tags', 't1,t2,tag one', True),
|
('set', 0, 'tags', 't1,t2,tag one', True),
|
||||||
('set', 0, 'authors', 'author one & Author Two', True),
|
('set', 0, 'authors', 'author one & Author Two', True),
|
||||||
('set', 0, 'rating', 3.2),
|
('set', 0, 'rating', 3.2),
|
||||||
('set', 0, 'publisher', 'publisher one', True),
|
('set', 0, 'publisher', 'publisher one', False),
|
||||||
(db.refresh,),
|
(db.refresh,),
|
||||||
('title', 0),
|
('title', 0),
|
||||||
('rating', 0),
|
('rating', 0),
|
||||||
@ -413,6 +480,4 @@ class LegacyTest(BaseTest):
|
|||||||
('authors', 0), ('authors', 1), ('authors', 2),
|
('authors', 0), ('authors', 1), ('authors', 2),
|
||||||
('publisher', 0), ('publisher', 1), ('publisher', 2),
|
('publisher', 0), ('publisher', 1), ('publisher', 2),
|
||||||
))
|
))
|
||||||
db.close()
|
|
||||||
|
|
||||||
# }}}
|
# }}}
|
||||||
|
@ -107,8 +107,8 @@ def adapt_languages(to_tuple, x):
|
|||||||
return tuple(ans)
|
return tuple(ans)
|
||||||
|
|
||||||
def clean_identifier(typ, val):
|
def clean_identifier(typ, val):
|
||||||
typ = icu_lower(typ).strip().replace(':', '').replace(',', '')
|
typ = icu_lower(typ or '').strip().replace(':', '').replace(',', '')
|
||||||
val = val.strip().replace(',', '|').replace(':', '|')
|
val = (val or '').strip().replace(',', '|').replace(':', '|')
|
||||||
return typ, val
|
return typ, val
|
||||||
|
|
||||||
def adapt_identifiers(to_tuple, x):
|
def adapt_identifiers(to_tuple, x):
|
||||||
|
@ -257,8 +257,8 @@ class EmailMixin(object): # {{{
|
|||||||
else:
|
else:
|
||||||
autos = [self.library_view.model().db.title(id, index_is_id=True) for id in auto]
|
autos = [self.library_view.model().db.title(id, index_is_id=True) for id in auto]
|
||||||
if self.auto_convert_question(
|
if self.auto_convert_question(
|
||||||
_('Auto convert the following books before sending via '
|
_('Auto convert the following books to %s before sending via '
|
||||||
'email?'), autos):
|
'email?') % format.upper(), autos):
|
||||||
self.iactions['Convert Books'].auto_convert_mail(to, fmts, delete_from_library, auto, format, subject)
|
self.iactions['Convert Books'].auto_convert_mail(to, fmts, delete_from_library, auto, format, subject)
|
||||||
|
|
||||||
if bad:
|
if bad:
|
||||||
|
@ -2423,7 +2423,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
|||||||
if not authors:
|
if not authors:
|
||||||
authors = [_('Unknown')]
|
authors = [_('Unknown')]
|
||||||
self.conn.execute('DELETE FROM books_authors_link WHERE book=?',(id,))
|
self.conn.execute('DELETE FROM books_authors_link WHERE book=?',(id,))
|
||||||
books_to_refresh = set([])
|
books_to_refresh = {id}
|
||||||
final_authors = []
|
final_authors = []
|
||||||
for a in authors:
|
for a in authors:
|
||||||
case_change = False
|
case_change = False
|
||||||
@ -2615,10 +2615,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
|||||||
def set_publisher(self, id, publisher, notify=True, commit=True,
|
def set_publisher(self, id, publisher, notify=True, commit=True,
|
||||||
allow_case_change=False):
|
allow_case_change=False):
|
||||||
self.conn.execute('DELETE FROM books_publishers_link WHERE book=?',(id,))
|
self.conn.execute('DELETE FROM books_publishers_link WHERE book=?',(id,))
|
||||||
self.conn.execute('''DELETE FROM publishers WHERE (SELECT COUNT(id)
|
books_to_refresh = {id}
|
||||||
FROM books_publishers_link
|
|
||||||
WHERE publisher=publishers.id) < 1''')
|
|
||||||
books_to_refresh = set([])
|
|
||||||
if publisher:
|
if publisher:
|
||||||
case_change = False
|
case_change = False
|
||||||
if not isinstance(publisher, unicode):
|
if not isinstance(publisher, unicode):
|
||||||
@ -2634,6 +2631,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
|||||||
case_change = True
|
case_change = True
|
||||||
else:
|
else:
|
||||||
publisher = cur_name
|
publisher = cur_name
|
||||||
|
books_to_refresh = set()
|
||||||
else:
|
else:
|
||||||
aid = self.conn.execute('''INSERT INTO publishers(name)
|
aid = self.conn.execute('''INSERT INTO publishers(name)
|
||||||
VALUES (?)''', (publisher,)).lastrowid
|
VALUES (?)''', (publisher,)).lastrowid
|
||||||
@ -2643,6 +2641,10 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
|||||||
bks = self.conn.get('''SELECT book FROM books_publishers_link
|
bks = self.conn.get('''SELECT book FROM books_publishers_link
|
||||||
WHERE publisher=?''', (aid,))
|
WHERE publisher=?''', (aid,))
|
||||||
books_to_refresh |= set([bk[0] for bk in bks])
|
books_to_refresh |= set([bk[0] for bk in bks])
|
||||||
|
self.conn.execute('''DELETE FROM publishers WHERE (SELECT COUNT(id)
|
||||||
|
FROM books_publishers_link
|
||||||
|
WHERE publisher=publishers.id) < 1''')
|
||||||
|
|
||||||
self.dirtied(set([id])|books_to_refresh, commit=False)
|
self.dirtied(set([id])|books_to_refresh, commit=False)
|
||||||
if commit:
|
if commit:
|
||||||
self.conn.commit()
|
self.conn.commit()
|
||||||
@ -3054,11 +3056,9 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
|||||||
tags = []
|
tags = []
|
||||||
if not append:
|
if not append:
|
||||||
self.conn.execute('DELETE FROM books_tags_link WHERE book=?', (id,))
|
self.conn.execute('DELETE FROM books_tags_link WHERE book=?', (id,))
|
||||||
self.conn.execute('''DELETE FROM tags WHERE (SELECT COUNT(id)
|
|
||||||
FROM books_tags_link WHERE tag=tags.id) < 1''')
|
|
||||||
otags = self.get_tags(id)
|
otags = self.get_tags(id)
|
||||||
tags = self.cleanup_tags(tags)
|
tags = self.cleanup_tags(tags)
|
||||||
books_to_refresh = set([])
|
books_to_refresh = {id}
|
||||||
for tag in (set(tags)-otags):
|
for tag in (set(tags)-otags):
|
||||||
case_changed = False
|
case_changed = False
|
||||||
tag = tag.strip()
|
tag = tag.strip()
|
||||||
@ -3089,6 +3089,8 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
|||||||
bks = self.conn.get('SELECT book FROM books_tags_link WHERE tag=?',
|
bks = self.conn.get('SELECT book FROM books_tags_link WHERE tag=?',
|
||||||
(tid,))
|
(tid,))
|
||||||
books_to_refresh |= set([bk[0] for bk in bks])
|
books_to_refresh |= set([bk[0] for bk in bks])
|
||||||
|
self.conn.execute('''DELETE FROM tags WHERE (SELECT COUNT(id)
|
||||||
|
FROM books_tags_link WHERE tag=tags.id) < 1''')
|
||||||
self.dirtied(set([id])|books_to_refresh, commit=False)
|
self.dirtied(set([id])|books_to_refresh, commit=False)
|
||||||
if commit:
|
if commit:
|
||||||
self.conn.commit()
|
self.conn.commit()
|
||||||
@ -3139,11 +3141,8 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
|||||||
|
|
||||||
def set_series(self, id, series, notify=True, commit=True, allow_case_change=True):
|
def set_series(self, id, series, notify=True, commit=True, allow_case_change=True):
|
||||||
self.conn.execute('DELETE FROM books_series_link WHERE book=?',(id,))
|
self.conn.execute('DELETE FROM books_series_link WHERE book=?',(id,))
|
||||||
self.conn.execute('''DELETE FROM series
|
|
||||||
WHERE (SELECT COUNT(id) FROM books_series_link
|
|
||||||
WHERE series=series.id) < 1''')
|
|
||||||
(series, idx) = self._get_series_values(series)
|
(series, idx) = self._get_series_values(series)
|
||||||
books_to_refresh = set([])
|
books_to_refresh = {id}
|
||||||
if series:
|
if series:
|
||||||
case_change = False
|
case_change = False
|
||||||
if not isinstance(series, unicode):
|
if not isinstance(series, unicode):
|
||||||
@ -3159,6 +3158,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
|||||||
case_change = True
|
case_change = True
|
||||||
else:
|
else:
|
||||||
series = cur_name
|
series = cur_name
|
||||||
|
books_to_refresh = set()
|
||||||
else:
|
else:
|
||||||
aid = self.conn.execute('INSERT INTO series(name) VALUES (?)', (series,)).lastrowid
|
aid = self.conn.execute('INSERT INTO series(name) VALUES (?)', (series,)).lastrowid
|
||||||
self.conn.execute('INSERT INTO books_series_link(book, series) VALUES (?,?)', (id, aid))
|
self.conn.execute('INSERT INTO books_series_link(book, series) VALUES (?,?)', (id, aid))
|
||||||
@ -3168,6 +3168,9 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
|||||||
bks = self.conn.get('SELECT book FROM books_series_link WHERE series=?',
|
bks = self.conn.get('SELECT book FROM books_series_link WHERE series=?',
|
||||||
(aid,))
|
(aid,))
|
||||||
books_to_refresh |= set([bk[0] for bk in bks])
|
books_to_refresh |= set([bk[0] for bk in bks])
|
||||||
|
self.conn.execute('''DELETE FROM series
|
||||||
|
WHERE (SELECT COUNT(id) FROM books_series_link
|
||||||
|
WHERE series=series.id) < 1''')
|
||||||
self.dirtied([id], commit=False)
|
self.dirtied([id], commit=False)
|
||||||
if commit:
|
if commit:
|
||||||
self.conn.commit()
|
self.conn.commit()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user