%PDF- %PDF-
Direktori : /usr/lib/calibre/calibre/db/tests/ |
Current File : //usr/lib/calibre/calibre/db/tests/add_remove.py |
#!/usr/bin/env python3 __license__ = 'GPL v3' __copyright__ = '2013, Kovid Goyal <kovid at kovidgoyal.net>' __docformat__ = 'restructuredtext en' import os, glob from io import BytesIO from tempfile import NamedTemporaryFile from datetime import timedelta from calibre.db.tests.base import BaseTest, IMG from calibre.ptempfile import PersistentTemporaryFile from calibre.utils.date import now, UNDEFINED_DATE from polyglot.builtins import iteritems, itervalues def import_test(replacement_data, replacement_fmt=None): def func(path, fmt): if not path.endswith('.'+fmt.lower()): raise AssertionError('path extension does not match format') ext = (replacement_fmt or fmt).lower() with PersistentTemporaryFile('.'+ext) as f: f.write(replacement_data) return f.name return func class AddRemoveTest(BaseTest): def test_add_format(self): # {{{ 'Test adding formats to an existing book record' af, ae, at = self.assertFalse, self.assertEqual, self.assertTrue cache = self.init_cache() table = cache.fields['formats'].table NF = b'test_add_formatxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' # Test that replace=False works previous = cache.format(1, 'FMT1') af(cache.add_format(1, 'FMT1', BytesIO(NF), replace=False)) ae(previous, cache.format(1, 'FMT1')) # Test that replace=True works lm = cache.field_for('last_modified', 1) at(cache.add_format(1, 'FMT1', BytesIO(NF), replace=True)) ae(NF, cache.format(1, 'FMT1')) ae(cache.format_metadata(1, 'FMT1')['size'], len(NF)) at(cache.field_for('size', 1) >= len(NF)) at(cache.field_for('last_modified', 1) > lm) ae(('FMT2','FMT1'), cache.formats(1)) at(1 in table.col_book_map['FMT1']) # Test adding a format to a record with no formats at(cache.add_format(3, 'FMT1', BytesIO(NF), replace=True)) ae(NF, cache.format(3, 'FMT1')) ae(cache.format_metadata(3, 'FMT1')['size'], len(NF)) ae(('FMT1',), cache.formats(3)) at(3 in table.col_book_map['FMT1']) at(cache.add_format(3, 'FMTX', BytesIO(NF), replace=True)) at(3 in table.col_book_map['FMTX']) ae(('FMT1','FMTX'), cache.formats(3)) # Test running on import plugins import calibre.db.cache as c orig = c.run_plugins_on_import try: c.run_plugins_on_import = import_test(b'replacement data') at(cache.add_format(3, 'REPL', BytesIO(NF))) ae(b'replacement data', cache.format(3, 'REPL')) c.run_plugins_on_import = import_test(b'replacement data2', 'REPL2') with NamedTemporaryFile(suffix='_test_add_format.repl') as f: f.write(NF) f.seek(0) at(cache.add_format(3, 'REPL', BytesIO(NF))) ae(b'replacement data', cache.format(3, 'REPL')) ae(b'replacement data2', cache.format(3, 'REPL2')) finally: c.run_plugins_on_import = orig # Test adding FMT with path with NamedTemporaryFile(suffix='_test_add_format.fmt9') as f: f.write(NF) f.seek(0) at(cache.add_format(2, 'FMT9', f)) ae(NF, cache.format(2, 'FMT9')) ae(cache.format_metadata(2, 'FMT9')['size'], len(NF)) at(cache.field_for('size', 2) >= len(NF)) at(2 in table.col_book_map['FMT9']) del cache # Test that the old interface also shows correct format data db = self.init_old() ae(db.formats(3, index_is_id=True), ','.join(['FMT1', 'FMTX', 'REPL', 'REPL2'])) ae(db.format(3, 'FMT1', index_is_id=True), NF) ae(db.format(1, 'FMT1', index_is_id=True), NF) db.close() del db # }}} def test_remove_formats(self): # {{{ 'Test removal of formats from book records' af, ae, at = self.assertFalse, self.assertEqual, self.assertTrue cache = self.init_cache() # Test removal of non-existing format does nothing formats = {bid:tuple(cache.formats(bid)) for bid in (1, 2, 3)} cache.remove_formats({1:{'NF'}, 2:{'NF'}, 3:{'NF'}}) nformats = {bid:tuple(cache.formats(bid)) for bid in (1, 2, 3)} ae(formats, nformats) # Test full removal of format af(cache.format(1, 'FMT1') is None) at(cache.has_format(1, 'FMT1')) ap = cache.format_abspath(1, 'FMT1') at(os.path.exists(ap)) cache.remove_formats({1:{'FMT1'}}) at(cache.format(1, 'FMT1') is None) af(bool(cache.format_metadata(1, 'FMT1'))) af(bool(cache.format_metadata(1, 'FMT1', allow_cache=False))) af('FMT1' in cache.formats(1)) af(cache.has_format(1, 'FMT1')) af(os.path.exists(ap)) # Test db only removal at(cache.has_format(1, 'FMT2')) ap = cache.format_abspath(1, 'FMT2') if ap and os.path.exists(ap): cache.remove_formats({1:{'FMT2'}}, db_only=True) af(bool(cache.format_metadata(1, 'FMT2'))) af(cache.has_format(1, 'FMT2')) at(os.path.exists(ap)) # Test that the old interface agrees db = self.init_old() at(db.format(1, 'FMT1', index_is_id=True) is None) db.close() del db # }}} def test_create_book_entry(self): # {{{ 'Test the creation of new book entries' from calibre.ebooks.metadata.book.base import Metadata cache = self.init_cache() mi = Metadata('Created One', authors=('Creator One', 'Creator Two')) book_id = cache.create_book_entry(mi) self.assertIsNot(book_id, None) def do_test(cache, book_id): for field in ('path', 'uuid', 'author_sort', 'timestamp', 'pubdate', 'title', 'authors', 'series_index', 'sort'): self.assertTrue(cache.field_for(field, book_id)) for field in ('size', 'cover'): self.assertFalse(cache.field_for(field, book_id)) self.assertEqual(book_id, cache.fields['uuid'].table.uuid_to_id_map[cache.field_for('uuid', book_id)]) self.assertLess(now() - cache.field_for('timestamp', book_id), timedelta(seconds=30)) self.assertEqual(('Created One', ('Creator One', 'Creator Two')), (cache.field_for('title', book_id), cache.field_for('authors', book_id))) self.assertEqual(cache.field_for('series_index', book_id), 1.0) self.assertEqual(cache.field_for('pubdate', book_id), UNDEFINED_DATE) do_test(cache, book_id) # Test that the db contains correct data cache = self.init_cache() do_test(cache, book_id) self.assertIs(None, cache.create_book_entry(mi, add_duplicates=False), 'Duplicate added incorrectly') book_id = cache.create_book_entry(mi, cover=IMG) self.assertIsNot(book_id, None) self.assertEqual(IMG, cache.cover(book_id)) import calibre.db.cache as c orig = c.prefs c.prefs = {'new_book_tags':('newbook', 'newbook2')} try: book_id = cache.create_book_entry(mi) self.assertEqual(('newbook', 'newbook2'), cache.field_for('tags', book_id)) mi.tags = ('one', 'two') book_id = cache.create_book_entry(mi) self.assertEqual(('one', 'two') + ('newbook', 'newbook2'), cache.field_for('tags', book_id)) mi.tags = () finally: c.prefs = orig mi.uuid = 'a preserved uuid' book_id = cache.create_book_entry(mi, preserve_uuid=True) self.assertEqual(mi.uuid, cache.field_for('uuid', book_id)) # }}} def test_add_books(self): # {{{ 'Test the adding of new books' from calibre.ebooks.metadata.book.base import Metadata cache = self.init_cache() mi = Metadata('Created One', authors=('Creator One', 'Creator Two')) FMT1, FMT2 = b'format1', b'format2' format_map = {'FMT1':BytesIO(FMT1), 'FMT2':BytesIO(FMT2)} ids, duplicates = cache.add_books([(mi, format_map)]) self.assertTrue(len(ids) == 1) self.assertFalse(duplicates) book_id = ids[0] self.assertEqual(set(cache.formats(book_id)), {'FMT1', 'FMT2'}) self.assertEqual(cache.format(book_id, 'FMT1'), FMT1) self.assertEqual(cache.format(book_id, 'FMT2'), FMT2) # }}} def test_remove_books(self): # {{{ 'Test removal of books' cl = self.cloned_library cache = self.init_cache() af, ae = self.assertFalse, self.assertEqual authors = cache.fields['authors'].table # Delete a single book, with no formats and check cleaning self.assertIn('Unknown', set(itervalues(authors.id_map))) olen = len(authors.id_map) item_id = {v:k for k, v in iteritems(authors.id_map)}['Unknown'] cache.remove_books((3,)) for c in (cache, self.init_cache()): table = c.fields['authors'].table self.assertNotIn(3, c.all_book_ids()) self.assertNotIn('Unknown', set(itervalues(table.id_map))) self.assertNotIn(item_id, table.asort_map) self.assertNotIn(item_id, table.alink_map) ae(len(table.id_map), olen-1) # Check that files are removed fmtpath = cache.format_abspath(1, 'FMT1') bookpath = os.path.dirname(fmtpath) authorpath = os.path.dirname(bookpath) os.mkdir(os.path.join(authorpath, '.DS_Store')) open(os.path.join(authorpath, 'Thumbs.db'), 'wb').close() item_id = {v:k for k, v in iteritems(cache.fields['#series'].table.id_map)}['My Series Two'] cache.remove_books((1,), permanent=True) for x in (fmtpath, bookpath, authorpath): af(os.path.exists(x), 'The file %s exists, when it should not' % x) for c in (cache, self.init_cache()): table = c.fields['authors'].table self.assertNotIn(1, c.all_book_ids()) self.assertNotIn('Author Two', set(itervalues(table.id_map))) self.assertNotIn(6, set(itervalues(c.fields['rating'].table.id_map))) self.assertIn('A Series One', set(itervalues(c.fields['series'].table.id_map))) self.assertNotIn('My Series Two', set(itervalues(c.fields['#series'].table.id_map))) self.assertNotIn(item_id, c.fields['#series'].table.col_book_map) self.assertNotIn(1, c.fields['#series'].table.book_col_map) # Test emptying the db cache.remove_books(cache.all_book_ids(), permanent=True) for f in ('authors', 'series', '#series', 'tags'): table = cache.fields[f].table self.assertFalse(table.id_map) self.assertFalse(table.book_col_map) self.assertFalse(table.col_book_map) # Test the delete service from calibre.db.delete_service import delete_service cache = self.init_cache(cl) # Check that files are removed fmtpath = cache.format_abspath(1, 'FMT1') bookpath = os.path.dirname(fmtpath) authorpath = os.path.dirname(bookpath) item_id = {v:k for k, v in iteritems(cache.fields['#series'].table.id_map)}['My Series Two'] cache.remove_books((1,)) delete_service().wait() for x in (fmtpath, bookpath, authorpath): af(os.path.exists(x), 'The file %s exists, when it should not' % x) # }}} def test_original_fmt(self): # {{{ ' Test management of original fmt ' af, ae, at = self.assertFalse, self.assertEqual, self.assertTrue db = self.init_cache() fmts = db.formats(1) af(db.has_format(1, 'ORIGINAL_FMT1')) at(db.save_original_format(1, 'FMT1')) at(db.has_format(1, 'ORIGINAL_FMT1')) raw = db.format(1, 'FMT1') ae(raw, db.format(1, 'ORIGINAL_FMT1')) db.add_format(1, 'FMT1', BytesIO(b'replacedfmt')) self.assertNotEqual(db.format(1, 'FMT1'), db.format(1, 'ORIGINAL_FMT1')) at(db.restore_original_format(1, 'ORIGINAL_FMT1')) ae(raw, db.format(1, 'FMT1')) af(db.has_format(1, 'ORIGINAL_FMT1')) ae(set(fmts), set(db.formats(1, verify_formats=False))) # }}} def test_format_orphan(self): # {{{ ' Test that adding formats does not create orphans if the file name algorithm changes ' cache = self.init_cache() path = cache.format_abspath(1, 'FMT1') base, name = os.path.split(path) prefix = 'mushroomxx' os.rename(path, os.path.join(base, prefix + name)) cache.fields['formats'].table.fname_map[1]['FMT1'] = prefix + os.path.splitext(name)[0] old = glob.glob(os.path.join(base, '*.fmt1')) cache.add_format(1, 'FMT1', BytesIO(b'xxxx'), run_hooks=False) new = glob.glob(os.path.join(base, '*.fmt1')) self.assertNotEqual(old, new) self.assertEqual(len(old), len(new)) self.assertNotIn(prefix, cache.fields['formats'].format_fname(1, 'FMT1')) # }}} def test_copy_to_library(self): # {{{ from calibre.db.copy_to_library import copy_one_book from calibre.ebooks.metadata import authors_to_string src_db = self.init_cache() dest_db = self.init_cache(self.cloned_library) def make_rdata(book_id=1, new_book_id=None, action='add'): return { 'title': src_db.field_for('title', book_id), 'authors': list(src_db.field_for('authors', book_id)), 'author': authors_to_string(src_db.field_for('authors', book_id)), 'book_id': book_id, 'new_book_id': new_book_id, 'action': action } def compare_field(field, func=self.assertEqual): func(src_db.field_for(field, rdata['book_id']), dest_db.field_for(field, rdata['new_book_id'])) rdata = copy_one_book(1, src_db, dest_db) self.assertEqual(rdata, make_rdata(new_book_id=max(dest_db.all_book_ids()))) compare_field('timestamp') compare_field('uuid', self.assertNotEqual) rdata = copy_one_book(1, src_db, dest_db, preserve_date=False, preserve_uuid=True) self.assertEqual(rdata, make_rdata(new_book_id=max(dest_db.all_book_ids()))) compare_field('timestamp', self.assertNotEqual) compare_field('uuid') rdata = copy_one_book(1, src_db, dest_db, duplicate_action='ignore') self.assertIsNone(rdata['new_book_id']) self.assertEqual(rdata['action'], 'duplicate') src_db.add_format(1, 'FMT1', BytesIO(b'replaced'), run_hooks=False) rdata = copy_one_book(1, src_db, dest_db, duplicate_action='add_formats_to_existing') self.assertEqual(rdata['action'], 'automerge') for new_book_id in (1, 4, 5): self.assertEqual(dest_db.format(new_book_id, 'FMT1'), b'replaced') src_db.add_format(1, 'FMT1', BytesIO(b'second-round'), run_hooks=False) rdata = copy_one_book(1, src_db, dest_db, duplicate_action='add_formats_to_existing', automerge_action='ignore') self.assertEqual(rdata['action'], 'automerge') for new_book_id in (1, 4, 5): self.assertEqual(dest_db.format(new_book_id, 'FMT1'), b'replaced') rdata = copy_one_book(1, src_db, dest_db, duplicate_action='add_formats_to_existing', automerge_action='new record') self.assertEqual(rdata['action'], 'automerge') for new_book_id in (1, 4, 5): self.assertEqual(dest_db.format(new_book_id, 'FMT1'), b'replaced') self.assertEqual(dest_db.format(rdata['new_book_id'], 'FMT1'), b'second-round') # }}}