Changes to respond to Kovid's mail, and some cleanups.

This commit is contained in:
Charles Haley 2010-08-28 13:34:49 +01:00
parent 47fedcee36
commit b4b0cb483d
5 changed files with 100 additions and 66 deletions

View File

@ -101,7 +101,9 @@ COPYABLE_METADATA_FIELDS = SOCIAL_METADATA_FIELDS.union(
BOOK_STRUCTURE_FIELDS).union(
DEVICE_METADATA_FIELDS).union(
CALIBRE_METADATA_FIELDS) - \
frozenset(['title', 'authors', 'comments', 'cover_data'])
frozenset(['title', 'title_sort', 'authors',
'author_sort', 'author_sort_map' 'comments',
'cover_data', 'tags', 'language'])
SERIALIZABLE_FIELDS = SOCIAL_METADATA_FIELDS.union(
USER_METADATA_FIELDS).union(

View File

@ -66,7 +66,7 @@ class Metadata(object):
raise AttributeError(
'Metadata object has no attribute named: '+ repr(field))
def __setattr__(self, field, val):
def __setattr__(self, field, val, extra=None):
_data = object.__getattribute__(self, '_data')
if field in STANDARD_METADATA_FIELDS:
if val is None:
@ -74,17 +74,23 @@ class Metadata(object):
_data[field] = val
elif field in _data['user_metadata'].iterkeys():
_data['user_metadata'][field]['#value#'] = val
_data['user_metadata'][field]['#extra#'] = extra
else:
# You are allowed to stick arbitrary attributes onto this object as
# long as they don't conflict with global or user metadata names
# Don't abuse this privilege
self.__dict__[field] = val
def get(self, field):
def get(self, field, default=None):
if default is not None:
try:
return self.__getattribute__(field)
except AttributeError:
return default
return self.__getattribute__(field)
def set(self, field, val):
self.__setattr__(field, val)
def set(self, field, val, extra=None):
self.__setattr__(field, val, extra)
@property
def user_metadata_keys(self):
@ -92,25 +98,25 @@ class Metadata(object):
_data = object.__getattribute__(self, '_data')
return frozenset(_data['user_metadata'].iterkeys())
@property
def all_user_metadata(self):
def get_all_user_metadata(self, make_copy):
'''
return a dict containing all the custom field metadata associated with
the book. Return a deep copy, just in case the user wants to change
values in the dict (json does).
the book.
'''
_data = object.__getattribute__(self, '_data')
_data = _data['user_metadata']
user_metadata = _data['user_metadata']
if not make_copy:
return user_metadata
res = {}
for k in _data:
res[k] = copy.deepcopy(_data[k])
for k in user_metadata:
res[k] = copy.deepcopy(user_metadata[k])
return res
def get_user_metadata(self, field):
'''
return field metadata from the object if it is there. Otherwise return
None. field is the key name, not the label. Return a shallow 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, just in case
the user wants to change values in the dict (json does).
'''
_data = object.__getattribute__(self, '_data')
_data = _data['user_metadata']
@ -118,6 +124,14 @@ class Metadata(object):
return copy.deepcopy(_data[field])
return None
@classmethod
def get_user_metadata_value(user_mi):
return user_mi['#value#']
@classmethod
def get_user_metadata_extra(user_mi):
return user_mi['#extra#']
def set_all_user_metadata(self, metadata):
'''
store custom field metadata into the object. Field is the key name
@ -139,21 +153,30 @@ class Metadata(object):
traceback.print_stack()
metadata = copy.deepcopy(metadata)
if '#value#' not in metadata:
if metadata['datatype'] == 'text' and metadata['is_multiple']:
metadata['#value#'] = []
else:
metadata['#value#'] = None
_data = object.__getattribute__(self, '_data')
_data['user_metadata'][field] = metadata
@property
def all_attributes(self):
def get_all_non_none_attributes(self):
'''
Return a dictionary containing all non-None metadata fields, including
the custom ones.
'''
result = {}
_data = object.__getattribute__(self, '_data')
for attr in STANDARD_METADATA_FIELDS:
v = _data.get(attr, None)
if v is not None:
result[attr] = v
for attr in self.user_metadata_keys:
if self.get(attr) is not None:
result[attr] = self.get(attr)
for attr in _data['user_metadata'].iterkeys():
v = _data['user_metadata'][attr]['#value#']
if v is not None:
result[attr] = v
if _data['user_metadata'][attr]['datatype'] == 'series':
result[attr+'_index'] = _data['user_metadata'][attr]['#extra#']
return result
# Old Metadata API {{{
@ -184,27 +207,34 @@ class Metadata(object):
'''
if other.title and other.title != _('Unknown'):
self.title = other.title
if hasattr(other, 'title_sort'):
self.title_sort = other.title_sort
if other.authors and other.authors[0] != _('Unknown'):
self.authors = other.authors
if hasattr(other, 'author_sort_map'):
self.author_sort_map = other.author_sort_map
if hasattr(other, 'author_sort'):
self.author_sort = other.author_sort
for attr in COPYABLE_METADATA_FIELDS:
if replace_metadata:
for attr in COPYABLE_METADATA_FIELDS:
setattr(self, attr, getattr(other, attr, 1.0 if \
attr == 'series_index' else None))
elif hasattr(other, attr):
self.tags = other.tags
self.cover_data = getattr(other, 'cover_data', '')
self.set_all_user_metadata(other.get_all_user_metadata(make_copy=True))
self.comments = getattr(other, 'comments', '')
self.language = getattr(other, 'language', None)
else:
for attr in COPYABLE_METADATA_FIELDS:
if hasattr(other, attr):
val = getattr(other, attr)
if val is not None:
setattr(self, attr, copy.deepcopy(val))
if replace_metadata:
self.tags = other.tags
elif other.tags:
self.tags += other.tags
self.tags = list(set(self.tags))
if getattr(other, 'author_sort_map', None):
self.author_sort_map.update(other.author_sort_map)
if other.tags:
self.tags += list(set(self.tags + other.tags))
if getattr(other, 'cover_data', False):
other_cover = other.cover_data[-1]
@ -217,12 +247,9 @@ class Metadata(object):
if getattr(other, 'user_metadata_keys', None):
for x in other.user_metadata_keys:
meta = other.get_user_metadata(x)
if meta is not None or replace_metadata:
if meta is not None:
self.set_user_metadata(x, meta) # get... did the deepcopy
if replace_metadata:
self.comments = getattr(other, 'comments', '')
else:
my_comments = getattr(self, 'comments', '')
other_comments = getattr(other, 'comments', '')
if not my_comments:
@ -236,7 +263,6 @@ class Metadata(object):
if other_lang and other_lang.lower() != 'und':
self.language = other_lang
def format_series_index(self):
from calibre.ebooks.metadata import fmt_sidx
try:

View File

@ -70,7 +70,7 @@ class JsonCodec(object):
def encode_metadata_attr(self, book, key):
if key == 'user_metadata':
meta = book.all_user_metadata
meta = book.get_all_user_metadata(make_copy=True)
for k in meta:
if meta[k]['datatype'] == 'datetime':
meta[k]['#value#'] = datetime_to_string(meta[k]['#value#'])

View File

@ -22,6 +22,7 @@ from calibre.library.sqlite import connect, IntegrityError, DBThread
from calibre.library.prefs import DBPrefs
from calibre.ebooks.metadata import string_to_authors, authors_to_string, \
MetaInformation
from calibre.ebooks.metadata.book.base import Metadata
from calibre.ebooks.metadata.meta import get_metadata, metadata_from_formats
from calibre.constants import preferred_encoding, iswindows, isosx, filesystem_encoding
from calibre.ptempfile import PersistentTemporaryFile
@ -537,7 +538,10 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
for key,meta in self.field_metadata.iteritems():
if meta['is_custom']:
mi.set_user_metadata(key, meta)
mi.set(key, self.get_custom(idx, label=meta['label'], index_is_id=index_is_id))
mi.set(key, val=self.get_custom(idx, label=meta['label'],
index_is_id=index_is_id),
extra=self.get_custom_extra(idx, label=meta['label'],
index_is_id=index_is_id))
if get_cover:
mi.cover = self.cover(id, index_is_id=True, as_path=True)
return mi
@ -1038,6 +1042,15 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
if getattr(mi, 'timestamp', None) is not None:
doit(self.set_timestamp, id, mi.timestamp, notify=False)
self.set_path(id, True)
user_mi = mi.get_all_user_metadata(make_copy=False)
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=Metadata.get_user_metadata_value(user_mi[key]),
extra=Metadata.get_user_metadata_extra(user_mi[key]),
label=user_mi[key]['label'])
self.notify('metadata', [id])
# Given a book, return the list of author sort strings for the book's authors

