From 68f20774fc41032d72b8e7d62e9a9d659c23e784 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 8 Apr 2014 10:19:13 +0530 Subject: [PATCH] Allow searching for books by id and uuid using the id: and uuid: prefixes Also create a nicer API for refreshing the set of search prefixes --- src/calibre/db/cache.py | 6 +++++- src/calibre/db/search.py | 16 +++++++++++----- src/calibre/db/tests/reading.py | 11 +++++++++-- src/calibre/gui2/tag_browser/model.py | 2 +- src/calibre/gui2/tag_browser/ui.py | 2 +- src/calibre/library/field_metadata.py | 4 ++-- 6 files changed, 29 insertions(+), 12 deletions(-) diff --git a/src/calibre/db/cache.py b/src/calibre/db/cache.py index 54bfe9c012..5cb4529649 100644 --- a/src/calibre/db/cache.py +++ b/src/calibre/db/cache.py @@ -157,7 +157,7 @@ class Cache(object): self.field_metadata.add_grouped_search_terms( self._pref('grouped_search_terms', {})) - self._search_api.change_locations(self.field_metadata.get_search_terms()) + self._refresh_search_locations() self.dirtied_cache = {x:i for i, (x,) in enumerate( self.backend.execute('SELECT book FROM metadata_dirtied'))} @@ -1758,6 +1758,10 @@ class Cache(object): def change_search_locations(self, newlocs): self._search_api.change_locations(newlocs) + @write_api + def refresh_search_locations(self): + self._search_api.change_locations(self.field_metadata.get_search_terms()) + @write_api def dump_and_restore(self, callback=None, sql=None): return self.backend.dump_and_restore(callback=callback, sql=sql) diff --git a/src/calibre/db/search.py b/src/calibre/db/search.py index 448b0f896d..c4f5ed03db 100644 --- a/src/calibre/db/search.py +++ b/src/calibre/db/search.py @@ -562,10 +562,16 @@ class Parser(SearchQueryParser): # {{{ if (dt in ('rating', 'int', 'float') or (dt == 'composite' and fm['display'].get('composite_sort', '') == 'number')): - field = self.dbcache.fields[location] + if location == 'id': + is_many = False + def fi(default_value=None): + for qid in candidates: + yield qid, {qid} + else: + field = self.dbcache.fields[location] + fi, is_many = partial(self.field_iter, location, candidates), field.is_many return self.num_search( - icu_lower(query), partial(self.field_iter, location, candidates), - location, dt, candidates, is_many=field.is_many) + icu_lower(query), fi, location, dt, candidates, is_many=is_many) # take care of the 'count' operator for is_multiples if (fm['is_multiple'] and @@ -602,8 +608,8 @@ class Parser(SearchQueryParser): # {{{ for x, fm in self.field_metadata.iteritems(): if x.startswith('@'): continue - if fm['search_terms'] and x != 'series_sort': - if x not in self.virtual_fields: + if fm['search_terms'] and x not in {'series_sort', 'id'}: + if x not in self.virtual_fields and x != 'uuid': # We dont search virtual fields because if we do, search # caching will not be used all_locs.add(x) diff --git a/src/calibre/db/tests/reading.py b/src/calibre/db/tests/reading.py index 34706a6597..d3f79433d1 100644 --- a/src/calibre/db/tests/reading.py +++ b/src/calibre/db/tests/reading.py @@ -274,7 +274,7 @@ class ReadingTest(BaseTest): 'rating:false', 'rating:>4', 'tags:#<2', 'tags:#>7', 'cover:false', 'cover:true', '#float:>11', '#float:<1k', '#float:10.01', '#float:false', 'series_index:1', - 'series_index:<3', 'id:1', 'id:>2', + 'series_index:<3', # Bool tests '#yesno:true', '#yesno:false', '#yesno:yes', '#yesno:no', @@ -289,7 +289,7 @@ class ReadingTest(BaseTest): # Text tests 'title:="Title One"', 'title:~title', '#enum:=one', '#enum:tw', '#enum:false', '#enum:true', 'series:one', 'tags:one', 'tags:true', - 'tags:false', '2', 'one', '20.02', '"publisher one"', + 'tags:false', 'uuid:2', 'one', '20.02', '"publisher one"', '"my comments one"', # User categories @@ -312,6 +312,13 @@ class ReadingTest(BaseTest): 'Old result: %r != New result: %r for search: %s'%( ans, nr, query)) + # Test searching by id, which was introduced in the new backend + self.assertEqual(cache.search('id:1', ''), {1}) + self.assertEqual(cache.search('id:>1', ''), {2, 3}) + + # Note that the old db searched uuid for un-prefixed searches, the new + # db does not, for performance + # }}} def test_get_categories(self): # {{{ diff --git a/src/calibre/gui2/tag_browser/model.py b/src/calibre/gui2/tag_browser/model.py index 60081fd5b5..32cd2ca523 100644 --- a/src/calibre/gui2/tag_browser/model.py +++ b/src/calibre/gui2/tag_browser/model.py @@ -876,7 +876,7 @@ class TagsModel(QAbstractItemModel): # {{{ tb_cats.add_user_category(label=u'@' + cat, name=cat) except ValueError: traceback.print_exc() - self.db.data.change_search_locations(self.db.field_metadata.get_search_terms()) + self.db.new_api.refresh_search_locations() if len(self.db.saved_search_names()): tb_cats.add_search_category(label='search', name=_('Searches')) diff --git a/src/calibre/gui2/tag_browser/ui.py b/src/calibre/gui2/tag_browser/ui.py index 792a27cfb1..80b4252315 100644 --- a/src/calibre/gui2/tag_browser/ui.py +++ b/src/calibre/gui2/tag_browser/ui.py @@ -111,7 +111,7 @@ class TagBrowserMixin(object): # {{{ db.field_metadata.remove_user_categories() for k in d.categories: db.field_metadata.add_user_category('@' + k, k) - db.data.change_search_locations(db.field_metadata.get_search_terms()) + db.new_api.refresh_search_locations() self.tags_view.recount() def do_delete_user_category(self, category_name): diff --git a/src/calibre/library/field_metadata.py b/src/calibre/library/field_metadata.py index e6a448aab4..de006f0d13 100644 --- a/src/calibre/library/field_metadata.py +++ b/src/calibre/library/field_metadata.py @@ -209,7 +209,7 @@ def _builtin_field_metadata(): 'is_multiple':{}, 'kind':'field', 'name':None, - 'search_terms':[], + 'search_terms':['id'], 'is_custom':False, 'is_category':False, 'is_csp': False}), @@ -329,7 +329,7 @@ def _builtin_field_metadata(): 'is_multiple':{}, 'kind':'field', 'name':None, - 'search_terms':[], + 'search_terms':['uuid'], 'is_custom':False, 'is_category':False, 'is_csp': False}),