mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Field interface, including refactoring (renaming) some existing methods.
This commit is contained in:
parent
792fccbcad
commit
89f64db891
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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)]
|
||||
|
@ -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)
|
||||
|
@ -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]
|
||||
|
@ -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:
|
||||
|
@ -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)
|
||||
|
Loading…
x
Reference in New Issue
Block a user