mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Add code for Sony collections
This commit is contained in:
parent
11f7bd06a8
commit
16e5b2f3b0
@ -91,3 +91,33 @@ save_template_title_series_sorting = 'library_order'
|
|||||||
# auto_connect_to_folder = 'C:\\Users\\someone\\Desktop\\testlib'
|
# auto_connect_to_folder = 'C:\\Users\\someone\\Desktop\\testlib'
|
||||||
# auto_connect_to_folder = '/home/dropbox/My Dropbox/someone/library'
|
# auto_connect_to_folder = '/home/dropbox/My Dropbox/someone/library'
|
||||||
auto_connect_to_folder = ''
|
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={}
|
@ -9,9 +9,10 @@ import os, re, time, sys
|
|||||||
from calibre.ebooks.metadata.book.base import Metadata
|
from calibre.ebooks.metadata.book.base import Metadata
|
||||||
from calibre.devices.mime import mime_type_ext
|
from calibre.devices.mime import mime_type_ext
|
||||||
from calibre.devices.interface import BookList as _BookList
|
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 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):
|
class Book(Metadata):
|
||||||
def __init__(self, prefix, lpath, size=None, other=None):
|
def __init__(self, prefix, lpath, size=None, other=None):
|
||||||
@ -94,11 +95,38 @@ class CollectionsBookList(BookList):
|
|||||||
def supports_collections(self):
|
def supports_collections(self):
|
||||||
return True
|
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):
|
def get_collections(self, collection_attributes):
|
||||||
from calibre.devices.usbms.driver import debug_print
|
from calibre.devices.usbms.driver import debug_print
|
||||||
debug_print('Starting get_collections:', prefs['manage_device_metadata'])
|
debug_print('Starting get_collections:', prefs['manage_device_metadata'])
|
||||||
|
debug_print('Renaming rules:', tweaks['sony_collection_renaming_rules'])
|
||||||
collections = {}
|
collections = {}
|
||||||
series_categories = set([])
|
|
||||||
# This map of sets is used to avoid linear searches when testing for
|
# This map of sets is used to avoid linear searches when testing for
|
||||||
# book equality
|
# book equality
|
||||||
collections_lpaths = {}
|
collections_lpaths = {}
|
||||||
@ -124,42 +152,55 @@ class CollectionsBookList(BookList):
|
|||||||
# For existing books, modify the collections only if the user
|
# For existing books, modify the collections only if the user
|
||||||
# specified 'on_connect'
|
# specified 'on_connect'
|
||||||
attrs = collection_attributes
|
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:
|
for attr in attrs:
|
||||||
attr = attr.strip()
|
attr = attr.strip()
|
||||||
val = getattr(book, attr, None)
|
val = meta_vals.get(attr, None)
|
||||||
if not val: continue
|
if not val: continue
|
||||||
if isbytestring(val):
|
if isbytestring(val):
|
||||||
val = val.decode(preferred_encoding, 'replace')
|
val = val.decode(preferred_encoding, 'replace')
|
||||||
if isinstance(val, (list, tuple)):
|
if isinstance(val, (list, tuple)):
|
||||||
val = list(val)
|
val = list(val)
|
||||||
elif isinstance(val, unicode):
|
else:
|
||||||
val = [val]
|
val = [val]
|
||||||
for category in val:
|
for category in val:
|
||||||
# TODO: NEWMETA: format the custom fields
|
is_series = False
|
||||||
if attr == 'tags' and len(category) > 1 and \
|
if attr in cust_field_meta: # is a custom field
|
||||||
category[0] == '[' and category[-1] == ']':
|
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
|
continue
|
||||||
if category not in collections:
|
collections_lpaths[cat_name].add(lpath)
|
||||||
collections[category] = []
|
if is_series:
|
||||||
collections_lpaths[category] = set()
|
collections[cat_name].append(
|
||||||
if lpath not in collections_lpaths[category]:
|
(book, meta_vals.get(attr+'_index', sys.maxint)))
|
||||||
collections_lpaths[category].add(lpath)
|
else:
|
||||||
collections[category].append(book)
|
collections[cat_name].append(
|
||||||
if attr == 'series' or \
|
(book, meta_vals.get('title_sort', 'zzzz')))
|
||||||
('series' in collection_attributes and
|
|
||||||
getattr(book, 'series', None) == category):
|
|
||||||
series_categories.add(category)
|
|
||||||
# Sort collections
|
# Sort collections
|
||||||
|
result = {}
|
||||||
for category, books in collections.items():
|
for category, books in collections.items():
|
||||||
def tgetter(x):
|
books.sort(cmp=lambda x,y:cmp(x[1], y[1]))
|
||||||
return getattr(x, 'title_sort', 'zzzz')
|
result[category] = [x[0] for x in books]
|
||||||
books.sort(cmp=lambda x,y:cmp(tgetter(x), tgetter(y)))
|
return result
|
||||||
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
|
|
||||||
|
|
||||||
def rebuild_collections(self, booklist, oncard):
|
def rebuild_collections(self, booklist, oncard):
|
||||||
'''
|
'''
|
||||||
|
@ -109,7 +109,7 @@ COPYABLE_METADATA_FIELDS = SOCIAL_METADATA_FIELDS.union(
|
|||||||
CALIBRE_METADATA_FIELDS) - \
|
CALIBRE_METADATA_FIELDS) - \
|
||||||
frozenset(['title', 'title_sort', 'authors',
|
frozenset(['title', 'title_sort', 'authors',
|
||||||
'author_sort', 'author_sort_map' 'comments',
|
'author_sort', 'author_sort_map' 'comments',
|
||||||
'cover_data', 'tags', 'language'])
|
'cover_data', 'tags', 'language', 'lpath'])
|
||||||
|
|
||||||
SERIALIZABLE_FIELDS = SOCIAL_METADATA_FIELDS.union(
|
SERIALIZABLE_FIELDS = SOCIAL_METADATA_FIELDS.union(
|
||||||
USER_METADATA_FIELDS).union(
|
USER_METADATA_FIELDS).union(
|
||||||
|
@ -117,16 +117,18 @@ class Metadata(object):
|
|||||||
res[k] = copy.deepcopy(user_metadata[k])
|
res[k] = copy.deepcopy(user_metadata[k])
|
||||||
return res
|
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
|
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
|
None. field is the key name, not the label. Return a copy if requested,
|
||||||
the user wants to change values in the dict (json does).
|
just in case the user wants to change values in the dict.
|
||||||
'''
|
'''
|
||||||
_data = object.__getattribute__(self, '_data')
|
_data = object.__getattribute__(self, '_data')
|
||||||
_data = _data['user_metadata']
|
_data = _data['user_metadata']
|
||||||
if field in _data:
|
if field in _data:
|
||||||
return copy.deepcopy(_data[field])
|
if make_copy:
|
||||||
|
return copy.deepcopy(_data[field])
|
||||||
|
return _data[field]
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -189,7 +191,7 @@ class Metadata(object):
|
|||||||
for x in STANDARD_METADATA_FIELDS:
|
for x in STANDARD_METADATA_FIELDS:
|
||||||
prints('%s:'%x, getattr(self, x, 'None'))
|
prints('%s:'%x, getattr(self, x, 'None'))
|
||||||
for x in self.user_metadata_keys:
|
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:
|
if meta is not None:
|
||||||
prints(x, meta)
|
prints(x, meta)
|
||||||
prints('--------------')
|
prints('--------------')
|
||||||
@ -220,6 +222,9 @@ class Metadata(object):
|
|||||||
self.set_all_user_metadata(other.get_all_user_metadata(make_copy=True))
|
self.set_all_user_metadata(other.get_all_user_metadata(make_copy=True))
|
||||||
self.comments = getattr(other, 'comments', '')
|
self.comments = getattr(other, 'comments', '')
|
||||||
self.language = getattr(other, 'language', None)
|
self.language = getattr(other, 'language', None)
|
||||||
|
lpath = getattr(other, 'lpath', None)
|
||||||
|
if lpath is not None:
|
||||||
|
self.lpath = lpath
|
||||||
else:
|
else:
|
||||||
for attr in COPYABLE_METADATA_FIELDS:
|
for attr in COPYABLE_METADATA_FIELDS:
|
||||||
if hasattr(other, attr):
|
if hasattr(other, attr):
|
||||||
@ -240,7 +245,7 @@ class Metadata(object):
|
|||||||
|
|
||||||
if getattr(other, 'user_metadata_keys', None):
|
if getattr(other, 'user_metadata_keys', None):
|
||||||
for x in other.user_metadata_keys:
|
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:
|
if meta is not None:
|
||||||
self.set_user_metadata(x, meta) # get... did the deepcopy
|
self.set_user_metadata(x, meta) # get... did the deepcopy
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user