From 937f475a9ef35b062afd93cdcd1457451b400447 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 11 Jul 2013 21:20:20 +0530 Subject: [PATCH 1/8] Implement adding of catalogs and news --- src/calibre/db/adding.py | 63 ++++++++++++++++++++++++++++++++++ src/calibre/db/legacy.py | 11 +++++- src/calibre/db/search.py | 8 ++--- src/calibre/db/tests/legacy.py | 19 ++++++++++ 4 files changed, 96 insertions(+), 5 deletions(-) diff --git a/src/calibre/db/adding.py b/src/calibre/db/adding.py index 34ac84c493..d777e7065e 100644 --- a/src/calibre/db/adding.py +++ b/src/calibre/db/adding.py @@ -100,3 +100,66 @@ def recursive_import(db, root, single_book_per_directory=True, break return duplicates +def add_catalog(cache, path, title): + from calibre.ebooks.metadata.book.base import Metadata + from calibre.ebooks.metadata.meta import get_metadata + from calibre.utils.date import utcnow + + fmt = os.path.splitext(path)[1][1:].lower() + with lopen(path, 'rb') as stream, cache.write_lock: + matches = cache._search('title:="%s" and tags:="%s"' % (title.replace('"', '\\"'), _('Catalog')), None) + db_id = None + if matches: + db_id = list(matches)[0] + try: + mi = get_metadata(stream, fmt) + mi.authors = ['calibre'] + except: + mi = Metadata(title, ['calibre']) + mi.title, mi.authors = title, ['calibre'] + mi.tags = [_('Catalog')] + mi.pubdate = mi.timestamp = utcnow() + if fmt == 'mobi': + mi.cover, mi.cover_data = None, (None, None) + if db_id is None: + db_id = cache._create_book_entry(mi, apply_import_tags=False) + else: + cache._set_metadata(db_id, mi) + cache._add_format(db_id, fmt, stream) + + return db_id + +def add_news(cache, path, arg): + from calibre.ebooks.metadata.meta import get_metadata + from calibre.utils.date import utcnow + + fmt = os.path.splitext(getattr(path, 'name', path))[1][1:].lower() + stream = path if hasattr(path, 'read') else lopen(path, 'rb') + stream.seek(0) + mi = get_metadata(stream, fmt, use_libprs_metadata=False, + force_read_metadata=True) + # Force the author to calibre as the auto delete of old news checks for + # both the author==calibre and the tag News + mi.authors = ['calibre'] + stream.seek(0) + with cache.write_lock: + if mi.series_index is None: + mi.series_index = cache._get_next_series_num_for(mi.series) + mi.tags = [_('News')] + if arg['add_title_tag']: + mi.tags += [arg['title']] + if arg['custom_tags']: + mi.tags += arg['custom_tags'] + if mi.pubdate is None: + mi.pubdate = utcnow() + if mi.timestamp is None: + mi.timestamp = utcnow() + + db_id = cache._create_book_entry(mi, apply_import_tags=False) + cache._add_format(db_id, fmt, stream) + + if not hasattr(path, 'read'): + stream.close() + return db_id + + diff --git a/src/calibre/db/legacy.py b/src/calibre/db/legacy.py index e75ea12169..b9f23cc5d7 100644 --- a/src/calibre/db/legacy.py +++ b/src/calibre/db/legacy.py @@ -11,7 +11,9 @@ from functools import partial from future_builtins import zip from calibre.db import _get_next_series_num_for_list, _get_series_values -from calibre.db.adding import find_books_in_directory, import_book_directory_multiple, import_book_directory, recursive_import +from calibre.db.adding import ( + find_books_in_directory, import_book_directory_multiple, + import_book_directory, recursive_import, add_catalog, add_news) from calibre.db.backend import DB from calibre.db.cache import Cache from calibre.db.categories import CATEGORY_SORTS @@ -205,6 +207,13 @@ class LibraryDatabase(object): def recursive_import(self, root, single_book_per_directory=True, callback=None, added_ids=None): return recursive_import(self, root, single_book_per_directory=single_book_per_directory, callback=callback, added_ids=added_ids) + + def add_catalog(self, path, title): + return add_catalog(self.new_api, path, title) + + def add_news(self, path, arg): + return add_news(self.new_api, path, arg) + # }}} # Private interface {{{ diff --git a/src/calibre/db/search.py b/src/calibre/db/search.py index 4a6eace0f7..7b4ad90bc3 100644 --- a/src/calibre/db/search.py +++ b/src/calibre/db/search.py @@ -526,7 +526,7 @@ class Parser(SearchQueryParser): if dt == 'bool': return self.bool_search(icu_lower(query), partial(self.field_iter, location, candidates), - self.dbcache.pref('bools_are_tristate')) + self.dbcache._pref('bools_are_tristate')) # special case: colon-separated fields such as identifiers. isbn # is a special case within the case @@ -630,7 +630,7 @@ class Parser(SearchQueryParser): if len(query) < 2: return matches - user_cats = self.dbcache.pref('user_categories') + user_cats = self.dbcache._pref('user_categories') c = set(candidates) if query.startswith('.'): @@ -674,7 +674,7 @@ class Search(object): if search_restriction: q = u'(%s) and (%s)' % (search_restriction, query) - all_book_ids = dbcache.all_book_ids(type=set) + all_book_ids = dbcache._all_book_ids(type=set) if not q: return all_book_ids @@ -686,7 +686,7 @@ class Search(object): # takes 0.000975 seconds and restoring it from a pickle takes # 0.000974 seconds. sqp = Parser( - dbcache, all_book_ids, dbcache.pref('grouped_search_terms'), + dbcache, all_book_ids, dbcache._pref('grouped_search_terms'), self.date_search, self.num_search, self.bool_search, self.keypair_search, prefs['limit_search_columns'], diff --git a/src/calibre/db/tests/legacy.py b/src/calibre/db/tests/legacy.py index cf3bd93d9d..af16c7c5eb 100644 --- a/src/calibre/db/tests/legacy.py +++ b/src/calibre/db/tests/legacy.py @@ -178,6 +178,25 @@ class LegacyTest(BaseTest): T() T({'add_duplicates':False}) T({'force_id':1000}) + + with NamedTemporaryFile(suffix='.txt') as f: + f.write(b'tttttt') + f.seek(0) + bid = legacy.add_catalog(f.name, 'My Catalog') + cache = legacy.new_api + self.assertEqual(cache.formats(bid), ('TXT',)) + self.assertEqual(cache.field_for('title', bid), 'My Catalog') + self.assertEqual(cache.field_for('authors', bid), ('calibre',)) + self.assertEqual(cache.field_for('tags', bid), (_('Catalog'),)) + self.assertTrue(bid < legacy.add_catalog(f.name, 'Something else')) + self.assertEqual(legacy.add_catalog(f.name, 'My Catalog'), bid) + + bid = legacy.add_news(f.name, {'title':'Events', 'add_title_tag':True, 'custom_tags':('one', 'two')}) + self.assertEqual(cache.formats(bid), ('TXT',)) + self.assertEqual(cache.field_for('authors', bid), ('calibre',)) + self.assertEqual(cache.field_for('tags', bid), (_('News'), 'Events', 'one', 'two')) + + old.close() # }}} def test_legacy_coverage(self): # {{{ From a8dba2f02072207174bbae65ed01c51a3ecab93b Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 11 Jul 2013 21:25:06 +0530 Subject: [PATCH 2/8] ... --- src/calibre/db/tests/legacy.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/calibre/db/tests/legacy.py b/src/calibre/db/tests/legacy.py index af16c7c5eb..ef47f5d7bf 100644 --- a/src/calibre/db/tests/legacy.py +++ b/src/calibre/db/tests/legacy.py @@ -183,6 +183,7 @@ class LegacyTest(BaseTest): f.write(b'tttttt') f.seek(0) bid = legacy.add_catalog(f.name, 'My Catalog') + self.assertEqual(old.add_catalog(f.name, 'My Catalog'), bid) cache = legacy.new_api self.assertEqual(cache.formats(bid), ('TXT',)) self.assertEqual(cache.field_for('title', bid), 'My Catalog') @@ -190,6 +191,7 @@ class LegacyTest(BaseTest): self.assertEqual(cache.field_for('tags', bid), (_('Catalog'),)) self.assertTrue(bid < legacy.add_catalog(f.name, 'Something else')) self.assertEqual(legacy.add_catalog(f.name, 'My Catalog'), bid) + self.assertEqual(old.add_catalog(f.name, 'My Catalog'), bid) bid = legacy.add_news(f.name, {'title':'Events', 'add_title_tag':True, 'custom_tags':('one', 'two')}) self.assertEqual(cache.formats(bid), ('TXT',)) From 26d20cdf6baf6d37d2fef66a504228911957f6b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20D=C5=82ugosz?= Date: Thu, 11 Jul 2013 22:30:26 +0200 Subject: [PATCH 3/8] update empik plugin for website change --- src/calibre/gui2/store/stores/empik_plugin.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/calibre/gui2/store/stores/empik_plugin.py b/src/calibre/gui2/store/stores/empik_plugin.py index c771722120..b716567e54 100644 --- a/src/calibre/gui2/store/stores/empik_plugin.py +++ b/src/calibre/gui2/store/stores/empik_plugin.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- from __future__ import (unicode_literals, division, absolute_import, print_function) -store_version = 2 # Needed for dynamic plugin loading +store_version = 3 # Needed for dynamic plugin loading __license__ = 'GPL 3' __copyright__ = '2011-2013, Tomasz Długosz ' @@ -51,7 +51,7 @@ class EmpikStore(BasicStoreConfig, StorePlugin): if not id: continue - cover_url = ''.join(data.xpath('.//div[@class="productBox-450Pic"]/a/img/@data-original')) + cover_url = ''.join(data.xpath('.//div[@class="productBox-450Pic"]/a/img/@src')) title = ''.join(data.xpath('.//a[@class="productBox-450Title"]/text()')) title = re.sub(r' \(ebook\)', '', title) author = ''.join(data.xpath('.//div[@class="productBox-450Author"]/a/text()')) From 90599f9c46a4afbf8556aadac8616fc7c2c89774 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 12 Jul 2013 08:16:55 +0530 Subject: [PATCH 4/8] version 0.9.39 --- Changelog.yaml | 39 +++++++++++++++++++++++++++++++++++++++ src/calibre/constants.py | 2 +- 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/Changelog.yaml b/Changelog.yaml index f68617fb3a..db25f77a8d 100644 --- a/Changelog.yaml +++ b/Changelog.yaml @@ -20,6 +20,45 @@ # new recipes: # - title: +- version: 0.9.39 + date: 2013-07-12 + + new features: + - title: "Bulk metadata edit: Add a checkbox to prevent the refreshing of the book list after the bulk edit. This means that the book list will not be resorted and any existing search/virtual library will not be refreshed. Useful if you have a large library as the refresh can be slow." + + - title: "Allow manually marking a book in the calibre library as being on the device. To do so click the device icon in calibre, then right click on the book you want marked and choose 'Match book to library'. Once you are done marking all the books, right click the device icon and choose 'Update cached metadata'" + + - title: "Driver for Coby Kyros MID1126" + tickets: [1199410] + + - title: "When adding formats to an existing book, by right clicking the add books button, ask for confirmation if some formats will be overwritten." + + - title: "Add a tweak to restrict the list of output formats available in the conversion dialog. Go to Preferences->Tweaks to change it." + + bug fixes: + - title: "Amazon metadata download: Update plugin to deal with the new amazon.com website" + + - title: "Edelweiss metadata download plugin: Workaround for advanced search being broken at the Edelweiss website." + + - title: "Invalid data in the device database on sony readers could cause errors when sorting device collections, ignore those errors." + + - title: "DOCX Input: Fix no page break being inserted before the last section." + tickets: [1198414] + + - title: "Metadata download dialog: Have the OK button enabled in the results screen as well." + tickets: [1198288] + + - title: "Get Books: Update empik store plugin" + + improved recipes: + - Houston Chronicle + - cracked.com + - mediapart.fr + + new recipes: + - title: Glenn Brenwald and Ludwig von Mises Institute + author: anywho + - version: 0.9.38 date: 2013-07-05 diff --git a/src/calibre/constants.py b/src/calibre/constants.py index 5c9ecbc832..18b4e3d238 100644 --- a/src/calibre/constants.py +++ b/src/calibre/constants.py @@ -4,7 +4,7 @@ __license__ = 'GPL v3' __copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net' __docformat__ = 'restructuredtext en' __appname__ = u'calibre' -numeric_version = (0, 9, 38) +numeric_version = (0, 9, 39) __version__ = u'.'.join(map(unicode, numeric_version)) __author__ = u"Kovid Goyal " From 6c9a6c1020f0641fa7ee7757c0d151de28298891 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 12 Jul 2013 10:23:38 +0530 Subject: [PATCH 5/8] Implement custom book data API --- src/calibre/db/backend.py | 41 ++++++++++++++++++++++++++++++++++ src/calibre/db/cache.py | 30 +++++++++++++++++++++++++ src/calibre/db/legacy.py | 23 +++++++++++++++++++ src/calibre/db/tests/legacy.py | 31 +++++++++++++++++++++++++ 4 files changed, 125 insertions(+) diff --git a/src/calibre/db/backend.py b/src/calibre/db/backend.py index d75106209f..8ebdc4a154 100644 --- a/src/calibre/db/backend.py +++ b/src/calibre/db/backend.py @@ -1175,5 +1175,46 @@ class DB(object): self.rmtree(parent, permanent=permanent) self.conn.executemany( 'DELETE FROM books WHERE id=?', [(x,) for x in path_map]) + + def add_custom_data(self, name, val_map, delete_first): + if delete_first: + self.conn.execute('DELETE FROM books_plugin_data WHERE name=?', (name, )) + self.conn.executemany( + 'INSERT OR REPLACE INTO books_plugin_data (book, name, val) VALUES (?, ?, ?)', + [(book_id, name, json.dumps(val, default=to_json)) + for book_id, val in val_map.iteritems()]) + + def get_custom_book_data(self, name, book_ids, default=None): + book_ids = frozenset(book_ids) + def safe_load(val): + try: + return json.loads(val, object_hook=from_json) + except: + return default + + if len(book_ids) == 1: + bid = next(iter(book_ids)) + ans = {book_id:safe_load(val) for book_id, val in + self.conn.execute('SELECT book, val FROM books_plugin_data WHERE book=? AND name=?', (bid, name))} + return ans or {bid:default} + + ans = {} + for book_id, val in self.conn.execute( + 'SELECT book, val FROM books_plugin_data WHERE name=?', (name,)): + if not book_ids or book_id in book_ids: + val = safe_load(val) + ans[book_id] = val + return ans + + def delete_custom_book_data(self, name, book_ids): + if book_ids: + self.conn.executemany('DELETE FROM books_plugin_data WHERE book=? AND name=?', + [(book_id, name) for book_id in book_ids]) + else: + self.conn.execute('DELETE FROM books_plugin_data WHERE name=?', (name,)) + + def get_ids_for_custom_book_data(self, name): + return frozenset(r[0] for r in self.conn.execute('SELECT book FROM books_plugin_data WHERE name=?', (name,))) + # }}} diff --git a/src/calibre/db/cache.py b/src/calibre/db/cache.py index b8b9ad7436..49f5bd24ec 100644 --- a/src/calibre/db/cache.py +++ b/src/calibre/db/cache.py @@ -1147,6 +1147,36 @@ class Cache(object): else: table.remove_books(book_ids, self.backend) + @write_api + def add_custom_book_data(self, name, val_map, delete_first=False): + ''' Add data for name where val_map is a map of book_ids to values. If + delete_first is True, all previously stored data for name will be + removed. ''' + missing = frozenset(val_map) - self._all_book_ids() + if missing: + raise ValueError('add_custom_book_data: no such book_ids: %d'%missing) + self.backend.add_custom_data(name, val_map, delete_first) + + @read_api + def get_custom_book_data(self, name, book_ids=(), default=None): + ''' Get data for name. By default returns data for all book_ids, pass + in a list of book ids if you only want some data. Returns a map of + book_id to values. If a particular value could not be decoded, uses + default for it. ''' + return self.backend.get_custom_book_data(name, book_ids, default) + + @write_api + def delete_custom_book_data(self, name, book_ids=()): + ''' Delete data for name. By default deletes all data, if you only want + to delete data for some book ids, pass in a list of book ids. ''' + self.backend.delete_custom_book_data(name, book_ids) + + @read_api + def get_ids_for_custom_book_data(self, name): + ''' Return the set of book ids for which name has data. ''' + return self.backend.get_ids_for_custom_book_data(name) + + # }}} class SortKey(object): # {{{ diff --git a/src/calibre/db/legacy.py b/src/calibre/db/legacy.py index b9f23cc5d7..a12e949b55 100644 --- a/src/calibre/db/legacy.py +++ b/src/calibre/db/legacy.py @@ -216,6 +216,29 @@ class LibraryDatabase(object): # }}} + # Custom data {{{ + def add_custom_book_data(self, book_id, name, val): + self.new_api.add_custom_book_data(name, {book_id:val}) + + def add_multiple_custom_book_data(self, name, val_map, delete_first=False): + self.new_api.add_custom_book_data(name, val_map, delete_first=delete_first) + + def get_custom_book_data(self, book_id, name, default=None): + return self.new_api.get_custom_book_data(name, book_ids={book_id}, default=default).get(book_id, default) + + def get_all_custom_book_data(self, name, default=None): + return self.new_api.get_custom_book_data(name, default=default) + + def delete_custom_book_data(self, book_id, name): + self.new_api.delete_custom_book_data(name, book_ids=(book_id,)) + + def delete_all_custom_book_data(self, name): + self.new_api.delete_custom_book_data(name) + + def get_ids_for_custom_book_data(self, name): + return list(self.new_api.get_ids_for_custom_book_data(name)) + # }}} + # Private interface {{{ def __iter__(self): diff --git a/src/calibre/db/tests/legacy.py b/src/calibre/db/tests/legacy.py index ef47f5d7bf..a94e59d17f 100644 --- a/src/calibre/db/tests/legacy.py +++ b/src/calibre/db/tests/legacy.py @@ -245,3 +245,34 @@ class LegacyTest(BaseTest): # }}} + def test_legacy_custom_data(self): # {{{ + 'Test the API for custom data storage' + legacy, old = self.init_legacy(self.cloned_library), self.init_old(self.cloned_library) + for name in ('name1', 'name2', 'name3'): + T = partial(ET, 'add_custom_book_data', old=old, legacy=legacy) + T((1, name, 'val1'))(self) + T((2, name, 'val2'))(self) + T((3, name, 'val3'))(self) + T = partial(ET, 'get_ids_for_custom_book_data', old=old, legacy=legacy) + T((name,))(self) + T = partial(ET, 'get_custom_book_data', old=old, legacy=legacy) + T((1, name, object())) + T((9, name, object())) + T = partial(ET, 'get_all_custom_book_data', old=old, legacy=legacy) + T((name, object())) + T((name+'!', object())) + T = partial(ET, 'delete_custom_book_data', old=old, legacy=legacy) + T((name, 1)) + T = partial(ET, 'get_all_custom_book_data', old=old, legacy=legacy) + T((name, object())) + T = partial(ET, 'delete_all_custom_book_data', old=old, legacy=legacy) + T((name)) + T = partial(ET, 'get_all_custom_book_data', old=old, legacy=legacy) + T((name, object())) + + T = partial(ET, 'add_multiple_custom_book_data', old=old, legacy=legacy) + T(('n', {1:'val1', 2:'val2'}))(self) + T = partial(ET, 'get_all_custom_book_data', old=old, legacy=legacy) + T(('n', object())) + old.close() + # }}} From f9bd87d785693e06f263a79c288a179fa54fd7ae Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 12 Jul 2013 10:29:56 +0530 Subject: [PATCH 6/8] Ignore unused feeds API --- src/calibre/db/tests/legacy.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/calibre/db/tests/legacy.py b/src/calibre/db/tests/legacy.py index a94e59d17f..dd9cbc2dc5 100644 --- a/src/calibre/db/tests/legacy.py +++ b/src/calibre/db/tests/legacy.py @@ -210,6 +210,8 @@ class LegacyTest(BaseTest): SKIP_ATTRS = { 'TCat_Tag', '_add_newbook_tag', '_clean_identifier', '_library_id_', '_set_authors', '_set_title', '_set_custom', '_update_author_in_cache', + # Feeds are now stored in the config folder + 'get_feeds', 'get_feed', 'update_feed', 'remove_feeds', 'add_feed', 'set_feeds', } SKIP_ARGSPEC = { '__init__', 'get_next_series_num_for', 'has_book', 'author_sort_from_authors', @@ -241,7 +243,7 @@ class LegacyTest(BaseTest): if missing: pc = len(missing)/total - raise AssertionError('{0:.1%} of API ({2} attrs) are missing. For example: {1}'.format(pc, missing[0], len(missing))) + raise AssertionError('{0:.1%} of API ({2} attrs) are missing. For example: {1}'.format(pc, ', '.join(missing[:5]), len(missing))) # }}} From 4a2a4a54d471731bdff97733f9605f24759230ef Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 12 Jul 2013 11:04:47 +0530 Subject: [PATCH 7/8] Add legacy add_format API --- src/calibre/db/legacy.py | 10 ++++++++++ src/calibre/db/tests/legacy.py | 6 ++++++ 2 files changed, 16 insertions(+) diff --git a/src/calibre/db/legacy.py b/src/calibre/db/legacy.py index a12e949b55..e1e533266f 100644 --- a/src/calibre/db/legacy.py +++ b/src/calibre/db/legacy.py @@ -214,6 +214,16 @@ class LibraryDatabase(object): def add_news(self, path, arg): return add_news(self.new_api, path, arg) + def add_format(self, index, fmt, stream, index_is_id=False, path=None, notify=True, replace=True, copy_function=None): + ''' path and copy_function are ignored by the new API ''' + book_id = index if index_is_id else self.data.index_to_id(index) + return self.new_api.add_format(book_id, fmt, stream, replace=replace, run_hooks=False, dbapi=self) + + def add_format_with_hooks(self, index, fmt, fpath, index_is_id=False, path=None, notify=True, replace=True): + ''' path is ignored by the new API ''' + book_id = index if index_is_id else self.data.index_to_id(index) + return self.new_api.add_format(book_id, fmt, fpath, replace=replace, run_hooks=True, dbapi=self) + # }}} # Custom data {{{ diff --git a/src/calibre/db/tests/legacy.py b/src/calibre/db/tests/legacy.py index dd9cbc2dc5..ced21f479d 100644 --- a/src/calibre/db/tests/legacy.py +++ b/src/calibre/db/tests/legacy.py @@ -7,6 +7,7 @@ __license__ = 'GPL v3' __copyright__ = '2013, Kovid Goyal ' import inspect +from io import BytesIO from repr import repr from functools import partial from tempfile import NamedTemporaryFile @@ -166,6 +167,11 @@ class LegacyTest(BaseTest): book_id = T(kwargs={'preserve_uuid':True})(self) self.assertEqual(legacy.uuid(book_id, index_is_id=True), old.uuid(book_id, index_is_id=True)) self.assertEqual(legacy.new_api.formats(book_id), ('AFF',)) + + T = partial(ET, 'add_format', old=old, legacy=legacy) + T((0, 'AFF', BytesIO(b'fffff')))(self) + T((0, 'AFF', BytesIO(b'fffff')))(self) + T((0, 'AFF', BytesIO(b'fffff')), {'replace':True})(self) with NamedTemporaryFile(suffix='.opf') as f: f.write(b'zzzz') f.flush() From 6a724d931f9856ce89396c8488cfd2d3811cc95f Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 12 Jul 2013 11:10:35 +0530 Subject: [PATCH 8/8] ... --- src/calibre/db/legacy.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/calibre/db/legacy.py b/src/calibre/db/legacy.py index e1e533266f..54d0887954 100644 --- a/src/calibre/db/legacy.py +++ b/src/calibre/db/legacy.py @@ -217,12 +217,22 @@ class LibraryDatabase(object): def add_format(self, index, fmt, stream, index_is_id=False, path=None, notify=True, replace=True, copy_function=None): ''' path and copy_function are ignored by the new API ''' book_id = index if index_is_id else self.data.index_to_id(index) - return self.new_api.add_format(book_id, fmt, stream, replace=replace, run_hooks=False, dbapi=self) + try: + return self.new_api.add_format(book_id, fmt, stream, replace=replace, run_hooks=False, dbapi=self) + except: + raise + else: + self.notify('metadata', [book_id]) def add_format_with_hooks(self, index, fmt, fpath, index_is_id=False, path=None, notify=True, replace=True): ''' path is ignored by the new API ''' book_id = index if index_is_id else self.data.index_to_id(index) - return self.new_api.add_format(book_id, fmt, fpath, replace=replace, run_hooks=True, dbapi=self) + try: + return self.new_api.add_format(book_id, fmt, fpath, replace=replace, run_hooks=True, dbapi=self) + except: + raise + else: + self.notify('metadata', [book_id]) # }}}