View File

@ -115,7 +115,7 @@ def get_components(template, mi, id, timefmt='%b %Y', length=250,
library_order = tweaks['save_template_title_series_sorting'] == 'library_order'
tsfmt = title_sort if library_order else lambda x: x
format_args = dict(**FORMAT_ARGS)
format_args.update(mi.all_attributes)
format_args.update(mi.get_all_non_none_attributes())
if mi.title:
format_args['title'] = tsfmt(mi.title)
if mi.authors:
@ -131,6 +131,8 @@ def get_components(template, mi, id, timefmt='%b %Y', length=250,
format_args['series_index'] = mi.format_series_index()
else:
template = re.sub(r'\{series_index[^}]*?\}', '', template)
## TODO: format custom values. Check all the datatypes.
if mi.rating is not None:
format_args['rating'] = mi.format_rating()
if hasattr(mi.timestamp, 'timetuple'):
@ -139,15 +141,6 @@ def get_components(template, mi, id, timefmt='%b %Y', length=250,
format_args['pubdate'] = strftime(timefmt, mi.pubdate.timetuple())
format_args['id'] = str(id)
# These are not necessary any more. The values are set by
# 'format_args.update' above, and there is no special formatting
# if mi.author_sort:
# format_args['author_sort'] = mi.author_sort
# if mi.isbn:
# format_args['isbn'] = mi.isbn
# if mi.publisher:
# format_args['publisher'] = mi.publisher
components = [x.strip() for x in template.split('/') if x.strip()]
components = [safe_format(x, format_args) for x in components]
components = [sanitize_func(x) for x in components if x]