diff --git a/src/calibre/db/cache.py b/src/calibre/db/cache.py index a48a910bcb..bb2fe6c94e 100644 --- a/src/calibre/db/cache.py +++ b/src/calibre/db/cache.py @@ -1230,8 +1230,8 @@ class Cache(object): else: v = sid = None if sid is None and name.startswith('#'): - extra = self._fast_field_for(sfield, k) - sid = extra or 1.0 # The value to be set the db link table + sid = self._fast_field_for(sfield, k) + sid = 1.0 if sid is None else sid # The value to be set the db link table bimap[k] = v if sid is not None: simap[k] = sid @@ -1244,11 +1244,11 @@ class Cache(object): sf = self.fields[f.name+'_index'] dirtied |= sf.writer.set_books(simap, self.backend, allow_case_change=False) - if dirtied and update_path and do_path_update: - self._update_path(dirtied, mark_as_dirtied=False) - - self._mark_as_dirty(dirtied) - self.event_dispatcher(EventType.metadata_changed, name, dirtied) + if dirtied: + if update_path and do_path_update: + self._update_path(dirtied, mark_as_dirtied=False) + self._mark_as_dirty(dirtied) + self.event_dispatcher(EventType.metadata_changed, name, dirtied) return dirtied @write_api diff --git a/src/calibre/db/write.py b/src/calibre/db/write.py index dfe15e108c..de0166339a 100644 --- a/src/calibre/db/write.py +++ b/src/calibre/db/write.py @@ -192,6 +192,12 @@ def get_adapter(name, metadata): def one_one_in_books(book_id_val_map, db, field, *args): 'Set a one-one field in the books table' + # Ignore those items whose value is the same as the current value + # We can't do this for the cover because the file might change without + # the presence-of-cover flag changing + if field.name != 'cover': + book_id_val_map = {k:v for k, v in iteritems(book_id_val_map) + if v != field.table.book_col_map.get(k, None)} if book_id_val_map: sequence = ((sqlite_datetime(v), k) for k, v in iteritems(book_id_val_map)) db.executemany( @@ -207,14 +213,17 @@ def set_uuid(book_id_val_map, db, field, *args): def set_title(book_id_val_map, db, field, *args): ans = one_one_in_books(book_id_val_map, db, field, *args) - # Set the title sort field + # Set the title sort field if the title changed field.title_sort_field.writer.set_books( - {k:title_sort(v) for k, v in iteritems(book_id_val_map)}, db) + {k:title_sort(v) for k, v in iteritems(book_id_val_map) if k in ans}, db) return ans def one_one_in_other(book_id_val_map, db, field, *args): 'Set a one-one field in the non-books table, like comments' + # Ignore those items whose value is the same as the current value + book_id_val_map = {k:v for k, v in iteritems(book_id_val_map) + if v != field.table.book_col_map.get(k, None)} deleted = tuple((k,) for k, v in iteritems(book_id_val_map) if v is None) if deleted: db.executemany('DELETE FROM %s WHERE book=?'%field.metadata['table'], @@ -234,12 +243,13 @@ def custom_series_index(book_id_val_map, db, field, *args): series_field = field.series_field sequence = [] for book_id, sidx in book_id_val_map.items(): + ids = series_field.ids_for_book(book_id) if sidx is None: sidx = 1.0 - ids = series_field.ids_for_book(book_id) if ids: - sequence.append((sidx, book_id, ids[0])) - field.table.book_col_map[book_id] = sidx + if field.table.book_col_map.get(book_id, None) != sidx: + sequence.append((sidx, book_id, ids[0])) + field.table.book_col_map[book_id] = sidx else: # the series has been deleted from the book, which means no row for # it exists in the series table. The series_index value should be @@ -427,7 +437,7 @@ def many_many(book_id_val_map, db, field, allow_case_change, *args): # Ignore those items whose value is the same as the current value book_id_item_id_map = {k:v for k, v in iteritems(book_id_item_id_map) - if v != table.book_col_map.get(k, None)} + if v != table.book_col_map.get(k, tuple())} dirtied |= set(book_id_item_id_map) # Update the book->col and col->book maps @@ -484,6 +494,10 @@ def many_many(book_id_val_map, db, field, allow_case_change, *args): def identifiers(book_id_val_map, db, field, *args): # {{{ + # Ignore those items whose value is the same as the current value + book_id_val_map = {k:v for k, v in iteritems(book_id_val_map) + if v != field.table.book_col_map.get(k, None)} + table = field.table updates = set() for book_id, identifiers in iteritems(book_id_val_map): diff --git a/src/calibre/utils/formatter_functions.py b/src/calibre/utils/formatter_functions.py index c6d98d5afb..7e8f149379 100644 --- a/src/calibre/utils/formatter_functions.py +++ b/src/calibre/utils/formatter_functions.py @@ -1266,14 +1266,12 @@ class BuiltinAnnotationCount(BuiltinFormatterFunction): 'This function works only in the GUI.') def evaluate(self, formatter, kwargs, mi, locals): - if hasattr(mi, '_proxy_metadata'): - try: - from calibre.gui2.ui import get_gui - c = get_gui().current_db.new_api.annotation_count_for_book(mi.id) - return '' if c == 0 else unicode_type(c) - except: - return _('Failed to fetch annotation count') - return '' + try: + from calibre.gui2.ui import get_gui + c = get_gui().current_db.new_api.annotation_count_for_book(mi.id) + return '' if c == 0 else unicode_type(c) + except: + pass return _('This function can be used only in the GUI') @@ -1287,14 +1285,12 @@ class BuiltinIsMarked(BuiltinFormatterFunction): "marks. Returns '' if the book is not marked.") def evaluate(self, formatter, kwargs, mi, locals): - if hasattr(mi, '_proxy_metadata'): - try: - from calibre.gui2.ui import get_gui - c = get_gui().current_db.data.get_marked(mi.id) - return c if c else '' - except: - return _('Failed to get marked status') - return '' + try: + from calibre.gui2.ui import get_gui + c = get_gui().current_db.data.get_marked(mi.id) + return c if c else '' + except: + pass return _('This function can be used only in the GUI') @@ -1762,10 +1758,12 @@ class BuiltinVirtualLibraries(BuiltinFormatterFunction): 'column\'s value in your save/send templates') def evaluate(self, formatter, kwargs, mi, locals_): - if hasattr(mi, '_proxy_metadata'): + try: from calibre.gui2.ui import get_gui a = get_gui().current_db.data.get_virtual_libraries_for_books((mi.id,)) return ', '.join(a[mi.id]) + except: + pass return _('This function can be used only in the GUI') @@ -1863,7 +1861,7 @@ class BuiltinConnectedDeviceName(BuiltinFormatterFunction): "'carda' and 'cardb'. This function works only in the GUI.") def evaluate(self, formatter, kwargs, mi, locals, storage_location): - if hasattr(mi, '_proxy_metadata'): + try: # Do the import here so that we don't entangle the GUI when using # command line functions from calibre.gui2.ui import get_gui @@ -1882,6 +1880,8 @@ class BuiltinConnectedDeviceName(BuiltinFormatterFunction): except: traceback.print_exc() raise + except: + pass return _('This function can be used only in the GUI') @@ -1897,7 +1897,7 @@ class BuiltinConnectedDeviceUUID(BuiltinFormatterFunction): "the GUI.") def evaluate(self, formatter, kwargs, mi, locals, storage_location): - if hasattr(mi, '_proxy_metadata'): + try: # Do the import here so that we don't entangle the GUI when using # command line functions from calibre.gui2.ui import get_gui @@ -1916,6 +1916,8 @@ class BuiltinConnectedDeviceUUID(BuiltinFormatterFunction): except: traceback.print_exc() raise + except: + pass return _('This function can be used only in the GUI')