diff --git a/src/calibre/ebooks/metadata/book/base.py b/src/calibre/ebooks/metadata/book/base.py index feb6ff4bb9..366d12e5be 100644 --- a/src/calibre/ebooks/metadata/book/base.py +++ b/src/calibre/ebooks/metadata/book/base.py @@ -169,10 +169,13 @@ class Metadata(object): pass return default - def get_extra(self, field): + def get_extra(self, field, default=None): _data = object.__getattribute__(self, '_data') if field in _data['user_metadata'].iterkeys(): - return _data['user_metadata'][field]['#extra#'] + try: + return _data['user_metadata'][field]['#extra#'] + except: + return default raise AttributeError( 'Metadata object has no attribute named: '+ repr(field)) diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py index d03975baea..e8467aaa50 100644 --- a/src/calibre/library/database2.py +++ b/src/calibre/library/database2.py @@ -1690,8 +1690,8 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): self.notify('metadata', [id]) return books_to_refresh - def set_metadata(self, id, mi, ignore_errors=False, - set_title=True, set_authors=True, commit=True): + def set_metadata(self, id, mi, ignore_errors=False, set_title=True, + set_authors=True, commit=True, force_changes=False): ''' Set metadata for the book `id` from the `Metadata` object `mi` ''' @@ -1707,6 +1707,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): traceback.print_exc() else: raise + # force_changes has no role to play in setting title or author path_changed = False if set_title and mi.title: self._set_title(id, mi.title) @@ -1721,16 +1722,18 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): path_changed = True if path_changed: self.set_path(id, index_is_id=True) - if mi.author_sort: + + if force_changes or mi.author_sort: doit(self.set_author_sort, id, mi.author_sort, notify=False, commit=False) - if mi.publisher: + if force_changes or mi.publisher: doit(self.set_publisher, id, mi.publisher, notify=False, commit=False) - if mi.rating: + if force_changes or mi.rating: doit(self.set_rating, id, mi.rating, notify=False, commit=False) - if mi.series: + if force_changes or mi.series: doit(self.set_series, id, mi.series, notify=False, commit=False) + if mi.cover_data[1] is not None: doit(self.set_cover, id, mi.cover_data[1], commit=False) elif mi.cover is not None: @@ -1739,13 +1742,18 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): raw = f.read() if raw: doit(self.set_cover, id, raw, commit=False) - if mi.tags: + elif force_changes: + doit(self.remove_cover, id, notify=False, commit=False) + + if force_changes or mi.tags: doit(self.set_tags, id, mi.tags, notify=False, commit=False) - if mi.comments: + if force_changes or mi.comments: doit(self.set_comment, id, mi.comments, notify=False, commit=False) - if mi.series_index: + if force_changes or mi.series_index: doit(self.set_series_index, id, mi.series_index, notify=False, commit=False) + + # force_changes would have no effect on the next two. if mi.pubdate: doit(self.set_pubdate, id, mi.pubdate, notify=False, commit=False) if getattr(mi, 'timestamp', None) is not None: @@ -1756,7 +1764,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): if mi_idents: identifiers = self.get_identifiers(id, index_is_id=True) for key, val in mi_idents.iteritems(): - if val and val.strip(): # Don't delete an existing identifier + if force_changes or (val and val.strip()): identifiers[icu_lower(key)] = val self.set_identifiers(id, identifiers, notify=False, commit=False) @@ -1765,10 +1773,10 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): for key in user_mi.iterkeys(): if key in self.field_metadata and \ user_mi[key]['datatype'] == self.field_metadata[key]['datatype']: - doit(self.set_custom, id, - val=mi.get(key), - extra=mi.get_extra(key), - label=user_mi[key]['label'], commit=False) + val = mi.get(key, None) + if force_changes or val: + doit(self.set_custom, id, val=val, extra=mi.get_extra(key), + label=user_mi[key]['label'], commit=False) if commit: self.conn.commit() self.notify('metadata', [id]) diff --git a/src/calibre/manual/sub_groups.rst b/src/calibre/manual/sub_groups.rst index 83b8f0cbe9..edfc81d3d4 100644 --- a/src/calibre/manual/sub_groups.rst +++ b/src/calibre/manual/sub_groups.rst @@ -105,3 +105,13 @@ After creating the saved search, you can use it as a restriction. .. image:: images/sg_restrict2.jpg :align: center + Useful Template Functions + ------------------------- + + You might want to use the genre information in a template, such as with save to disk or send to device. The question might then be "How do I get the outermost genre name or names?" An |app| template function, subitems, is provided to make doing this easier. + + For example, assume you want to add the outermost genre level to the save-to-disk template to make genre folders, as in "History/The Gathering Storm - Churchill, Winston". To do this, you must extract the first level of the hierarchy and add it to the front along with a slash to indicate that it should make a folder. The template below accomplishes this:: + + {#genre:subitems(0,1)||/}{title} - {authors} + +See :ref:`The |app| template language ` for more information templates and the subitem function. \ No newline at end of file diff --git a/src/calibre/manual/template_lang.rst b/src/calibre/manual/template_lang.rst index b686d78981..c6e29e3915 100644 --- a/src/calibre/manual/template_lang.rst +++ b/src/calibre/manual/template_lang.rst @@ -129,7 +129,7 @@ The functions available are: * ``switch(pattern, value, pattern, value, ..., else_value)`` -- for each ``pattern, value`` pair, checks if the field matches the regular expression ``pattern`` and if so, returns that ``value``. If no ``pattern`` matches, then ``else_value`` is returned. You can have as many ``pattern, value`` pairs as you want. * ``lookup(pattern, field, pattern, field, ..., else_field)`` -- like switch, except the arguments are field (metadata) names, not text. The value of the appropriate field will be fetched and used. Note that because composite columns are fields, you can use this function in one composite field to use the value of some other composite field. This is extremely useful when constructing variable save paths (more later). * ``select(key)`` -- interpret the field as a comma-separated list of items, with the items being of the form "id:value". Find the pair with the id equal to key, and return the corresponding value. This function is particularly useful for extracting a value such as an isbn from the set of identifiers for a book. - * ``subitems(val, start_index, end_index)`` -- This function is used to break apart lists of tag-like hierarchical items such as genres. It interprets the value as a comma-separated list of tag-like items, where each item is a period-separated list. Returns a new list made by first finding all the period-separated tag-like items, then for each such item extracting the `start_index` th to the `end_index` th components, then combining the results back together. The first component in a period-separated list has an index of zero. If an index is negative, then it counts from the end of the list. As a special case, an end_index of zero is assumed to be the length of the list. Examples:: + * ``subitems(val, start_index, end_index)`` -- This function is used to break apart lists of tag-like hierarchical items such as genres. It interprets the value as a comma-separated list of tag-like items, where each item is a period-separated list. Returns a new list made by first finding all the period-separated tag-like items, then for each such item extracting the components from `start_index` to `end_index`, then combining the results back together. The first component in a period-separated list has an index of zero. If an index is negative, then it counts from the end of the list. As a special case, an end_index of zero is assumed to be the length of the list. Examples:: Assuming a #genre column containing "A.B.C": {#genre:subitems(0,1)} returns "A" @@ -139,7 +139,7 @@ The functions available are: {#genre:subitems(0,1)} returns "A, D" {#genre:subitems(0,2)} returns "A.B, D.E" - * ``sublist(val, start_index, end_index, separator)`` -- interpret the value as a list of items separated by `separator`, returning a new list made from the `start_index` th to the `end_index` th item. The first item is number zero. If an index is negative, then it counts from the end of the list. As a special case, an end_index of zero is assumed to be the length of the list. Examples assuming that the tags column (which is comma-separated) contains "A, B ,C":: + * ``sublist(val, start_index, end_index, separator)`` -- interpret the value as a list of items separated by `separator`, returning a new list made from the items from `start_index`to `end_index`. The first item is number zero. If an index is negative, then it counts from the end of the list. As a special case, an end_index of zero is assumed to be the length of the list. Examples assuming that the tags column (which is comma-separated) contains "A, B ,C":: {tags:sublist(0,1,\,)} returns "A" {tags:sublist(-1,0,\,)} returns "C"