From a124d9c79922407cbdf031a21b2656dee9b79d2f Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Wed, 26 May 2010 16:35:41 +0100 Subject: [PATCH] Added all standard fields to the metadata dictionary. The datatypes might be wrong in some cases. --- src/calibre/gui2/tag_view.py | 2 +- src/calibre/library/caches.py | 8 +- src/calibre/library/custom_columns.py | 13 +- src/calibre/library/database2.py | 17 +- src/calibre/library/field_metadata.py | 259 ++++++++++++++++++++++++++ src/calibre/library/tag_categories.py | 202 -------------------- 6 files changed, 285 insertions(+), 216 deletions(-) create mode 100644 src/calibre/library/field_metadata.py delete mode 100644 src/calibre/library/tag_categories.py diff --git a/src/calibre/gui2/tag_view.py b/src/calibre/gui2/tag_view.py index 5c4c7d82ac..3882e4e174 100644 --- a/src/calibre/gui2/tag_view.py +++ b/src/calibre/gui2/tag_view.py @@ -14,7 +14,7 @@ from PyQt4.Qt import Qt, QTreeView, QApplication, pyqtSignal, \ QAbstractItemModel, QVariant, QModelIndex from calibre.gui2 import config, NONE from calibre.utils.config import prefs -from calibre.library.tag_categories import TagsIcons +from calibre.library.field_metadata import TagsIcons class TagsView(QTreeView): # {{{ diff --git a/src/calibre/library/caches.py b/src/calibre/library/caches.py index fa73b34b41..36698533c5 100644 --- a/src/calibre/library/caches.py +++ b/src/calibre/library/caches.py @@ -17,7 +17,7 @@ from calibre.utils.config import tweaks from calibre.utils.date import parse_date, now, UNDEFINED_DATE from calibre.utils.search_query_parser import SearchQueryParser from calibre.utils.pyparsing import ParseException -from calibre.library.tag_categories import TagsMetadata +# from calibre.library.field_metadata import FieldMetadata class CoverCache(QThread): @@ -387,9 +387,9 @@ class ResultCache(SearchQueryParser): # get the db columns for the standard searchables for x in self.tag_browser_categories: - if (len(self.tag_browser_categories[x]['search_labels']) and \ - self.tag_browser_categories[x]['kind'] in ['standard', 'not_cat']): - MAP[x] = self.FIELD_MAP[self.tag_browser_categories.get_field_label(x)] + if len(self.tag_browser_categories[x]['search_labels']) and \ + not self.tag_browser_categories.is_custom_field(x): + MAP[x] = self.tag_browser_categories[x]['rec_index'] if self.tag_browser_categories[x]['datatype'] != 'text': EXCLUDE_FIELDS.append(MAP[x]) diff --git a/src/calibre/library/custom_columns.py b/src/calibre/library/custom_columns.py index cc7ee7ba7f..535b8cfb72 100644 --- a/src/calibre/library/custom_columns.py +++ b/src/calibre/library/custom_columns.py @@ -144,11 +144,14 @@ class CustomColumns(object): for k in sorted(self.custom_column_label_map.keys()): v = self.custom_column_label_map[k] if v['normalized']: - tn = 'custom_column_{0}'.format(v['num']) - self.tag_browser_categories.add_custom_field( - field_name = v['label'], table = tn, column='value', - datatype=v['datatype'], is_multiple=v['is_multiple'], - number=v['num'], name=v['name']) + searchable = True + else: + searchable = False + tn = 'custom_column_{0}'.format(v['num']) + self.tag_browser_categories.add_custom_field(label=v['label'], + table=tn, column='value', datatype=v['datatype'], + is_multiple=v['is_multiple'], colnum=v['num'], + name=v['name'], searchable=searchable) def get_custom(self, idx, label=None, num=None, index_is_id=False): if label is not None: diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py index 7da16c4ec6..b8f55d76db 100644 --- a/src/calibre/library/database2.py +++ b/src/calibre/library/database2.py @@ -20,7 +20,7 @@ from PyQt4.QtGui import QImage from calibre.ebooks.metadata import title_sort from calibre.library.database import LibraryDatabase -from calibre.library.tag_categories import TagsMetadata, TagsIcons +from calibre.library.field_metadata import FieldMetadata, TagsIcons from calibre.library.schema_upgrades import SchemaUpgrade from calibre.library.caches import ResultCache from calibre.library.custom_columns import CustomColumns @@ -116,7 +116,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): self.books_list_filter = self.conn.create_dynamic_filter('books_list_filter') def __init__(self, library_path, row_factory=False): - self.tag_browser_categories = TagsMetadata() #.get_tag_browser_categories() + self.tag_browser_categories = FieldMetadata() #.get_tag_browser_categories() if not os.path.exists(library_path): os.makedirs(library_path) self.listeners = set([]) @@ -204,12 +204,21 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): 'sort':11, 'author_sort':12, 'formats':13, 'isbn':14, 'path':15, 'lccn':16, 'pubdate':17, 'flags':18, 'uuid':19} + for k,v in self.FIELD_MAP.iteritems(): + self.tag_browser_categories.set_field_record_index(k, v, prefer_custom=False) + base = max(self.FIELD_MAP.values()) for col in custom_cols: self.FIELD_MAP[col] = base = base+1 + self.tag_browser_categories.set_field_record_index( + self.custom_column_num_map[col]['label'], + base, + prefer_custom=True) self.FIELD_MAP['cover'] = base+1 + self.tag_browser_categories.set_field_record_index('cover', base+1, prefer_custom=False) self.FIELD_MAP['ondevice'] = base+2 + self.tag_browser_categories.set_field_record_index('ondevice', base+2, prefer_custom=False) script = ''' DROP VIEW IF EXISTS meta2; @@ -672,10 +681,10 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): icon, tooltip = None, '' label = tb_cats.get_field_label(category) if icon_map: - if cat['kind'] == 'standard': + if not tb_cats.is_custom_field(category): if category in icon_map: icon = icon_map[label] - elif cat['kind'] == 'custom': + else: icon = icon_map[':custom'] icon_map[category] = icon tooltip = self.custom_column_label_map[label]['name'] diff --git a/src/calibre/library/field_metadata.py b/src/calibre/library/field_metadata.py new file mode 100644 index 0000000000..383d55b2ff --- /dev/null +++ b/src/calibre/library/field_metadata.py @@ -0,0 +1,259 @@ +''' +Created on 25 May 2010 + +@author: charles +''' + +from UserDict import DictMixin +from calibre.utils.ordered_dict import OrderedDict + +class TagsIcons(dict): + ''' + If the client wants icons to be in the tag structure, this class must be + instantiated and filled in with real icons. If this class is instantiated + and passed to get_categories, All items must be given a value not None + ''' + + category_icons = ['authors', 'series', 'formats', 'publisher', 'rating', + 'news', 'tags', ':custom', ':user', 'search',] + def __init__(self, icon_dict): + for a in self.category_icons: + if a not in icon_dict: + raise ValueError('Missing category icon [%s]'%a) + self[a] = icon_dict[a] + +class FieldMetadata(dict, DictMixin): + + # kind == standard: is tag category. May be a search label. Is db col + # or is specially handled (e.g., news) + # kind == not_cat: Is not a tag category. May be a search label. Is db col + # kind == user: user-defined tag category + # kind == search: saved-searches category + # For 'standard', the order below is the order that the categories will + # appear in the tags pane. + # + # label is the column label. key is either the label or in the case of + # custom fields, the label prefixed with 'x'. Because of the prefixing, + # there cannot be a name clash between standard and custom fields, so key + # can be used as the metadata dictionary key. + + category_items_ = [ + ('authors', {'table':'authors', 'column':'name', + 'datatype':'text', 'is_multiple':False, + 'kind':'standard', 'name':_('Authors'), + 'search_labels':['authors', 'author'], + 'is_custom':False}), + ('series', {'table':'series', 'column':'name', + 'datatype':'text', 'is_multiple':False, + 'kind':'standard', 'name':_('Series'), + 'search_labels':['series'], + 'is_custom':False}), + ('formats', {'table':None, 'column':None, + 'datatype':'text', 'is_multiple':False, # must think what type this is! + 'kind':'standard', 'name':_('Formats'), + 'search_labels':['formats', 'format'], + 'is_custom':False}), + ('publisher', {'table':'publishers', 'column':'name', + 'datatype':'text', 'is_multiple':False, + 'kind':'standard', 'name':_('Publishers'), + 'search_labels':['publisher'], + 'is_custom':False}), + ('rating', {'table':'ratings', 'column':'rating', + 'datatype':'rating', 'is_multiple':False, + 'kind':'standard', 'name':_('Ratings'), + 'search_labels':['rating'], + 'is_custom':False}), + ('news', {'table':'news', 'column':'name', + 'datatype':None, 'is_multiple':False, + 'kind':'standard', 'name':_('News'), + 'search_labels':[], + 'is_custom':False}), + ('tags', {'table':'tags', 'column':'name', + 'datatype':'text', 'is_multiple':True, + 'kind':'standard', 'name':_('Tags'), + 'search_labels':['tags', 'tag'], + 'is_custom':False}), + ('author_sort',{'table':None, 'column':None, 'datatype':'text', + 'is_multiple':False, 'kind':'not_cat', 'name':None, + 'search_labels':[], 'is_custom':False}), + ('comments', {'table':None, 'column':None, 'datatype':'text', + 'is_multiple':False, 'kind':'not_cat', 'name':None, + 'search_labels':['comments', 'comment'], 'is_custom':False}), + ('cover', {'table':None, 'column':None, 'datatype':None, + 'is_multiple':False, 'kind':'not_cat', 'name':None, + 'search_labels':['cover'], 'is_custom':False}), + ('flags', {'table':None, 'column':None, 'datatype':'text', + 'is_multiple':False, 'kind':'not_cat', 'name':None, + 'search_labels':[], 'is_custom':False}), + ('id', {'table':None, 'column':None, 'datatype':'int', + 'is_multiple':False, 'kind':'not_cat', 'name':None, + 'search_labels':[], 'is_custom':False}), + ('isbn', {'table':None, 'column':None, 'datatype':'text', + 'is_multiple':False, 'kind':'not_cat', 'name':None, + 'search_labels':['isbn'], 'is_custom':False}), + ('lccn', {'table':None, 'column':None, 'datatype':'text', + 'is_multiple':False, 'kind':'not_cat', 'name':None, + 'search_labels':[], 'is_custom':False}), + ('ondevice', {'table':None, 'column':None, 'datatype':'bool', + 'is_multiple':False, 'kind':'not_cat', 'name':None, + 'search_labels':[], 'is_custom':False}), + ('path', {'table':None, 'column':None, 'datatype':'text', + 'is_multiple':False, 'kind':'not_cat', 'name':None, + 'search_labels':[], 'is_custom':False}), + ('pubdate', {'table':None, 'column':None, 'datatype':'datetime', + 'is_multiple':False, 'kind':'not_cat', 'name':None, + 'search_labels':['pubdate'], 'is_custom':False}), + ('series_index',{'table':None, 'column':None, 'datatype':'float', + 'is_multiple':False, 'kind':'not_cat', 'name':None, + 'search_labels':[], 'is_custom':False}), + ('sort', {'table':None, 'column':None, 'datatype':'text', + 'is_multiple':False, 'kind':'not_cat', 'name':None, + 'search_labels':[], 'is_custom':False}), + ('size', {'table':None, 'column':None, 'datatype':'float', + 'is_multiple':False, 'kind':'not_cat', 'name':None, + 'search_labels':[], 'is_custom':False}), + ('timestamp', {'table':None, 'column':None, 'datatype':'datetime', + 'is_multiple':False, 'kind':'not_cat', 'name':None, + 'search_labels':['date'], 'is_custom':False}), + ('title', {'table':None, 'column':None, 'datatype':'text', + 'is_multiple':False, 'kind':'not_cat', 'name':None, + 'search_labels':['title'], 'is_custom':False}), + ('uuid', {'table':None, 'column':None, 'datatype':'text', + 'is_multiple':False, 'kind':'not_cat', 'name':None, + 'search_labels':[], 'is_custom':False}), + ] + + # search labels that are not db columns + search_items = [ 'all', +# 'date', + 'search', + ] + + def __init__(self): + self._tb_cats = OrderedDict() + for k,v in self.category_items_: + self._tb_cats[k] = v + self._custom_fields = [] + self.custom_field_prefix = '#' + + def __getitem__(self, key): + return self._tb_cats[key] + + def __setitem__(self, key, val): + raise AttributeError('Assigning to this object is forbidden') + + def __delitem__(self, key): + del self._tb_cats[key] + + def __iter__(self): + for key in self._tb_cats: + yield key + + def keys(self): + return self._tb_cats.keys() + + def iterkeys(self): + for key in self._tb_cats: + yield key + + def iteritems(self): + for key in self._tb_cats: + yield (key, self._tb_cats[key]) + + def is_custom_field(self, key): + return key.startswith(self.custom_field_prefix) or key in self._custom_fields + + def get_field_label(self, key): + if 'label' not in self._tb_cats[key]: + return key + return self._tb_cats[key]['label'] + + def get_search_label(self, label): + if 'label' in self._tb_cats: + return label + if self.is_custom_field(label): + return self.custom_field_prefix+label + raise ValueError('Unknown key [%s]'%(label)) + + def get_custom_fields(self): + return [l for l in self._tb_cats if self._tb_cats[l]['is_custom']] + + def add_custom_field(self, label, table, column, datatype, + is_multiple, colnum, name, searchable): + fn = self.custom_field_prefix + label + if fn in self._tb_cats: + raise ValueError('Duplicate custom field [%s]'%(label)) + self._custom_fields.append(label) + if searchable: + sl = [fn] + kind = 'standard' + else: + sl = [] + kind = 'not_cat' + self._tb_cats[fn] = {'table':table, 'column':column, + 'datatype':datatype, 'is_multiple':is_multiple, + 'kind':kind, 'name':name, + 'search_labels':sl, 'label':label, + 'colnum':colnum, 'is_custom':True} + + def set_field_record_index(self, label, index, prefer_custom=False): + if prefer_custom: + key = self.custom_field_prefix+label + if key not in self._tb_cats: + key = label + else: + if label in self._tb_cats: + key = label + else: + key = self.custom_field_prefix+label + self._tb_cats[key]['rec_index'] = index # let the exception fly ... + + def add_user_category(self, label, name): + if label in self._tb_cats: + raise ValueError('Duplicate user field [%s]'%(label)) + self._tb_cats[label] = {'table':None, 'column':None, + 'datatype':None, 'is_multiple':False, + 'kind':'user', 'name':name, + 'search_labels':[], 'is_custom':False} + + def add_search_category(self, label, name): + if label in self._tb_cats: + raise ValueError('Duplicate user field [%s]'%(label)) + self._tb_cats[label] = {'table':None, 'column':None, + 'datatype':None, 'is_multiple':False, + 'kind':'search', 'name':name, + 'search_labels':[], 'is_custom':False} + +# 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_labels(self): + s_labels = [] + for v in self._tb_cats.itervalues(): + map((lambda x:s_labels.append(x)), v['search_labels']) + for v in self.search_items: + s_labels.append(v) +# if set(s_labels) != self.DEFAULT_LOCATIONS: +# print 'search labels and default_locations do not match:' +# print set(s_labels) ^ self.DEFAULT_LOCATIONS + return s_labels diff --git a/src/calibre/library/tag_categories.py b/src/calibre/library/tag_categories.py deleted file mode 100644 index 63327fac45..0000000000 --- a/src/calibre/library/tag_categories.py +++ /dev/null @@ -1,202 +0,0 @@ -''' -Created on 25 May 2010 - -@author: charles -''' - -from UserDict import DictMixin -from calibre.utils.ordered_dict import OrderedDict - -class TagsIcons(dict): - ''' - If the client wants icons to be in the tag structure, this class must be - instantiated and filled in with real icons. If this class is instantiated - and passed to get_categories, All items must be given a value not None - ''' - - category_icons = ['authors', 'series', 'formats', 'publisher', 'rating', - 'news', 'tags', ':custom', ':user', 'search',] - def __init__(self, icon_dict): - for a in self.category_icons: - if a not in icon_dict: - raise ValueError('Missing category icon [%s]'%a) - self[a] = icon_dict[a] - -class TagsMetadata(dict, DictMixin): - - # kind == standard: is tag category. May be a search label. Is db col - # or is specially handled (e.g., news) - # kind == not_cat: Is not a tag category. Should be a search label. Is db col - # kind == user: user-defined tag category - # kind == search: saved-searches category - # Order as has been customary in the tags pane. - category_items_ = [ - ('authors', {'table':'authors', 'column':'name', - 'datatype':'text', 'is_multiple':False, - 'kind':'standard', 'name':_('Authors'), - 'search_labels':['authors', 'author']}), - ('series', {'table':'series', 'column':'name', - 'datatype':'text', 'is_multiple':False, - 'kind':'standard', 'name':_('Series'), - 'search_labels':['series']}), - ('formats', {'table':None, 'column':None, - 'datatype':None, 'is_multiple':False, - 'kind':'standard', 'name':_('Formats'), - 'search_labels':['formats', 'format']}), - ('publisher', {'table':'publishers', 'column':'name', - 'datatype':'text', 'is_multiple':False, - 'kind':'standard', 'name':_('Publishers'), - 'search_labels':['publisher']}), - ('rating', {'table':'ratings', 'column':'rating', - 'datatype':'rating', 'is_multiple':False, - 'kind':'standard', 'name':_('Ratings'), - 'search_labels':['rating']}), - ('news', {'table':'news', 'column':'name', - 'datatype':None, 'is_multiple':False, - 'kind':'standard', 'name':_('News'), - 'search_labels':[]}), - ('tags', {'table':'tags', 'column':'name', - 'datatype':'text', 'is_multiple':True, - 'kind':'standard', 'name':_('Tags'), - 'search_labels':['tags', 'tag']}), - ('comments', {'table':None, 'column':None, - 'datatype':'text', 'is_multiple':False, - 'kind':'not_cat', 'name':None, - 'search_labels':['comments', 'comment']}), - ('cover', {'table':None, 'column':None, - 'datatype':None, 'is_multiple':False, - 'kind':'not_cat', 'name':None, - 'search_labels':['cover']}), - ('timestamp', {'table':None, 'column':None, - 'datatype':'datetime', 'is_multiple':False, - 'kind':'not_cat', 'name':None, - 'search_labels':['date']}), - ('isbn', {'table':None, 'column':None, - 'datatype':'text', 'is_multiple':False, - 'kind':'not_cat', 'name':None, - 'search_labels':['isbn']}), - ('pubdate', {'table':None, 'column':None, - 'datatype':'datetime', 'is_multiple':False, - 'kind':'not_cat', 'name':None, - 'search_labels':['pubdate']}), - ('title', {'table':None, 'column':None, - 'datatype':'text', 'is_multiple':False, - 'kind':'not_cat', 'name':None, - 'search_labels':['title']}), - ] - - # search labels that are not db columns - search_items = [ 'all', -# 'date', - 'search', - ] - - def __init__(self): - self._tb_cats = OrderedDict() - for k,v in self.category_items_: - self._tb_cats[k] = v - self._custom_fields = [] - self.custom_field_prefix = '#' - - def __getitem__(self, key): - return self._tb_cats[key] - - def __setitem__(self, key, val): - raise AttributeError('Assigning to this object is forbidden') - - def __delitem__(self, key): - del self._tb_cats[key] - - def __iter__(self): - for key in self._tb_cats: - yield key - - def keys(self): - return self._tb_cats.keys() - - def iterkeys(self): - for key in self._tb_cats: - yield key - - def iteritems(self): - for key in self._tb_cats: - yield (key, self._tb_cats[key]) - - def is_custom_field(self, label): - return label.startswith(self.custom_field_prefix) or label in self._custom_fields - - def get_field_label(self, key): - if 'label' not in self._tb_cats[key]: - return key - return self._tb_cats[key]['label'] - - def get_search_label(self, key): - if 'label' in self._tb_cats: - return key - if self.is_custom_field(key): - return self.custom_field_prefix+key - raise ValueError('Unknown key [%s]'%(key)) - - def get_custom_fields(self): - return [l for l in self._tb_cats if self._tb_cats[l]['kind'] == 'custom'] - - def add_custom_field(self, field_name, table, column, datatype, is_multiple, number, name): - fn = self.custom_field_prefix + field_name - if fn in self._tb_cats: - raise ValueError('Duplicate custom field [%s]'%(field_name)) - self._custom_fields.append(field_name) - self._tb_cats[fn] = {'table':table, 'column':column, - 'datatype':datatype, 'is_multiple':is_multiple, - 'kind':'custom', 'name':name, - 'search_labels':[fn],'label':field_name, - 'colnum':number} - - def add_user_category(self, field_name, name): - if field_name in self._tb_cats: - raise ValueError('Duplicate user field [%s]'%(field_name)) - self._tb_cats[field_name] = {'table':None, 'column':None, - 'datatype':None, 'is_multiple':False, - 'kind':'user', 'name':name, - 'search_labels':[]} - - def add_search_category(self, field_name, name): - if field_name in self._tb_cats: - raise ValueError('Duplicate user field [%s]'%(field_name)) - self._tb_cats[field_name] = {'table':None, 'column':None, - 'datatype':None, 'is_multiple':False, - 'kind':'search', 'name':name, - 'search_labels':[]} - -# 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_labels(self): - s_labels = [] - for v in self._tb_cats.itervalues(): - map((lambda x:s_labels.append(x)), v['search_labels']) - for v in self.search_items: - s_labels.append(v) -# if set(s_labels) != self.DEFAULT_LOCATIONS: -# print 'search labels and default_locations do not match:' -# print set(s_labels) ^ self.DEFAULT_LOCATIONS - return s_labels