diff --git a/src/calibre/gui2/dialogs/tag_categories.py b/src/calibre/gui2/dialogs/tag_categories.py index 50a0dccfb3..fcf517e571 100644 --- a/src/calibre/gui2/dialogs/tag_categories.py +++ b/src/calibre/gui2/dialogs/tag_categories.py @@ -49,7 +49,7 @@ class TagCategories(QDialog, Ui_TagCategories): cc_map = self.db.custom_column_label_map for cc in cc_map: if cc_map[cc]['datatype'] == 'text': - self.category_labels.append(db.tag_browser_categories.label_to_key(cc)) + self.category_labels.append(db.field_metadata.label_to_key(cc)) category_icons.append(cc_icon) category_values.append(lambda col=cc: self.db.all_custom(label=col)) category_names.append(cc_map[cc]['name']) diff --git a/src/calibre/gui2/library/models.py b/src/calibre/gui2/library/models.py index a871ce2aa3..072f81e2d1 100644 --- a/src/calibre/gui2/library/models.py +++ b/src/calibre/gui2/library/models.py @@ -633,7 +633,7 @@ class BooksModel(QAbstractTableModel): # {{{ if role == Qt.ToolTipRole: ht = self.column_map[section] if self.is_custom_column(self.column_map[section]): - ht = self.db.tag_browser_categories.custom_field_prefix + ht + ht = self.db.field_metadata.custom_field_prefix + ht if ht == 'timestamp': # change help text because users know this field as 'date' ht = 'date' return QVariant(_('The lookup/search name is "{0}"').format(ht)) diff --git a/src/calibre/gui2/tag_view.py b/src/calibre/gui2/tag_view.py index 3882e4e174..8ecc26e30c 100644 --- a/src/calibre/gui2/tag_view.py +++ b/src/calibre/gui2/tag_view.py @@ -224,7 +224,7 @@ class TagsModel(QAbstractItemModel): # {{{ data = self.get_node_tree(config['sort_by_popularity']) self.root_item = TagTreeItem() for i, r in enumerate(self.row_map): - if self.db.get_tag_browser_categories()[r]['kind'] != 'user': + if self.db.field_metadata[r]['kind'] != 'user': tt = _('The lookup/search name is "{0}"').format(r) else: tt = '' @@ -248,7 +248,7 @@ class TagsModel(QAbstractItemModel): # {{{ else: data = self.db.get_categories(sort_on_count=sort, icon_map=self.category_icon_map) - tb_categories = self.db.get_tag_browser_categories() + tb_categories = self.db.field_metadata for category in tb_categories: if category in data: # They should always be there, but ... self.row_map.append(category) diff --git a/src/calibre/library/caches.py b/src/calibre/library/caches.py index 20cfce02bd..47529f223f 100644 --- a/src/calibre/library/caches.py +++ b/src/calibre/library/caches.py @@ -150,14 +150,14 @@ class ResultCache(SearchQueryParser): ''' Stores sorted and filtered metadata in memory. ''' - def __init__(self, FIELD_MAP, cc_label_map, tag_browser_categories): + def __init__(self, FIELD_MAP, cc_label_map, field_metadata): self.FIELD_MAP = FIELD_MAP self.custom_column_label_map = cc_label_map self._map = self._map_filtered = self._data = [] self.first_sort = True self.search_restriction = '' - self.tag_browser_categories = tag_browser_categories - self.all_search_locations = tag_browser_categories.get_search_keys() + self.field_metadata = field_metadata + self.all_search_locations = field_metadata.get_search_terms() SearchQueryParser.__init__(self, self.all_search_locations) self.build_date_relop_dict() self.build_numeric_relop_dict() @@ -252,7 +252,7 @@ class ResultCache(SearchQueryParser): if location == 'date': location = 'timestamp' - loc = self.tag_browser_categories[location]['rec_index'] + loc = self.field_metadata[location]['rec_index'] if query == _('today'): qd = now() @@ -311,8 +311,8 @@ class ResultCache(SearchQueryParser): if relop is None: (p, relop) = self.numeric_search_relops['='] - loc = self.tag_browser_categories[location]['rec_index'] - dt = self.tag_browser_categories[location]['datatype'] + loc = self.field_metadata[location]['rec_index'] + dt = self.field_metadata[location]['datatype'] if dt == 'int': cast = (lambda x: int (x)) adjust = lambda x: x @@ -342,23 +342,21 @@ class ResultCache(SearchQueryParser): def get_matches(self, location, query): matches = set([]) if query and query.strip(): - location = location.lower().strip() - if location in ('tag', 'author', 'format', 'comment'): - location += 's' + # get metadata key associated with the search term. Eliminates + # dealing with plurals and other aliases + location = self.field_metadata.search_term_to_key(location.lower().strip()) - ### take care of dates special case - if (location in ('pubdate', 'date')) or \ - (location in self.tag_browser_categories and \ - self.tag_browser_categories[location]['datatype'] == 'datetime'): + # take care of dates special case + if location in self.field_metadata and \ + self.field_metadata[location]['datatype'] == 'datetime': return self.get_dates_matches(location, query.lower()) - ### take care of numbers special case - if location in self.tag_browser_categories and \ - self.tag_browser_categories[location]['datatype'] in \ - ('rating', 'int', 'float'): + # take care of numbers special case + if location in self.field_metadata and \ + self.field_metadata[location]['datatype'] in ('rating', 'int', 'float'): return self.get_numeric_matches(location, query.lower()) - ### everything else, or 'all' matches + # everything else, or 'all' matches matchkind = CONTAINS_MATCH if (len(query) > 1): if query.startswith('\\'): @@ -377,25 +375,18 @@ class ResultCache(SearchQueryParser): query = query.decode('utf-8') db_col = {} - # fields to not check when matching against text. - exclude_fields = [] + exclude_fields = [] # fields to not check when matching against text. col_datatype = [] + is_multiple_cols = {} for x in range(len(self.FIELD_MAP)): col_datatype.append('') - for x in self.tag_browser_categories: - if len(self.tag_browser_categories[x]['search_keys']): - db_col[x] = self.tag_browser_categories[x]['rec_index'] - if self.tag_browser_categories[x]['datatype'] not in ['text', 'comments']: + for x in self.field_metadata: + if len(self.field_metadata[x]['search_terms']): + db_col[x] = self.field_metadata[x]['rec_index'] + if self.field_metadata[x]['datatype'] not in ['text', 'comments']: exclude_fields.append(db_col[x]) - if self.tag_browser_categories.is_custom_field(x): - col_datatype[db_col[x]] = self.tag_browser_categories[x]['datatype'] - # normal and custom ratings columns use the same code - col_datatype[self.FIELD_MAP['rating']] = 'rating' - - splitable_fields = [db_col['authors'], db_col['tags'], db_col['formats']] - for x in self.tag_browser_categories.get_custom_fields(): - if self.tag_browser_categories[x]['is_multiple']: - splitable_fields.append(db_col[x]) + col_datatype[db_col[x]] = self.field_metadata[x]['datatype'] + is_multiple_cols[db_col[x]] = self.field_metadata[x]['is_multiple'] try: rating_query = int(query) * 2 @@ -409,7 +400,7 @@ class ResultCache(SearchQueryParser): # get the tweak here so that the string lookup and compare aren't in the loop bools_are_tristate = tweaks['bool_custom_columns_are_tristate'] == 'yes' - for loc in location: + for loc in location: # location is now an array of field indices if loc == db_col['authors']: ### DB stores authors with commas changed to bars, so change query q = query.replace(',', '|'); @@ -460,7 +451,7 @@ class ResultCache(SearchQueryParser): continue try: # a conversion below might fail - # relationals not supported in 'all' queries + # relationals are not supported in 'all' queries if col_datatype[loc] == 'float': if float(query) == item[loc]: matches.add(item[0]) @@ -474,12 +465,9 @@ class ResultCache(SearchQueryParser): # no further match is possible continue - if loc not in exclude_fields: - if loc in splitable_fields: - if col_datatype[loc]: - vals = item[loc].split('|') - else: - vals = item[loc].split(',') + if loc not in exclude_fields: # time for text matching + if is_multiple_cols[loc] is not None: + vals = item[loc].split(is_multiple_cols[loc]) else: vals = [item[loc]] ### make into list to make _match happy if _match(q, vals, matchkind): diff --git a/src/calibre/library/custom_columns.py b/src/calibre/library/custom_columns.py index 5b5fcadf59..7a63f37588 100644 --- a/src/calibre/library/custom_columns.py +++ b/src/calibre/library/custom_columns.py @@ -147,11 +147,15 @@ class CustomColumns(object): is_category = True else: is_category = False + if v['is_multiple']: + is_m = '|' + else: + is_m = None tn = 'custom_column_{0}'.format(v['num']) - self.tag_browser_categories.add_custom_field(label=v['label'], + self.field_metadata.add_custom_field(label=v['label'], table=tn, column='value', datatype=v['datatype'], - is_multiple=v['is_multiple'], colnum=v['num'], - name=v['name'], is_category=is_category) + is_multiple=is_m, colnum=v['num'], name=v['name'], + is_category=is_category) 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 939874ed3b..5d4c2a783e 100644 --- a/src/calibre/library/database2.py +++ b/src/calibre/library/database2.py @@ -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 = FieldMetadata() #.get_tag_browser_categories() + self.field_metadata = FieldMetadata() if not os.path.exists(library_path): os.makedirs(library_path) self.listeners = set([]) @@ -206,20 +206,20 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): '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) + self.field_metadata.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.field_metadata.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_metadata.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) + self.field_metadata.set_field_record_index('ondevice', base+2, prefer_custom=False) script = ''' DROP VIEW IF EXISTS meta2; @@ -233,7 +233,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): self.book_on_device_func = None self.data = ResultCache(self.FIELD_MAP, self.custom_column_label_map, - self.tag_browser_categories) + self.field_metadata) self.search = self.data.search self.refresh = functools.partial(self.data.refresh, self) self.sort = self.data.sort @@ -646,9 +646,6 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): def get_recipe(self, id): return self.conn.get('SELECT script FROM feeds WHERE id=?', (id,), all=False) - def get_tag_browser_categories(self): - return self.tag_browser_categories - def get_categories(self, sort_on_count=False, ids=None, icon_map=None): self.books_list_filter.change([] if not ids else ids) @@ -656,9 +653,9 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): if icon_map is not None and type(icon_map) != TagsIcons: raise TypeError('icon_map passed to get_categories must be of type TagIcons') - tb_cats = self.tag_browser_categories + tb_cats = self.field_metadata - # remove all user categories from tag_browser_categories. They can + # remove all user categories from field_metadata. They can # easily come and go. We will add all the existing ones in below. for k in tb_cats.keys(): if tb_cats[k]['kind'] in ['user', 'search']: diff --git a/src/calibre/library/field_metadata.py b/src/calibre/library/field_metadata.py index f8f3695473..638b7c7dd0 100644 --- a/src/calibre/library/field_metadata.py +++ b/src/calibre/library/field_metadata.py @@ -22,104 +22,252 @@ class TagsIcons(dict): self[a] = icon_dict[a] class FieldMetadata(dict): + ''' + key: the key to the dictionary is: + - for standard fields, the metadata field name. + - for custom fields, the metadata field name prefixed by '#' + This is done to create two 'namespaces' so the names don't clash - # 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. + label: the actual column label. No prefixing. - category_items_ = [ - ('authors', {'table':'authors', 'column':'name', - 'datatype':'text', 'is_multiple':False, - 'kind':'standard', 'name':_('Authors'), - 'search_keys':['authors', 'author'], - 'is_custom':False, 'is_category':True}), - ('series', {'table':'series', 'column':'name', - 'datatype':'text', 'is_multiple':False, - 'kind':'standard', 'name':_('Series'), - 'search_keys':['series'], - 'is_custom':False, 'is_category':True}), - ('formats', {'table':None, 'column':None, - 'datatype':'text', 'is_multiple':False, # must think what type this is! - 'kind':'standard', 'name':_('Formats'), - 'search_keys':['formats', 'format'], - 'is_custom':False, 'is_category':True}), - ('publisher', {'table':'publishers', 'column':'name', - 'datatype':'text', 'is_multiple':False, - 'kind':'standard', 'name':_('Publishers'), - 'search_keys':['publisher'], - 'is_custom':False, 'is_category':True}), - ('rating', {'table':'ratings', 'column':'rating', - 'datatype':'rating', 'is_multiple':False, - 'kind':'standard', 'name':_('Ratings'), - 'search_keys':['rating'], - 'is_custom':False, 'is_category':True}), - ('news', {'table':'news', 'column':'name', - 'datatype':None, 'is_multiple':False, - 'kind':'standard', 'name':_('News'), - 'search_keys':[], - 'is_custom':False, 'is_category':True}), - ('tags', {'table':'tags', 'column':'name', - 'datatype':'text', 'is_multiple':True, - 'kind':'standard', 'name':_('Tags'), - 'search_keys':['tags', 'tag'], - 'is_custom':False, 'is_category':True}), - ('author_sort',{'table':None, 'column':None, 'datatype':'text', - 'is_multiple':False, 'kind':'standard', 'name':None, - 'search_keys':[], 'is_custom':False, 'is_category':False}), - ('comments', {'table':None, 'column':None, 'datatype':'text', - 'is_multiple':False, 'kind':'standard', 'name':None, - 'search_keys':['comments', 'comment'], 'is_custom':False, 'is_category':False}), - ('cover', {'table':None, 'column':None, 'datatype':None, - 'is_multiple':False, 'kind':'standard', 'name':None, - 'search_keys':['cover'], 'is_custom':False, 'is_category':False}), - ('flags', {'table':None, 'column':None, 'datatype':'text', - 'is_multiple':False, 'kind':'standard', 'name':None, - 'search_keys':[], 'is_custom':False, 'is_category':False}), - ('id', {'table':None, 'column':None, 'datatype':'int', - 'is_multiple':False, 'kind':'standard', 'name':None, - 'search_keys':[], 'is_custom':False, 'is_category':False}), - ('isbn', {'table':None, 'column':None, 'datatype':'text', - 'is_multiple':False, 'kind':'standard', 'name':None, - 'search_keys':['isbn'], 'is_custom':False, 'is_category':False}), - ('lccn', {'table':None, 'column':None, 'datatype':'text', - 'is_multiple':False, 'kind':'standard', 'name':None, - 'search_keys':[], 'is_custom':False, 'is_category':False}), - ('ondevice', {'table':None, 'column':None, 'datatype':'bool', - 'is_multiple':False, 'kind':'standard', 'name':None, - 'search_keys':[], 'is_custom':False, 'is_category':False}), - ('path', {'table':None, 'column':None, 'datatype':'text', - 'is_multiple':False, 'kind':'standard', 'name':None, - 'search_keys':[], 'is_custom':False, 'is_category':False}), - ('pubdate', {'table':None, 'column':None, 'datatype':'datetime', - 'is_multiple':False, 'kind':'standard', 'name':None, - 'search_keys':['pubdate'], 'is_custom':False, 'is_category':False}), - ('series_index',{'table':None, 'column':None, 'datatype':'float', - 'is_multiple':False, 'kind':'standard', 'name':None, - 'search_keys':[], 'is_custom':False, 'is_category':False}), - ('sort', {'table':None, 'column':None, 'datatype':'text', - 'is_multiple':False, 'kind':'standard', 'name':None, - 'search_keys':[], 'is_custom':False, 'is_category':False}), - ('size', {'table':None, 'column':None, 'datatype':'float', - 'is_multiple':False, 'kind':'standard', 'name':None, - 'search_keys':[], 'is_custom':False, 'is_category':False}), - ('timestamp', {'table':None, 'column':None, 'datatype':'datetime', - 'is_multiple':False, 'kind':'standard', 'name':None, - 'search_keys':['date'], 'is_custom':False, 'is_category':False}), - ('title', {'table':None, 'column':None, 'datatype':'text', - 'is_multiple':False, 'kind':'standard', 'name':None, - 'search_keys':['title'], 'is_custom':False, 'is_category':False}), - ('uuid', {'table':None, 'column':None, 'datatype':'text', - 'is_multiple':False, 'kind':'standard', 'name':None, - 'search_keys':[], 'is_custom':False, 'is_category':False}), + datatype: the type of the information in the field. Valid values are float, + int, rating, bool, comments, datetime, text. + is_multiple: valid for the text datatype. If None, the field is to be + treated as a single term. If not None, it contains a string, and the field + is assumed to contain a list of terms separated by that string + + kind == standard: is a db field. + kind == category: standard tag category that isn't a field. see news. + kind == user: user-defined tag category. + kind == search: saved-searches category. + + is_category: is a tag browser category. If true, then: + table: name of the db table used to construct item list + column: name of the column in the connection table to join on + If these are None, then the category constructor must know how + to build the item list (e.g., formats). + The order below is the order that the categories will + appear in the tags pane. + + name: the text that is to be used when displaying the field. Column headings + in the GUI, etc. + + search_terms: the terms that can be used to identify the field when + searching. They can be thought of as aliases for metadata keys, but are only + valid when passed to search(). + + is_custom: the field has been added by the user. + + rec_index: the index of the field in the db metadata record. + + ''' + _field_metadata = [ + ('authors', {'table':'authors', + 'column':'name', + 'datatype':'text', + 'is_multiple':',', + 'kind':'field', + 'name':_('Authors'), + 'search_terms':['authors', 'author'], + 'is_custom':False, + 'is_category':True}), + ('series', {'table':'series', + 'column':'name', + 'datatype':'text', + 'is_multiple':None, + 'kind':'field', + 'name':_('Series'), + 'search_terms':['series'], + 'is_custom':False, + 'is_category':True}), + ('formats', {'table':None, + 'column':None, + 'datatype':'text', + 'is_multiple':',', + 'kind':'field', + 'name':_('Formats'), + 'search_terms':['formats', 'format'], + 'is_custom':False, + 'is_category':True}), + ('publisher', {'table':'publishers', + 'column':'name', + 'datatype':'text', + 'is_multiple':None, + 'kind':'field', + 'name':_('Publishers'), + 'search_terms':['publisher'], + 'is_custom':False, + 'is_category':True}), + ('rating', {'table':'ratings', + 'column':'rating', + 'datatype':'rating', + 'is_multiple':None, + 'kind':'field', + 'name':_('Ratings'), + 'search_terms':['rating'], + 'is_custom':False, + 'is_category':True}), + ('news', {'table':'news', + 'column':'name', + 'datatype':None, + 'is_multiple':None, + 'kind':'category', + 'name':_('News'), + 'search_terms':[], + 'is_custom':False, + 'is_category':True}), + ('tags', {'table':'tags', + 'column':'name', + 'datatype':'text', + 'is_multiple':',', + 'kind':'field', + 'name':_('Tags'), + 'search_terms':['tags', 'tag'], + 'is_custom':False, + 'is_category':True}), + ('author_sort',{'table':None, + 'column':None, + 'datatype':'text', + 'is_multiple':None, + 'kind':'field', + 'name':None, + 'search_terms':[], + 'is_custom':False, + 'is_category':False}), + ('comments', {'table':None, + 'column':None, + 'datatype':'text', + 'is_multiple':None, + 'kind':'field', + 'name':None, + 'search_terms':['comments', 'comment'], + 'is_custom':False, 'is_category':False}), + ('cover', {'table':None, + 'column':None, + 'datatype':None, + 'is_multiple':None, + 'kind':'field', + 'name':None, + 'search_terms':['cover'], + 'is_custom':False, + 'is_category':False}), + ('flags', {'table':None, + 'column':None, + 'datatype':'text', + 'is_multiple':None, + 'kind':'field', + 'name':None, + 'search_terms':[], + 'is_custom':False, + 'is_category':False}), + ('id', {'table':None, + 'column':None, + 'datatype':'int', + 'is_multiple':None, + 'kind':'field', + 'name':None, + 'search_terms':[], + 'is_custom':False, + 'is_category':False}), + ('isbn', {'table':None, + 'column':None, + 'datatype':'text', + 'is_multiple':None, + 'kind':'field', + 'name':None, + 'search_terms':['isbn'], + 'is_custom':False, + 'is_category':False}), + ('lccn', {'table':None, + 'column':None, + 'datatype':'text', + 'is_multiple':None, + 'kind':'field', + 'name':None, + 'search_terms':[], + 'is_custom':False, + 'is_category':False}), + ('ondevice', {'table':None, + 'column':None, + 'datatype':'bool', + 'is_multiple':None, + 'kind':'field', + 'name':None, + 'search_terms':[], + 'is_custom':False, + 'is_category':False}), + ('path', {'table':None, + 'column':None, + 'datatype':'text', + 'is_multiple':None, + 'kind':'field', + 'name':None, + 'search_terms':[], + 'is_custom':False, + 'is_category':False}), + ('pubdate', {'table':None, + 'column':None, + 'datatype':'datetime', + 'is_multiple':None, + 'kind':'field', + 'name':None, + 'search_terms':['pubdate'], + 'is_custom':False, + 'is_category':False}), + ('series_index',{'table':None, + 'column':None, + 'datatype':'float', + 'is_multiple':None, + 'kind':'field', + 'name':None, + 'search_terms':[], + 'is_custom':False, + 'is_category':False}), + ('sort', {'table':None, + 'column':None, + 'datatype':'text', + 'is_multiple':None, + 'kind':'field', + 'name':None, + 'search_terms':[], + 'is_custom':False, + 'is_category':False}), + ('size', {'table':None, + 'column':None, + 'datatype':'float', + 'is_multiple':None, + 'kind':'field', + 'name':None, + 'search_terms':[], + 'is_custom':False, + 'is_category':False}), + ('timestamp', {'table':None, + 'column':None, + 'datatype':'datetime', + 'is_multiple':None, + 'kind':'field', + 'name':None, + 'search_terms':['date'], + 'is_custom':False, + 'is_category':False}), + ('title', {'table':None, + 'column':None, + 'datatype':'text', + 'is_multiple':None, + 'kind':'field', + 'name':None, + 'search_terms':['title'], + 'is_custom':False, + 'is_category':False}), + ('uuid', {'table':None, + 'column':None, + 'datatype':'text', + 'is_multiple':None, + 'kind':'field', + 'name':None, + 'search_terms':[], + 'is_custom':False, + 'is_category':False}), ] # search labels that are not db columns @@ -130,10 +278,13 @@ class FieldMetadata(dict): def __init__(self): self._tb_cats = OrderedDict() - for k,v in self.category_items_: + self._search_term_map = {} + self.custom_label_to_key_map = {} + for k,v in self._field_metadata: self._tb_cats[k] = v + self._tb_cats[k]['label'] = k # saved some typing above... + self._add_search_terms_to_map(k, self._tb_cats[k]['search_terms']) self.custom_field_prefix = '#' - self.get = self._tb_cats.get def __getitem__(self, key): @@ -175,10 +326,14 @@ class FieldMetadata(dict): return self._tb_cats[key]['label'] def label_to_key(self, label, prefer_custom=False): + if prefer_custom: + if label in self.custom_label_to_key_map: + return self.custom_label_to_key_map[label] if 'label' in self._tb_cats: return label - if self.is_custom_field(label): - return self.custom_field_prefix+label + if not prefer_custom: + if label in self.custom_label_to_key_map: + return self.custom_label_to_key_map[label] raise ValueError('Unknown key [%s]'%(label)) def get_custom_fields(self): @@ -186,15 +341,35 @@ class FieldMetadata(dict): def add_custom_field(self, label, table, column, datatype, colnum, name, is_multiple, is_category): - fn = self.custom_field_prefix + label - if fn in self._tb_cats: + key = self.custom_field_prefix + label + if key in self._tb_cats: raise ValueError('Duplicate custom field [%s]'%(label)) - self._tb_cats[fn] = {'table':table, 'column':column, - 'datatype':datatype, 'is_multiple':is_multiple, - 'kind':'standard', 'name':name, - 'search_keys':[fn], 'label':label, - 'colnum':colnum, 'is_custom':True, + self._tb_cats[key] = {'table':table, 'column':column, + 'datatype':datatype, 'is_multiple':is_multiple, + 'kind':'field', 'name':name, + 'search_terms':[key], 'label':label, + 'colnum':colnum, 'is_custom':True, 'is_category':is_category} + self._add_search_terms_to_map(key, [key]) + self.custom_label_to_key_map[label] = key + + 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':None, + 'kind':'user', 'name':name, + 'search_terms':[], 'is_custom':False, + 'is_category':True} + + 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':None, + 'kind':'search', 'name':name, + 'search_terms':[], 'is_custom':False, + 'is_category':True} def set_field_record_index(self, label, index, prefer_custom=False): if prefer_custom: @@ -208,23 +383,6 @@ class FieldMetadata(dict): 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_keys':[], 'is_custom':False, - 'is_category':True} - - 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_keys':[], 'is_custom':False, - 'is_category':True} # DEFAULT_LOCATIONS = frozenset([ # 'all', @@ -248,14 +406,23 @@ class FieldMetadata(dict): # 'title', # ]) - - def get_search_keys(self): + def get_search_terms(self): s_keys = [] for v in self._tb_cats.itervalues(): - map((lambda x:s_keys.append(x)), v['search_keys']) + map((lambda x:s_keys.append(x)), v['search_terms']) 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): + if terms is not None: + for t in terms: + self._search_term_map[t] = key + + def search_term_to_key(self, term): + if term in self._search_term_map: + return self._search_term_map[term] + return term diff --git a/src/calibre/library/schema_upgrades.py b/src/calibre/library/schema_upgrades.py index f1e68b3916..8d4fd5eba3 100644 --- a/src/calibre/library/schema_upgrades.py +++ b/src/calibre/library/schema_upgrades.py @@ -289,6 +289,6 @@ class SchemaUpgrade(object): '''.format(tn=table_name, cn=column_name, vcn=view_column_name)) self.conn.executescript(script) - for tn, cn in self.tag_browser_categories.items(): + for tn, cn in self.field_metadata.items(): if tn != 'news': create_tag_browser_view(tn, cn[0], cn[1]) diff --git a/src/calibre/library/server/opds.py b/src/calibre/library/server/opds.py index 86fae8bae8..7c727cbf52 100644 --- a/src/calibre/library/server/opds.py +++ b/src/calibre/library/server/opds.py @@ -331,7 +331,7 @@ class OPDSServer(object): raise cherrypy.HTTPError(404, 'Not found') categories = self.categories_cache( self.get_opds_allowed_ids_for_version(version)) - category_meta = self.db.get_tag_browser_categories() + category_meta = self.db.field_metadata cats = [ (_('Newest'), _('Date'), 'Onewest'), (_('Title'), _('Title'), 'Otitle'),