From 4ae7a3b8c9c1b69695148675dfd47ceedc927c8a Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Mon, 4 Oct 2010 11:51:27 +0100 Subject: [PATCH] Make Metadata smart update handle classifiers correctly --- src/calibre/ebooks/metadata/book/__init__.py | 6 ++--- src/calibre/ebooks/metadata/book/base.py | 24 ++++++++++++++++++++ 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/src/calibre/ebooks/metadata/book/__init__.py b/src/calibre/ebooks/metadata/book/__init__.py index 761a573b69..2da0d1b8fb 100644 --- a/src/calibre/ebooks/metadata/book/__init__.py +++ b/src/calibre/ebooks/metadata/book/__init__.py @@ -104,7 +104,8 @@ STANDARD_METADATA_FIELDS = SOCIAL_METADATA_FIELDS.union( SC_FIELDS_NOT_COPIED = frozenset(['title', 'title_sort', 'authors', 'author_sort', 'author_sort_map', - 'cover_data', 'tags', 'language']) + 'cover_data', 'tags', 'language', + 'classifiers']) # Metadata fields that smart update should copy only if the source is not None SC_FIELDS_COPY_NOT_NULL = frozenset(['lpath', 'size', 'comments', 'thumbnail']) @@ -114,8 +115,7 @@ SC_COPYABLE_FIELDS = SOCIAL_METADATA_FIELDS.union( PUBLICATION_METADATA_FIELDS).union( BOOK_STRUCTURE_FIELDS).union( DEVICE_METADATA_FIELDS).union( - CALIBRE_METADATA_FIELDS).union( - TOP_LEVEL_CLASSIFIERS) - \ + CALIBRE_METADATA_FIELDS) - \ SC_FIELDS_NOT_COPIED.union( SC_FIELDS_COPY_NOT_NULL) diff --git a/src/calibre/ebooks/metadata/book/base.py b/src/calibre/ebooks/metadata/book/base.py index 8e4385100a..29a285918f 100644 --- a/src/calibre/ebooks/metadata/book/base.py +++ b/src/calibre/ebooks/metadata/book/base.py @@ -164,6 +164,18 @@ class Metadata(object): def set(self, field, val, extra=None): self.__setattr__(field, val, extra) + def get_classifiers(self): + ''' + Return a copy of the classifiers dictionary. + The dict is small, and the penalty for using a reference where a copy is + needed is large. Also, we don't want any manipulations of the returned + dict to show up in the book. + ''' + return copy.deepcopy(object.__getattribute__(self, '_data')['classifiers']) + + def set_classifiers(self, classifiers): + object.__getattribute__(self, '_data')['classifiers'] = classifiers + # field-oriented interface. Intended to be the same as in LibraryDatabase def standard_field_keys(self): @@ -369,6 +381,7 @@ class Metadata(object): self.set_all_user_metadata(other.get_all_user_metadata(make_copy=True)) for x in SC_FIELDS_COPY_NOT_NULL: copy_not_none(self, other, x) + self.set_classifiers(other.get_classifiers()) # language is handled below else: for attr in SC_COPYABLE_FIELDS: @@ -423,6 +436,17 @@ class Metadata(object): if len(other_comments.strip()) > len(my_comments.strip()): self.comments = other_comments + # Copy all the non-none classifiers + if callable(getattr(other, 'get_classifiers', None)): + d = self.get_classifiers() + s = other.get_classifiers() + d.update([v for v in s.iteritems() if v[1] is not None]) + self.set_classifiers(d) + else: + # other structure not Metadata. Copy the top-level classifiers + for attr in TOP_LEVEL_CLASSIFIERS: + copy_not_none(self, other, attr) + other_lang = getattr(other, 'language', None) if other_lang and other_lang.lower() != 'und': self.language = other_lang