Field interface, including refactoring (renaming) some existing methods.

This commit is contained in:
Charles Haley 2010-09-20 09:51:04 +01:00
parent 792fccbcad
commit 89f64db891
9 changed files with 123 additions and 62 deletions

View File

@ -138,20 +138,66 @@ class Metadata(object):
def set(self, field, val, extra=None):
self.__setattr__(field, val, extra)
@property
def all_keys(self):
# field-oriented interface. Intended to be the same as in LibraryDatabase
def standard_field_keys(self):
'''
All attribute keys known by this instance, even if their value is None
return a list of all possible keys, even if this book doesn't have them
'''
return STANDARD_METADATA_FIELDS
def custom_field_keys(self):
'''
return a list of the custom fields in this book
'''
return object.__getattribute__(self, '_data')['user_metadata'].iterkeys()
def all_field_keys(self):
'''
All field keys known by this instance, even if their value is None
'''
_data = object.__getattribute__(self, '_data')
return frozenset(ALL_METADATA_FIELDS.union(_data['user_metadata'].iterkeys()))
@property
def metadata_for_field(self, key):
'''
return metadata describing a standard or custom field.
'''
if key in self.user_metadata_keys():
return self.get_standard_metadata(self, key, make_copy=False)
return self.get_user_metadata(key, make_copy=False)
def user_metadata_keys(self):
'The set of user metadata names this object knows about'
'''
Return the standard keys actually in this book.
'''
_data = object.__getattribute__(self, '_data')
return frozenset(_data['user_metadata'].iterkeys())
def all_non_none_fields(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 _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
# End of field-oriented interface
# Extended interfaces. These permit one to get copies of metadata dictionaries, and to
# get and set custom field metadata
def get_standard_metadata(self, field, make_copy):
'''
return field metadata from the field if it is there. Otherwise return
@ -237,30 +283,11 @@ class Metadata(object):
_data = object.__getattribute__(self, '_data')
_data['user_metadata'][field] = metadata
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 _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 {{{
def print_all_attributes(self):
for x in STANDARD_METADATA_FIELDS:
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, make_copy=False)
if meta is not None:
prints(x, meta)
@ -326,7 +353,7 @@ class Metadata(object):
self.cover_data = other.cover_data
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, make_copy=True)
if meta is not None:
self_tags = self.get(x, [])
@ -389,7 +416,7 @@ class Metadata(object):
'''
returns the tuple (field_name, formatted_value)
'''
if key in self.user_metadata_keys:
if key in self.user_metadata_keys():
res = self.get(key, None)
cmeta = self.get_user_metadata(key, make_copy=False)
if cmeta['datatype'] != 'composite' and (res is None or res == ''):
@ -432,6 +459,9 @@ class Metadata(object):
return (None, None, None, None)
def expand_template(self, template):
return format_composite(template, self)
def __unicode__(self):
from calibre.ebooks.metadata import authors_to_string
ans = []
@ -466,7 +496,7 @@ class Metadata(object):
fmt('Published', isoformat(self.pubdate))
if self.rights is not None:
fmt('Rights', unicode(self.rights))
for key in self.user_metadata_keys:
for key in self.user_metadata_keys():
val = self.get(key, None)
if val is not None:
(name, val) = self.format_field(key)
@ -491,7 +521,7 @@ class Metadata(object):
ans += [(_('Published'), unicode(self.pubdate.isoformat(' ')))]
if self.rights is not None:
ans += [(_('Rights'), unicode(self.rights))]
for key in self.user_metadata_keys:
for key in self.user_metadata_keys():
val = self.get(key, None)
if val is not None:
(name, val) = self.format_field(key)

View File

@ -270,11 +270,11 @@ class MetadataBulkDialog(QDialog, Ui_MetadataBulkDialog):
def s_r_func(self, match):
rfunc = self.s_r_functions[unicode(self.replace_func.currentText())]
rtext = unicode(self.replace_with.text())
mi_data = self.mi.get_all_non_none_attributes()
mi_data = self.mi.all_non_none_fields()
def fm_func(m):
try:
if m.group(3) not in self.mi.all_keys: return m.group(0)
if m.group(3) not in self.mi.all_field_keys(): return m.group(0)
else: return '%s{%s}'%(m.group(1), m.group(3))
except:
import traceback

View File

@ -319,7 +319,7 @@ class BooksModel(QAbstractTableModel): # {{{
_('Book <font face="serif">%s</font> of %s.')%\
(sidx, prepare_string_for_xml(series))
mi = self.db.get_metadata(idx)
for key in mi.user_metadata_keys:
for key in mi.user_metadata_keys():
name, val = mi.format_field(key)
if val is not None:
data[name] = val

View File

@ -533,6 +533,9 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, # {{{
# Save the current field_metadata for applications like calibre2opds
# Goes here, because if cf is valid, db is valid.
db.prefs['field_metadata'] = db.field_metadata.all_metadata()
if db.gm_count > 0:
print 'get_metadata cache: {0:d} calls, {1:4.2f}% misses'.format(
db.gm_count, (db.gm_missed*100.0)/db.gm_count)
for action in self.iactions.values():
if not action.shutting_down():
return

View File

@ -621,7 +621,7 @@ class ResultCache(SearchQueryParser):
def multisort(self, fields=[], subsort=False):
fields = [(self.sanitize_sort_field_name(x), bool(y)) for x, y in fields]
keys = self.field_metadata.sortable_keys()
keys = self.field_metadata.sortable_field_keys()
fields = [x for x in fields if x[0] in keys]
if subsort and 'sort' not in [x[0] for x in fields]:
fields += [('sort', True)]

View File

@ -325,6 +325,10 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
self.has_id = self.data.has_id
self.count = self.data.count
# Count times get_metadata is called, and how many times in the cache
self.gm_count = 0
self.gm_missed = 0
for prop in ('author_sort', 'authors', 'comment', 'comments', 'isbn',
'publisher', 'rating', 'series', 'series_index', 'tags',
'title', 'timestamp', 'uuid', 'pubdate', 'ondevice'):
@ -520,15 +524,47 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
f.close()
return ans
### The field-style interface. These use field keys.
def get_field(self, idx, key, default=None, index_is_id=False):
mi = self.get_metadata(idx, index_is_id=index_is_id, get_cover=True)
try:
return mi[key]
except:
return default
def standard_field_keys(self):
return self.field_metadata.standard_field_keys()
def custom_field_keys(self):
return self.field_metadata.custom_field_keys()
def all_field_keys(self):
return self.field_metadata.all_field_keys()
def sortable_field_keys(self):
return self.field_metadata.sortable_field_keys()
def searchable_fields(self):
return self.field_metadata.searchable_field_keys()
def search_term_to_field_key(self, term):
return self.field_metadata.search_term_to_key(term)
def metadata_for_field(self, key):
return self.field_metadata[key]
def get_metadata(self, idx, index_is_id=False, get_cover=False):
'''
Convenience method to return metadata as a :class:`Metadata` object.
'''
self.gm_count += 1
mi = self.data.get(idx, self.FIELD_MAP['all_metadata'],
row_is_id = index_is_id)
if mi is not None:
return mi
self.gm_missed += 1
mi = Metadata(None)
self.data.set(idx, self.FIELD_MAP['all_metadata'], mi,
row_is_id = index_is_id)

View File

@ -348,11 +348,24 @@ class FieldMetadata(dict):
def keys(self):
return self._tb_cats.keys()
def sortable_keys(self):
def sortable_field_keys(self):
return [k for k in self._tb_cats.keys()
if self._tb_cats[k]['kind']=='field' and
self._tb_cats[k]['datatype'] is not None]
def standard_field_keys(self):
return [k for k in self._tb_cats.keys()
if self._tb_cats[k]['kind']=='field' and
not self._tb_cats[k]['is_custom']]
def custom_field_keys(self):
return [k for k in self._tb_cats.keys()
if self._tb_cats[k]['kind']=='field' and
self._tb_cats[k]['is_custom']]
def all_field_keys(self):
return [k for k in self._tb_cats.keys() if self._tb_cats[k]['kind']=='field']
def iterkeys(self):
for key in self._tb_cats:
yield key
@ -474,36 +487,10 @@ class FieldMetadata(dict):
key = self.custom_field_prefix+label
self._tb_cats[key]['rec_index'] = index # let the exception fly ...
# DEFAULT_LOCATIONS = frozenset([
# 'all',
# 'author', # compatibility
# 'authors',
# 'comment', # compatibility
# 'comments',
# 'cover',
# 'date',
# 'format', # compatibility
# 'formats',
# 'isbn',
# 'ondevice',
# 'pubdate',
# 'publisher',
# 'search',
# 'series',
# 'rating',
# 'tag', # compatibility
# 'tags',
# 'title',
# ])
def get_search_terms(self):
s_keys = sorted(self._search_term_map.keys())
for v in self.search_items:
s_keys.append(v)
# if set(s_keys) != self.DEFAULT_LOCATIONS:
# print 'search labels and default_locations do not match:'
# print set(s_keys) ^ self.DEFAULT_LOCATIONS
return s_keys
def _add_search_terms_to_map(self, key, terms):
@ -518,3 +505,8 @@ class FieldMetadata(dict):
if term in self._search_term_map:
return self._search_term_map[term]
return term
def searchable_field_keys(self):
return [k for k in self._tb_cats.keys()
if self._tb_cats[k]['kind']=='field' and
len(self._tb_cats[k]['search_terms']) > 0]

View File

@ -125,7 +125,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 = FORMAT_ARGS.copy()
format_args.update(mi.get_all_non_none_attributes())
format_args.update(mi.all_non_none_fields())
if mi.title:
format_args['title'] = tsfmt(mi.title)
if mi.authors:

View File

@ -56,7 +56,7 @@ class ContentServer(object):
def sort(self, items, field, order):
field = self.db.data.sanitize_sort_field_name(field)
if field not in self.db.field_metadata.sortable_keys():
if field not in self.db.field_metadata.sortable_field_keys():
raise cherrypy.HTTPError(400, '%s is not a valid sort field'%field)
keyg = CSSortKeyGenerator([(field, order)], self.db.field_metadata)
items.sort(key=keyg, reverse=not order)