diff --git a/resources/default_tweaks.py b/resources/default_tweaks.py index e03b0680be..e68096ecd5 100644 --- a/resources/default_tweaks.py +++ b/resources/default_tweaks.py @@ -90,4 +90,34 @@ save_template_title_series_sorting = 'library_order' # Examples: # auto_connect_to_folder = 'C:\\Users\\someone\\Desktop\\testlib' # auto_connect_to_folder = '/home/dropbox/My Dropbox/someone/library' -auto_connect_to_folder = '' \ No newline at end of file +auto_connect_to_folder = '' + +# Specify renaming rules for sony collections. Collections on Sonys are named +# depending upon whether the field is standard or custom. A collection derived +# from a standard field is named for the value in that field. For example, if +# the standard 'series' column contains the name 'Darkover', then the series +# will be named 'Darkover'. A collection derived from a custom field will have +# the name of the field added to the value. For example, if a custom series +# column named 'My Series' contains the name 'Darkover', then the collection +# will be named 'Darkover (My Series)'. If two books have fields that generate +# the same collection name, then both books will be in that collection. This +# tweak lets you specify for a standard or custom field the value to be put +# inside the parentheses. You can use it to add a parenthetical description to a +# standard field, for example 'Foo (Tag)' instead of the 'Foo'. You can also use +# it to force multiple fields to end up in the same collection. For example, you +# could force the values in 'series', '#my_series_1', and '#my_series_2' to +# appear in collections named 'some_value (Series)', thereby merging all of the +# fields into one set of collections. The syntax of this tweak is +# {'field_lookup_name':'name_to_use', 'lookup_name':'name', ...} +# Example 1: I want three series columns to be merged into one set of +# collections. If the column lookup names are 'series', '#series_1' and +# '#series_2', and if I want nothing in the parenthesis, then the value to use +# in the tweak value would be: +# sony_collection_renaming_rules={'series':'', '#series_1':'', '#series_2':''} +# Example 2: I want the word '(Series)' to appear on collections made from +# series, and the word '(Tag)' to appear on collections made from tags. Use: +# sony_collection_renaming_rules={'series':'Series', 'tags':'Tag'} +# Example 3: I want 'series' and '#myseries' to be merged, and for the +# collection name to have '(Series)' appended. The renaming rule is: +# sony_collection_renaming_rules={'series':'Series', '#myseries':'Series'} +sony_collection_renaming_rules={} \ No newline at end of file diff --git a/src/calibre/devices/usbms/books.py b/src/calibre/devices/usbms/books.py index 0efa507e09..3e13527bd0 100644 --- a/src/calibre/devices/usbms/books.py +++ b/src/calibre/devices/usbms/books.py @@ -9,9 +9,10 @@ import os, re, time, sys from calibre.ebooks.metadata.book.base import Metadata from calibre.devices.mime import mime_type_ext from calibre.devices.interface import BookList as _BookList -from calibre.constants import filesystem_encoding, preferred_encoding +from calibre.constants import preferred_encoding from calibre import isbytestring -from calibre.utils.config import prefs +from calibre.utils.config import prefs, tweaks +from calibre.utils.date import format_date class Book(Metadata): def __init__(self, prefix, lpath, size=None, other=None): @@ -94,11 +95,38 @@ class CollectionsBookList(BookList): def supports_collections(self): return True + def compute_category_name(self, attr, category, cust_field_meta): + renames = tweaks['sony_collection_renaming_rules'] + attr_name = renames.get(attr, None) + if attr_name is None: + if attr in cust_field_meta: + attr_name = '(%s)'%cust_field_meta[attr]['name'] + else: + attr_name = '' + elif attr_name != '': + attr_name = '(%s)'%attr_name + + if attr not in cust_field_meta: + cat_name = '%s %s'%(category, attr_name) + else: + fm = cust_field_meta[attr] + if fm['datatype'] == 'bool': + if category: + cat_name = '%s %s'%(_('Yes'), attr_name) + else: + cat_name = '%s %s'%(_('No'), attr_name) + elif fm['datatype'] == 'datetime': + cat_name = '%s %s'%(format_date(category, + fm['display'].get('date_format','dd MMM yyyy')), attr_name) + else: + cat_name = '%s %s'%(category, attr_name) + return cat_name.strip() + def get_collections(self, collection_attributes): from calibre.devices.usbms.driver import debug_print debug_print('Starting get_collections:', prefs['manage_device_metadata']) + debug_print('Renaming rules:', tweaks['sony_collection_renaming_rules']) collections = {} - series_categories = set([]) # This map of sets is used to avoid linear searches when testing for # book equality collections_lpaths = {} @@ -124,42 +152,55 @@ class CollectionsBookList(BookList): # For existing books, modify the collections only if the user # specified 'on_connect' attrs = collection_attributes + meta_vals = book.get_all_non_none_attributes() + cust_field_meta = book.get_all_user_metadata(make_copy=False) for attr in attrs: attr = attr.strip() - val = getattr(book, attr, None) + val = meta_vals.get(attr, None) if not val: continue if isbytestring(val): val = val.decode(preferred_encoding, 'replace') if isinstance(val, (list, tuple)): val = list(val) - elif isinstance(val, unicode): + else: val = [val] for category in val: - # TODO: NEWMETA: format the custom fields - if attr == 'tags' and len(category) > 1 and \ - category[0] == '[' and category[-1] == ']': + is_series = False + if attr in cust_field_meta: # is a custom field + fm = cust_field_meta[attr] + if fm['datatype'] == 'text' and len(category) > 1 and \ + category[0] == '[' and category[-1] == ']': + continue + if fm['datatype'] == 'series': + is_series = True + else: # is a standard field + if attr == 'tags' and len(category) > 1 and \ + category[0] == '[' and category[-1] == ']': + continue + if attr == 'series' or \ + ('series' in collection_attributes and + meta_vals.get('series', None) == category): + is_series = True + cat_name = self.compute_category_name(attr, category, + cust_field_meta) + if cat_name not in collections: + collections[cat_name] = [] + collections_lpaths[cat_name] = set() + if lpath in collections_lpaths[cat_name]: continue - if category not in collections: - collections[category] = [] - collections_lpaths[category] = set() - if lpath not in collections_lpaths[category]: - collections_lpaths[category].add(lpath) - collections[category].append(book) - if attr == 'series' or \ - ('series' in collection_attributes and - getattr(book, 'series', None) == category): - series_categories.add(category) + collections_lpaths[cat_name].add(lpath) + if is_series: + collections[cat_name].append( + (book, meta_vals.get(attr+'_index', sys.maxint))) + else: + collections[cat_name].append( + (book, meta_vals.get('title_sort', 'zzzz'))) # Sort collections + result = {} for category, books in collections.items(): - def tgetter(x): - return getattr(x, 'title_sort', 'zzzz') - books.sort(cmp=lambda x,y:cmp(tgetter(x), tgetter(y))) - if category in series_categories: - # Ensures books are sub sorted by title - def getter(x): - return getattr(x, 'series_index', sys.maxint) - books.sort(cmp=lambda x,y:cmp(getter(x), getter(y))) - return collections + books.sort(cmp=lambda x,y:cmp(x[1], y[1])) + result[category] = [x[0] for x in books] + return result def rebuild_collections(self, booklist, oncard): ''' diff --git a/src/calibre/ebooks/metadata/book/__init__.py b/src/calibre/ebooks/metadata/book/__init__.py index 47eb616394..ca7f4f7074 100644 --- a/src/calibre/ebooks/metadata/book/__init__.py +++ b/src/calibre/ebooks/metadata/book/__init__.py @@ -109,7 +109,7 @@ COPYABLE_METADATA_FIELDS = SOCIAL_METADATA_FIELDS.union( CALIBRE_METADATA_FIELDS) - \ frozenset(['title', 'title_sort', 'authors', 'author_sort', 'author_sort_map' 'comments', - 'cover_data', 'tags', 'language']) + 'cover_data', 'tags', 'language', 'lpath']) SERIALIZABLE_FIELDS = SOCIAL_METADATA_FIELDS.union( USER_METADATA_FIELDS).union( diff --git a/src/calibre/ebooks/metadata/book/base.py b/src/calibre/ebooks/metadata/book/base.py index dcb31c3ecc..3f5507d676 100644 --- a/src/calibre/ebooks/metadata/book/base.py +++ b/src/calibre/ebooks/metadata/book/base.py @@ -117,16 +117,18 @@ class Metadata(object): res[k] = copy.deepcopy(user_metadata[k]) return res - def get_user_metadata(self, field): + def get_user_metadata(self, field, make_copy): ''' return field metadata from the object if it is there. Otherwise return - None. field is the key name, not the label. Return a copy, just in case - the user wants to change values in the dict (json does). + None. field is the key name, not the label. Return a copy if requested, + just in case the user wants to change values in the dict. ''' _data = object.__getattribute__(self, '_data') _data = _data['user_metadata'] if field in _data: - return copy.deepcopy(_data[field]) + if make_copy: + return copy.deepcopy(_data[field]) + return _data[field] return None @classmethod @@ -189,7 +191,7 @@ class Metadata(object): for x in STANDARD_METADATA_FIELDS: prints('%s:'%x, getattr(self, x, 'None')) for x in self.user_metadata_keys: - meta = self.get_user_metadata(x) + meta = self.get_user_metadata(x, make_copy=False) if meta is not None: prints(x, meta) prints('--------------') @@ -220,6 +222,9 @@ class Metadata(object): self.set_all_user_metadata(other.get_all_user_metadata(make_copy=True)) self.comments = getattr(other, 'comments', '') self.language = getattr(other, 'language', None) + lpath = getattr(other, 'lpath', None) + if lpath is not None: + self.lpath = lpath else: for attr in COPYABLE_METADATA_FIELDS: if hasattr(other, attr): @@ -240,7 +245,7 @@ class Metadata(object): if getattr(other, 'user_metadata_keys', None): for x in other.user_metadata_keys: - meta = other.get_user_metadata(x) + meta = other.get_user_metadata(x, make_copy=True) if meta is not None: self.set_user_metadata(x, meta) # get... did the deepcopy