From 3d1b02d10fb835fca6ee62c2d00e9ceb5d0ca348 Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Tue, 1 Mar 2011 13:37:10 +0000 Subject: [PATCH] Make searching on indentifiers work --- src/calibre/library/caches.py | 72 +++++++++++++++++++++------ src/calibre/library/field_metadata.py | 53 +++++--------------- 2 files changed, 70 insertions(+), 55 deletions(-) diff --git a/src/calibre/library/caches.py b/src/calibre/library/caches.py index 0335c1d280..6b8f7c34f2 100644 --- a/src/calibre/library/caches.py +++ b/src/calibre/library/caches.py @@ -418,9 +418,9 @@ class ResultCache(SearchQueryParser): # {{{ return matches def get_user_category_matches(self, location, query, candidates): - res = set([]) + matches = set([]) if self.db_prefs is None or len(query) < 2: - return res + return matches user_cats = self.db_prefs.get('user_categories', []) c = set(candidates) @@ -435,10 +435,56 @@ class ResultCache(SearchQueryParser): # {{{ for (item, category, ign) in user_cats[key]: s = self.get_matches(category, '=' + item, candidates=c) c -= s - res |= s + matches |= s if query == 'false': - return candidates - res - return res + return candidates - matches + return matches + + def get_keypair_matches(self, location, query, candidates): + matches = set([]) + if query.find(':') >= 0: + q = [q.strip() for q in query.split(':')] + if len(q) != 2: + raise ParseException(query, len(query), + 'Invalid query format for colon-separated search', self) + (keyq, valq) = q + keyq_mkind, keyq = self._matchkind(keyq) + valq_mkind, valq = self._matchkind(valq) + else: + keyq = keyq_mkind = '' + valq_mkind, valq = self._matchkind(query) + + loc = self.field_metadata[location]['rec_index'] + for id_ in candidates: + item = self._data[id_] + if item is None or item[loc] is None: + continue + pairs = [p.strip() for p in item[loc].split(',')] + for pair in pairs: + parts = pair.split(':') + if len(parts) != 2: + continue + k = parts[:1] + v = parts[1:] + if keyq and not _match(keyq, k, keyq_mkind): + continue + if valq and not _match(valq, v, valq_mkind): + continue + matches.add(id_) + return matches + + def _matchkind(self, query): + matchkind = CONTAINS_MATCH + if (len(query) > 1): + if query.startswith('\\'): + query = query[1:] + elif query.startswith('='): + matchkind = EQUALS_MATCH + query = query[1:] + elif query.startswith('~'): + matchkind = REGEXP_MATCH + query = query[1:] + return matchkind, query def get_matches(self, location, query, candidates=None, allow_recursion=True): @@ -504,22 +550,18 @@ class ResultCache(SearchQueryParser): # {{{ len(item[loc].split(ms)) if item[loc] is not None else 0 return self.get_numeric_matches(location, query[1:], candidates, val_func=vf) + # special case: identifiers. isbn is a special case within the case + if location == 'identifiers': + return self.get_keypair_matches(location, query, candidates) + if location == 'isbn': + return self.get_keypair_matches('identifiers', '=isbn:'+query, candidates) # check for user categories if len(location) >= 2 and location.startswith('@'): return self.get_user_category_matches(location[1:], query.lower(), candidates) # everything else, or 'all' matches - matchkind = CONTAINS_MATCH - if (len(query) > 1): - if query.startswith('\\'): - query = query[1:] - elif query.startswith('='): - matchkind = EQUALS_MATCH - query = query[1:] - elif query.startswith('~'): - matchkind = REGEXP_MATCH - query = query[1:] + matchkind, query = self._matchkind(query) if matchkind != REGEXP_MATCH: # leave case in regexps because it can be significant e.g. \S \W \D query = icu_lower(query) diff --git a/src/calibre/library/field_metadata.py b/src/calibre/library/field_metadata.py index b0d604dc57..533322f581 100644 --- a/src/calibre/library/field_metadata.py +++ b/src/calibre/library/field_metadata.py @@ -119,15 +119,6 @@ class FieldMetadata(dict): 'search_terms':['formats', 'format'], 'is_custom':False, 'is_category':True}), - ('identifiers', {'table':None, - 'column':None, - 'datatype':'text', - 'is_multiple':',', - 'kind':'field', - 'name':_('Identifiers'), - 'search_terms':['identifiers', 'identifier'], - 'is_custom':False, - 'is_category':True}), ('publisher', {'table':'publishers', 'column':'name', 'link_column':'publisher', @@ -171,6 +162,15 @@ class FieldMetadata(dict): 'search_terms':['tags', 'tag'], 'is_custom':False, 'is_category':True}), + ('identifiers', {'table':None, + 'column':None, + 'datatype':'text', + 'is_multiple':',', + 'kind':'field', + 'name':_('Identifiers'), + 'search_terms':['identifiers', 'identifier', 'isbn'], + 'is_custom':False, + 'is_category':True}), ('author_sort',{'table':None, 'column':None, 'datatype':'text', @@ -206,15 +206,6 @@ class FieldMetadata(dict): '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', @@ -224,22 +215,13 @@ class FieldMetadata(dict): 'search_terms':[], 'is_custom':False, 'is_category':False}), - ('isbn', {'table':None, + ('last_modified', {'table':None, 'column':None, - 'datatype':'text', + 'datatype':'datetime', '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':[], + 'name':_('Date'), + 'search_terms':['last_modified'], 'is_custom':False, 'is_category':False}), ('ondevice', {'table':None, @@ -305,15 +287,6 @@ class FieldMetadata(dict): 'search_terms':['date'], 'is_custom':False, 'is_category':False}), - ('last_modified', {'table':None, - 'column':None, - 'datatype':'datetime', - 'is_multiple':None, - 'kind':'field', - 'name':_('Date'), - 'search_terms':['last_modified'], - 'is_custom':False, - 'is_category':False}), ('title', {'table':None, 'column':None, 'datatype':'text',