From 53fb1ef4621cf1a6a88ce15b34b2d49d1210ab81 Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Wed, 26 May 2010 12:19:53 +0100 Subject: [PATCH 1/6] First iteration of refactoring tag category and search term management --- src/calibre/ebooks/metadata/book/__init__.py | 11 -- src/calibre/gui2/tag_view.py | 12 +- src/calibre/library/caches.py | 39 ++-- src/calibre/library/custom_columns.py | 9 +- src/calibre/library/database2.py | 79 +++----- src/calibre/library/tag_categories.py | 194 +++++++++++++++++++ src/calibre/utils/search_query_parser.py | 29 +-- 7 files changed, 254 insertions(+), 119 deletions(-) create mode 100644 src/calibre/library/tag_categories.py diff --git a/src/calibre/ebooks/metadata/book/__init__.py b/src/calibre/ebooks/metadata/book/__init__.py index 39fb1920cd..23fed3171a 100644 --- a/src/calibre/ebooks/metadata/book/__init__.py +++ b/src/calibre/ebooks/metadata/book/__init__.py @@ -89,17 +89,6 @@ CALIBRE_METADATA_FIELDS = frozenset([ ) CALIBRE_RESERVED_LABELS = frozenset([ - 'all', # search term - 'date', # search term - 'formats', # search term - 'inlibrary', # search term - 'news', # search term - 'ondevice', # search term - 'search', # search term - 'format', # The next four are here for backwards compatibility - 'tag', # with searching. The terms can be used without the - 'author', # trailing 's'. - 'comment', # Sigh ... ] ) diff --git a/src/calibre/gui2/tag_view.py b/src/calibre/gui2/tag_view.py index 8bbdc69c62..f2729da480 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.ebooks.metadata.book import RESERVED_METADATA_FIELDS +from calibre.library.tag_categories import TagsIcons class TagsView(QTreeView): # {{{ @@ -205,7 +205,7 @@ class TagsModel(QAbstractItemModel): # {{{ # must do this here because 'QPixmap: Must construct a QApplication # before a QPaintDevice'. The ':' in front avoids polluting either the # user-defined categories (':' at end) or columns namespaces (no ':'). - self.category_icon_map = { + self.category_icon_map = TagsIcons({ 'authors' : QIcon(I('user_profile.svg')), 'series' : QIcon(I('series.svg')), 'formats' : QIcon(I('book.svg')), @@ -215,10 +215,8 @@ class TagsModel(QAbstractItemModel): # {{{ 'tags' : QIcon(I('tags.svg')), ':custom' : QIcon(I('column.svg')), ':user' : QIcon(I('drawer.svg')), - 'search' : QIcon(I('search.svg'))} - for k in self.category_icon_map.keys(): - if not k.startswith(':') and k not in RESERVED_METADATA_FIELDS: - raise ValueError('Tag category [%s] is not a reserved word.' %(k)) + 'search' : QIcon(I('search.svg'))}) + self.icon_state_map = [None, QIcon(I('plus.svg')), QIcon(I('minus.svg'))] self.db = db self.search_restriction = '' @@ -247,7 +245,7 @@ class TagsModel(QAbstractItemModel): # {{{ data = self.db.get_categories(sort_on_count=sort, icon_map=self.category_icon_map) tb_categories = self.db.get_tag_browser_categories() - for category in tb_categories.iterkeys(): + for category in tb_categories: if category in data: # They should always be there, but ... self.row_map.append(category) self.categories.append(tb_categories[category]['name']) diff --git a/src/calibre/library/caches.py b/src/calibre/library/caches.py index 10487af75a..9e140b4125 100644 --- a/src/calibre/library/caches.py +++ b/src/calibre/library/caches.py @@ -17,6 +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 class CoverCache(QThread): @@ -149,15 +150,15 @@ class ResultCache(SearchQueryParser): ''' Stores sorted and filtered metadata in memory. ''' - def __init__(self, FIELD_MAP, cc_label_map): + def __init__(self, FIELD_MAP, cc_label_map, tag_browser_categories): 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 = '' - SearchQueryParser.__init__(self, - locations=SearchQueryParser.DEFAULT_LOCATIONS + - [c for c in cc_label_map]) + self.tag_browser_categories = tag_browser_categories + self.all_search_locations = tag_browser_categories.get_search_labels() + SearchQueryParser.__init__(self, self.all_search_locations) self.build_date_relop_dict() self.build_numeric_relop_dict() @@ -379,25 +380,33 @@ class ResultCache(SearchQueryParser): if location in ('tag', 'author', 'format', 'comment'): location += 's' - all = ('title', 'authors', 'publisher', 'tags', 'comments', 'series', - 'formats', 'isbn', 'rating', 'cover', 'ondevice') +# all = ('title', 'authors', 'publisher', 'tags', 'comments', 'series', +# 'formats', 'isbn', 'rating', 'cover', 'ondevice') MAP = {} - for x in all: # get the db columns for the standard searchables - MAP[x] = self.FIELD_MAP[x] + # 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']): +# self.tag_browser_categories[x]['kind'] == 'standard') \ +# or self.tag_browser_categories[x]['kind'] == 'not_cat': + MAP[x] = self.FIELD_MAP[self.tag_browser_categories.get_label(x)] + IS_CUSTOM = [] - for x in range(len(self.FIELD_MAP)): # build a list containing '' the size of FIELD_MAP + for x in range(len(self.FIELD_MAP)): IS_CUSTOM.append('') IS_CUSTOM[self.FIELD_MAP['rating']] = 'rating' # normal and custom ratings columns use the same code - for x in self.custom_column_label_map: # add custom columns to MAP. Put the column's type into IS_CUSTOM - if self.custom_column_label_map[x]['datatype'] != "datetime": - MAP[x] = self.FIELD_MAP[self.custom_column_label_map[x]['num']] - IS_CUSTOM[MAP[x]] = self.custom_column_label_map[x]['datatype'] + + # add custom columns to MAP. Put the column's type into IS_CUSTOM + for x in self.tag_browser_categories.get_custom_fields(): + if self.tag_browser_categories[x]['datatype'] != "datetime": + MAP[x] = self.FIELD_MAP[self.tag_browser_categories[x]['colnum']] + IS_CUSTOM[MAP[x]] = self.tag_browser_categories[x]['datatype'] EXCLUDE_FIELDS = [MAP['rating'], MAP['cover']] SPLITABLE_FIELDS = [MAP['authors'], MAP['tags'], MAP['formats']] - for x in self.custom_column_label_map: - if self.custom_column_label_map[x]['is_multiple']: + for x in self.tag_browser_categories.get_custom_fields(): + if self.tag_browser_categories[x]['is_multiple']: SPLITABLE_FIELDS.append(MAP[x]) try: diff --git a/src/calibre/library/custom_columns.py b/src/calibre/library/custom_columns.py index 36ea49763e..cc7ee7ba7f 100644 --- a/src/calibre/library/custom_columns.py +++ b/src/calibre/library/custom_columns.py @@ -145,11 +145,10 @@ class CustomColumns(object): v = self.custom_column_label_map[k] if v['normalized']: tn = 'custom_column_{0}'.format(v['num']) - self.tag_browser_categories[v['label']] = { - 'table':tn, 'column':'value', - 'type':v['datatype'], 'is_multiple':v['is_multiple'], - 'kind':'custom', 'name':v['name'] - } + 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']) 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 84124b6ce9..eb27ff8bfb 100644 --- a/src/calibre/library/database2.py +++ b/src/calibre/library/database2.py @@ -20,6 +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.schema_upgrades import SchemaUpgrade from calibre.library.caches import ResultCache from calibre.library.custom_columns import CustomColumns @@ -33,11 +34,10 @@ from calibre.customize.ui import run_plugins_on_import from calibre.utils.filenames import ascii_filename from calibre.utils.date import utcnow, now as nowf, utcfromtimestamp -from calibre.utils.ordered_dict import OrderedDict from calibre.utils.config import prefs from calibre.utils.search_query_parser import saved_searches from calibre.ebooks import BOOK_EXTENSIONS, check_ebook_format -from calibre.ebooks.metadata.book import RESERVED_METADATA_FIELDS + if iswindows: import calibre.utils.winshell as winshell @@ -116,6 +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() if not os.path.exists(library_path): os.makedirs(library_path) self.listeners = set([]) @@ -127,36 +128,6 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): if isinstance(self.dbpath, unicode): self.dbpath = self.dbpath.encode(filesystem_encoding) - # Order as has been customary in the tags pane. - tag_browser_categories_items = [ - ('authors', {'table':'authors', 'column':'name', - 'type':'text', 'is_multiple':False, - 'kind':'standard', 'name':_('Authors')}), - ('series', {'table':'series', 'column':'name', - 'type':None, 'is_multiple':False, - 'kind':'standard', 'name':_('Series')}), - ('formats', {'table':None, 'column':None, - 'type':None, 'is_multiple':False, - 'kind':'standard', 'name':_('Formats')}), - ('publisher', {'table':'publishers', 'column':'name', - 'type':'text', 'is_multiple':False, - 'kind':'standard', 'name':_('Publishers')}), - ('rating', {'table':'ratings', 'column':'rating', - 'type':'rating', 'is_multiple':False, - 'kind':'standard', 'name':_('Ratings')}), - ('news', {'table':'news', 'column':'name', - 'type':None, 'is_multiple':False, - 'kind':'standard', 'name':_('News')}), - ('tags', {'table':'tags', 'column':'name', - 'type':'text', 'is_multiple':True, - 'kind':'standard', 'name':_('Tags')}), - ] - self.tag_browser_categories = OrderedDict() - for k,v in tag_browser_categories_items: - if k not in RESERVED_METADATA_FIELDS: - raise ValueError('Tag category [%s] is not a reserved word.' %(k)) - self.tag_browser_categories[k] = v - self.connect() self.is_case_sensitive = not iswindows and not isosx and \ not os.path.exists(self.dbpath.replace('metadata.db', 'MeTAdAtA.dB')) @@ -251,7 +222,8 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): self.conn.commit() self.book_on_device_func = None - self.data = ResultCache(self.FIELD_MAP, self.custom_column_label_map) + self.data = ResultCache(self.FIELD_MAP, self.custom_column_label_map, + self.tag_browser_categories) self.search = self.data.search self.refresh = functools.partial(self.data.refresh, self) self.sort = self.data.sort @@ -671,14 +643,20 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): self.books_list_filter.change([] if not ids else ids) categories = {} + if icon_map is not None and type(icon_map) != TagsIcons: + raise TypeError('icon_map passed to get_categories must be of type TagIcons') #### First, build the standard and custom-column categories #### - for category in self.tag_browser_categories.keys(): - tn = self.tag_browser_categories[category]['table'] + tb_cats = self.tag_browser_categories + for category in tb_cats.keys(): + cat = tb_cats[category] + if cat['kind'] == 'not_cat': + continue + tn = cat['table'] categories[category] = [] #reserve the position in the ordered list if tn is None: # Nothing to do for the moment continue - cn = self.tag_browser_categories[category]['column'] + cn = cat['column'] if ids is None: query = 'SELECT id, {0}, count FROM tag_browser_{1}'.format(cn, tn) else: @@ -692,16 +670,17 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): # icon_map is not None if get_categories is to store an icon and # possibly a tooltip in the tag structure. icon, tooltip = None, '' + label = tb_cats.get_label(category) if icon_map: - if self.tag_browser_categories[category]['kind'] == 'standard': + if cat['kind'] == 'standard': if category in icon_map: - icon = icon_map[category] - elif self.tag_browser_categories[category]['kind'] == 'custom': + icon = icon_map[label] + elif cat['kind'] == 'custom': icon = icon_map[':custom'] icon_map[category] = icon - tooltip = self.custom_column_label_map[category]['name'] + tooltip = self.custom_column_label_map[label]['name'] - datatype = self.tag_browser_categories[category]['type'] + datatype = cat['datatype'] if datatype == 'rating': item_not_zero_func = (lambda x: x[1] > 0 and x[2] > 0) formatter = (lambda x:u'\u2605'*int(round(x/2.))) @@ -711,7 +690,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): formatter = (lambda x: x.replace('|', ',')) else: item_not_zero_func = (lambda x: x[2] > 0) - formatter = (lambda x:x) + formatter = (lambda x:unicode(x)) categories[category] = [Tag(formatter(r[1]), count=r[2], id=r[0], icon=icon, tooltip = tooltip) @@ -750,9 +729,9 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): # remove all user categories from tag_browser_categories. They can # easily come and go. We will add all the existing ones in below. - for k in self.tag_browser_categories.keys(): - if self.tag_browser_categories[k]['kind'] in ['user', 'search']: - del self.tag_browser_categories[k] + for k in tb_cats.keys(): + if tb_cats[k]['kind'] in ['user', 'search']: + del tb_cats[k] # We want to use same node in the user category as in the source # category. To do that, we need to find the original Tag node. There is @@ -771,10 +750,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): # else: do nothing, to not include nodes w zero counts if len(items): cat_name = user_cat+':' # add the ':' to avoid name collision - self.tag_browser_categories[cat_name] = { - 'table':None, 'column':None, - 'type':None, 'is_multiple':False, - 'kind':'user', 'name':user_cat} + tb_cats.add_user_category(field_name=cat_name, name=user_cat) # Not a problem if we accumulate entries in the icon map if icon_map is not None: icon_map[cat_name] = icon_map[':user'] @@ -793,10 +769,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): for srch in saved_searches.names(): items.append(Tag(srch, tooltip=saved_searches.lookup(srch), icon=icon)) if len(items): - self.tag_browser_categories['search'] = { - 'table':None, 'column':None, - 'type':None, 'is_multiple':False, - 'kind':'search', 'name':_('Searches')} + tb_cats.add_user_category(field_name='search', name=_('Searches')) if icon_map is not None: icon_map['search'] = icon_map['search'] categories['search'] = items diff --git a/src/calibre/library/tag_categories.py b/src/calibre/library/tag_categories.py new file mode 100644 index 0000000000..8cd47c44fb --- /dev/null +++ b/src/calibre/library/tag_categories.py @@ -0,0 +1,194 @@ +''' +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':None, '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':None, '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']}), + ('isbn', {'table':None, 'column':None, + 'datatype':None, 'is_multiple':False, + 'kind':'not_cat', 'name':None, + 'search_labels':['isbn']}), + ('pubdate', {'table':None, 'column':None, + 'datatype':None, 'is_multiple':False, + 'kind':'not_cat', 'name':None, + 'search_labels':['pubdate']}), + ('title', {'table':None, 'column':None, + 'datatype':None, '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 + + def __getattr__(self, name): + if name in self.tb_cats_: + return self.tb_cats_[name] + return None + +# def __setattr__(self, name, val): +# dict.__setattr__(self, name, val) + + def __getitem__(self, key): + return self.tb_cats_[key] + +# def __setitem__(self, key, val): +# print 'setitem', key, val +# self.tb_cats_[key] = val + + 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 get_label(self, key): + if 'label' not in self.tb_cats_[key]: + return key + return self.tb_cats_[key]['label'] + + 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 = '#' + field_name + if fn in self.tb_cats_: + raise ValueError('Duplicate custom field [%s]'%(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 diff --git a/src/calibre/utils/search_query_parser.py b/src/calibre/utils/search_query_parser.py index 5fe0a242f8..509adb49d4 100644 --- a/src/calibre/utils/search_query_parser.py +++ b/src/calibre/utils/search_query_parser.py @@ -22,7 +22,6 @@ from calibre.utils.pyparsing import Keyword, Group, Forward, CharsNotIn, Suppres OneOrMore, oneOf, CaselessLiteral, Optional, NoMatch, ParseException from calibre.constants import preferred_encoding from calibre.utils.config import prefs -from calibre.ebooks.metadata.book import RESERVED_METADATA_FIELDS ''' This class manages access to the preference holding the saved search queries. @@ -86,27 +85,6 @@ class SearchQueryParser(object): * `(author:Asimov or author:Hardy) and not tag:read` [search for unread books by Asimov or Hardy] ''' - DEFAULT_LOCATIONS = [ - 'all', - 'author', # compatibility - 'authors', - 'comment', # compatibility - 'comments', - 'cover', - 'date', - 'format', # compatibility - 'formats', - 'isbn', - 'ondevice', - 'pubdate', - 'publisher', - 'search', - 'series', - 'rating', - 'tag', # compatibility - 'tags', - 'title', - ] @staticmethod def run_tests(parser, result, tests): @@ -121,12 +99,7 @@ class SearchQueryParser(object): failed.append(test[0]) return failed - def __init__(self, locations=None, test=False): - for k in self.DEFAULT_LOCATIONS: - if k not in RESERVED_METADATA_FIELDS: - raise ValueError('Search location [%s] is not a reserved word.' %(k)) - if locations is None: - locations = self.DEFAULT_LOCATIONS + def __init__(self, locations, test=False): self._tests_failed = False # Define a token standard_locations = map(lambda x : CaselessLiteral(x)+Suppress(':'), From fb13f2e7f4a058d574ebe7c1013a73657d9babe4 Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Wed, 26 May 2010 12:30:41 +0100 Subject: [PATCH 2/6] Remove some commented-out code --- src/calibre/library/caches.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/calibre/library/caches.py b/src/calibre/library/caches.py index 9e140b4125..9005686fd2 100644 --- a/src/calibre/library/caches.py +++ b/src/calibre/library/caches.py @@ -380,29 +380,26 @@ class ResultCache(SearchQueryParser): if location in ('tag', 'author', 'format', 'comment'): location += 's' -# all = ('title', 'authors', 'publisher', 'tags', 'comments', 'series', -# 'formats', 'isbn', 'rating', 'cover', 'ondevice') MAP = {} # 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']): -# self.tag_browser_categories[x]['kind'] == 'standard') \ -# or self.tag_browser_categories[x]['kind'] == 'not_cat': MAP[x] = self.FIELD_MAP[self.tag_browser_categories.get_label(x)] + # add custom columns to MAP. Put the column's type into IS_CUSTOM IS_CUSTOM = [] for x in range(len(self.FIELD_MAP)): IS_CUSTOM.append('') - IS_CUSTOM[self.FIELD_MAP['rating']] = 'rating' # normal and custom ratings columns use the same code - - # add custom columns to MAP. Put the column's type into IS_CUSTOM + # normal and custom ratings columns use the same code + IS_CUSTOM[self.FIELD_MAP['rating']] = 'rating' for x in self.tag_browser_categories.get_custom_fields(): if self.tag_browser_categories[x]['datatype'] != "datetime": MAP[x] = self.FIELD_MAP[self.tag_browser_categories[x]['colnum']] IS_CUSTOM[MAP[x]] = self.tag_browser_categories[x]['datatype'] + # Some fields not used when matching against contents EXCLUDE_FIELDS = [MAP['rating'], MAP['cover']] SPLITABLE_FIELDS = [MAP['authors'], MAP['tags'], MAP['formats']] for x in self.tag_browser_categories.get_custom_fields(): From 149f9794092aca74ae9948ca90096cf1949ebbb9 Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Wed, 26 May 2010 13:52:13 +0100 Subject: [PATCH 3/6] Misc cleanups --- src/calibre/gui2/dialogs/tag_categories.py | 2 +- src/calibre/gui2/library/models.py | 2 + src/calibre/gui2/tag_view.py | 6 +- src/calibre/library/caches.py | 2 +- src/calibre/library/database2.py | 4 +- src/calibre/library/tag_categories.py | 68 ++++++++++++---------- 6 files changed, 47 insertions(+), 37 deletions(-) diff --git a/src/calibre/gui2/dialogs/tag_categories.py b/src/calibre/gui2/dialogs/tag_categories.py index f49ae4ce83..fdec767d4d 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(cc) + self.category_labels.append(db.tag_browser_categories.get_search_label(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 18af6d8560..6146ff18df 100644 --- a/src/calibre/gui2/library/models.py +++ b/src/calibre/gui2/library/models.py @@ -632,6 +632,8 @@ class BooksModel(QAbstractTableModel): # {{{ return None 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 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 f2729da480..5c4c7d82ac 100644 --- a/src/calibre/gui2/tag_view.py +++ b/src/calibre/gui2/tag_view.py @@ -224,10 +224,14 @@ 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': + tt = _('The lookup/search name is "{0}"').format(r) + else: + tt = '' c = TagTreeItem(parent=self.root_item, data=self.categories[i], category_icon=self.category_icon_map[r], - tooltip=_('The lookup/search name is "{0}"').format(r)) + tooltip=tt) for tag in data[r]: TagTreeItem(parent=c, data=tag, icon_map=self.icon_state_map) diff --git a/src/calibre/library/caches.py b/src/calibre/library/caches.py index 9005686fd2..d403c0e7ae 100644 --- a/src/calibre/library/caches.py +++ b/src/calibre/library/caches.py @@ -386,7 +386,7 @@ class ResultCache(SearchQueryParser): 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_label(x)] + MAP[x] = self.FIELD_MAP[self.tag_browser_categories.get_field_label(x)] # add custom columns to MAP. Put the column's type into IS_CUSTOM IS_CUSTOM = [] diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py index eb27ff8bfb..7da16c4ec6 100644 --- a/src/calibre/library/database2.py +++ b/src/calibre/library/database2.py @@ -670,7 +670,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): # icon_map is not None if get_categories is to store an icon and # possibly a tooltip in the tag structure. icon, tooltip = None, '' - label = tb_cats.get_label(category) + label = tb_cats.get_field_label(category) if icon_map: if cat['kind'] == 'standard': if category in icon_map: @@ -769,7 +769,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): for srch in saved_searches.names(): items.append(Tag(srch, tooltip=saved_searches.lookup(srch), icon=icon)) if len(items): - tb_cats.add_user_category(field_name='search', name=_('Searches')) + tb_cats.add_search_category(field_name='search', name=_('Searches')) if icon_map is not None: icon_map['search'] = icon_map['search'] categories['search'] = items diff --git a/src/calibre/library/tag_categories.py b/src/calibre/library/tag_categories.py index 8cd47c44fb..41477acba8 100644 --- a/src/calibre/library/tag_categories.py +++ b/src/calibre/library/tag_categories.py @@ -88,73 +88,77 @@ class TagsMetadata(dict, DictMixin): ] def __init__(self): - self.tb_cats_ = OrderedDict() + self._tb_cats = OrderedDict() for k,v in self.category_items_: - self.tb_cats_[k] = v - - def __getattr__(self, name): - if name in self.tb_cats_: - return self.tb_cats_[name] - return None - -# def __setattr__(self, name, val): -# dict.__setattr__(self, name, val) + self._tb_cats[k] = v + self._custom_fields = [] + self.custom_field_prefix = '#' def __getitem__(self, key): - return self.tb_cats_[key] + return self._tb_cats[key] -# def __setitem__(self, key, val): -# print 'setitem', key, val -# self.tb_cats_[key] = val + def __setitem__(self, key, val): + raise AttributeError('Assigning to this object is forbidden') def __delitem__(self, key): - del self.tb_cats_[key] + del self._tb_cats[key] def __iter__(self): - for key in self.tb_cats_: + for key in self._tb_cats: yield key def keys(self): - return self.tb_cats_.keys() + return self._tb_cats.keys() def iterkeys(self): - for key in self.tb_cats_: + for key in self._tb_cats: yield key def iteritems(self): - for key in self.tb_cats_: - yield (key, self.tb_cats_[key]) + for key in self._tb_cats: + yield (key, self._tb_cats[key]) - def get_label(self, key): - if 'label' not in 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'] + 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'] + 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 = '#' + field_name - if fn in self.tb_cats_: + fn = self.custom_field_prefix + field_name + if fn in self._tb_cats: raise ValueError('Duplicate custom field [%s]'%(field_name)) - self.tb_cats_[fn] = {'table':table, 'column':column, + 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_: + if field_name in self._tb_cats: raise ValueError('Duplicate user field [%s]'%(field_name)) - self.tb_cats_[field_name] = {'table':None, 'column':None, + 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_: + if field_name in self._tb_cats: raise ValueError('Duplicate user field [%s]'%(field_name)) - self.tb_cats_[field_name] = {'table':None, 'column':None, + self._tb_cats[field_name] = {'table':None, 'column':None, 'datatype':None, 'is_multiple':False, 'kind':'search', 'name':name, 'search_labels':[]} @@ -184,7 +188,7 @@ class TagsMetadata(dict, DictMixin): def get_search_labels(self): s_labels = [] - for v in self.tb_cats_.itervalues(): + 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) From 3d38872b56da7cff99bad9f1cc66c9cd2896c16e Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Wed, 26 May 2010 14:48:53 +0100 Subject: [PATCH 4/6] Fix bareword searches trying to match against non-text types. --- src/calibre/library/caches.py | 7 +++++-- src/calibre/library/tag_categories.py | 16 ++++++++++------ 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/src/calibre/library/caches.py b/src/calibre/library/caches.py index d403c0e7ae..fa73b34b41 100644 --- a/src/calibre/library/caches.py +++ b/src/calibre/library/caches.py @@ -381,12 +381,17 @@ class ResultCache(SearchQueryParser): location += 's' MAP = {} + # Fields not used when matching against text contents. These are + # the non-text fields + EXCLUDE_FIELDS = [] # 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 self.tag_browser_categories[x]['datatype'] != 'text': + EXCLUDE_FIELDS.append(MAP[x]) # add custom columns to MAP. Put the column's type into IS_CUSTOM IS_CUSTOM = [] @@ -399,8 +404,6 @@ class ResultCache(SearchQueryParser): MAP[x] = self.FIELD_MAP[self.tag_browser_categories[x]['colnum']] IS_CUSTOM[MAP[x]] = self.tag_browser_categories[x]['datatype'] - # Some fields not used when matching against contents - EXCLUDE_FIELDS = [MAP['rating'], MAP['cover']] SPLITABLE_FIELDS = [MAP['authors'], MAP['tags'], MAP['formats']] for x in self.tag_browser_categories.get_custom_fields(): if self.tag_browser_categories[x]['is_multiple']: diff --git a/src/calibre/library/tag_categories.py b/src/calibre/library/tag_categories.py index 41477acba8..63327fac45 100644 --- a/src/calibre/library/tag_categories.py +++ b/src/calibre/library/tag_categories.py @@ -36,7 +36,7 @@ class TagsMetadata(dict, DictMixin): 'kind':'standard', 'name':_('Authors'), 'search_labels':['authors', 'author']}), ('series', {'table':'series', 'column':'name', - 'datatype':None, 'is_multiple':False, + 'datatype':'text', 'is_multiple':False, 'kind':'standard', 'name':_('Series'), 'search_labels':['series']}), ('formats', {'table':None, 'column':None, @@ -60,30 +60,34 @@ class TagsMetadata(dict, DictMixin): 'kind':'standard', 'name':_('Tags'), 'search_labels':['tags', 'tag']}), ('comments', {'table':None, 'column':None, - 'datatype':None, 'is_multiple':False, + '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':None, 'is_multiple':False, + 'datatype':'text', 'is_multiple':False, 'kind':'not_cat', 'name':None, 'search_labels':['isbn']}), ('pubdate', {'table':None, 'column':None, - 'datatype':None, 'is_multiple':False, + 'datatype':'datetime', 'is_multiple':False, 'kind':'not_cat', 'name':None, 'search_labels':['pubdate']}), ('title', {'table':None, 'column':None, - 'datatype':None, 'is_multiple':False, + 'datatype':'text', 'is_multiple':False, 'kind':'not_cat', 'name':None, 'search_labels':['title']}), ] # search labels that are not db columns search_items = [ 'all', - 'date', +# 'date', 'search', ] From a124d9c79922407cbdf031a21b2656dee9b79d2f Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Wed, 26 May 2010 16:35:41 +0100 Subject: [PATCH 5/6] 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 From e4619e969cbb0251a518c2107d7ea5c6e5fe03ce Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Wed, 26 May 2010 16:53:35 +0100 Subject: [PATCH 6/6] Correct a renamed parameter --- src/calibre/library/database2.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py index b8f55d76db..03d8bcc53d 100644 --- a/src/calibre/library/database2.py +++ b/src/calibre/library/database2.py @@ -759,7 +759,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): # else: do nothing, to not include nodes w zero counts if len(items): cat_name = user_cat+':' # add the ':' to avoid name collision - tb_cats.add_user_category(field_name=cat_name, name=user_cat) + tb_cats.add_user_category(label=cat_name, name=user_cat) # Not a problem if we accumulate entries in the icon map if icon_map is not None: icon_map[cat_name] = icon_map[':user'] @@ -778,7 +778,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): for srch in saved_searches.names(): items.append(Tag(srch, tooltip=saved_searches.lookup(srch), icon=icon)) if len(items): - tb_cats.add_search_category(field_name='search', name=_('Searches')) + tb_cats.add_search_category(label='search', name=_('Searches')) if icon_map is not None: icon_map['search'] = icon_map['search'] categories['search'] = items