diff --git a/src/calibre/db/backend.py b/src/calibre/db/backend.py index 4f33a917fa..3241493af1 100644 --- a/src/calibre/db/backend.py +++ b/src/calibre/db/backend.py @@ -70,6 +70,10 @@ class DBPrefs(dict): # {{{ self.db = db self.defaults = {} self.disable_setting = False + self.load_from_db() + + def load_from_db(self): + self.clear() for key, val in self.db.conn.get('SELECT key,val FROM preferences'): try: val = self.raw_to_object(val) @@ -136,28 +140,10 @@ class DBPrefs(dict): # {{{ @classmethod def read_serialized(cls, library_path, recreate_prefs=False): - try: - from_filename = os.path.join(library_path, - 'metadata_db_prefs_backup.json') - with open(from_filename, "rb") as f: - d = json.load(f, object_hook=from_json) - if not recreate_prefs: - return d - cls.clear() - cls.db.conn.execute('DELETE FROM preferences') - for k,v in d.iteritems(): - raw = cls.to_raw(v) - cls.db.conn.execute( - 'INSERT INTO preferences (key,val) VALUES (?,?)', (k, raw)) - cls.db.conn.commit() - cls.clear() - cls.update(d) - return d - except: - import traceback - traceback.print_exc() - raise - return None + from_filename = os.path.join(library_path, + 'metadata_db_prefs_backup.json') + with open(from_filename, "rb") as f: + return json.load(f, object_hook=from_json) # }}} # Extra collators {{{ diff --git a/src/calibre/db/cache.py b/src/calibre/db/cache.py index c2d094eef0..ce8ce288ea 100644 --- a/src/calibre/db/cache.py +++ b/src/calibre/db/cache.py @@ -89,7 +89,6 @@ class Cache(object): self.formatter_template_cache = {} self.dirtied_cache = {} self.dirtied_sequence = 0 - self._search_api = Search(self, 'saved_searches', self.field_metadata.get_search_terms()) # Implement locking for all simple read/write API methods # An unlocked version of the method is stored with the name starting @@ -105,6 +104,7 @@ class Cache(object): lock = self.read_lock if ira else self.write_lock setattr(self, name, wrap_simple(lock, func)) + self._search_api = Search(self, 'saved_searches', self.field_metadata.get_search_terms()) self.initialize_dynamic() @write_api @@ -127,7 +127,7 @@ class Cache(object): except: traceback.print_exc() - if len(self._search_api.get_saved_searches().names()): + if len(self._search_api.saved_searches.names()) > 0: self.field_metadata.add_search_category(label='search', name=_('Searches')) self.field_metadata.add_grouped_search_terms( @@ -140,11 +140,6 @@ class Cache(object): if self.dirtied_cache: self.dirtied_sequence = max(self.dirtied_cache.itervalues())+1 - @property - def prefs(self): - 'For internal use only (used by SavedSearchQueries). For thread-safe access to the preferences, use the pref() and set_pref() methods.' - return self.backend.prefs - @write_api def initialize_template_cache(self): self.formatter_template_cache = {} @@ -161,6 +156,8 @@ class Cache(object): def reload_from_db(self, clear_caches=True): if clear_caches: self._clear_caches() + self.backend.prefs.load_from_db() + self._search_api.saved_searches.load_from_db() for field in self.fields.itervalues(): if hasattr(field, 'table'): field.table.read(self.backend) # Reread data from metadata.db @@ -1520,6 +1517,30 @@ class Cache(object): all_paths = {self._field_for('path', book_id).partition('/')[0] for book_id in self._all_book_ids()} self.backend.move_library_to(all_paths, newloc, progress=progress) + @read_api + def saved_search_names(self): + return self._search_api.saved_searches.names() + + @read_api + def saved_search_lookup(self, name): + return self._search_api.saved_searches.lookup(name) + + @write_api + def saved_search_set_all(self, smap): + self._search_api.saved_searches.set_all(smap) + + @write_api + def saved_search_delete(self, name): + self._search_api.saved_searches.delete(name) + + @write_api + def saved_search_add(self, name, val): + self._search_api.saved_searches.add(name, val) + + @write_api + def saved_search_rename(self, old_name, new_name): + self._search_api.saved_searches.rename(old_name, new_name) + # }}} class SortKey(object): # {{{ diff --git a/src/calibre/db/categories.py b/src/calibre/db/categories.py index df6c1402d2..f80bb52c08 100644 --- a/src/calibre/db/categories.py +++ b/src/calibre/db/categories.py @@ -228,11 +228,10 @@ def get_categories(dbcache, sort='name', book_ids=None, icon_map=None): icon = None if icon_map and 'search' in icon_map: icon = icon_map['search'] - ss = dbcache._search_api.get_saved_searches() - for srch in ss.names(): - items.append(Tag(srch, tooltip=ss.lookup(srch), - sort=srch, icon=icon, category='search', - is_editable=False)) + queries = dbcache._search_api.saved_searches.queries + for srch in sorted(queries, key=sort_key): + items.append(Tag(srch, tooltip=queries[srch], sort=srch, icon=icon, + category='search', is_editable=False)) if len(items): categories['search'] = items diff --git a/src/calibre/db/legacy.py b/src/calibre/db/legacy.py index 838ccdfe21..9de6d5ce1f 100644 --- a/src/calibre/db/legacy.py +++ b/src/calibre/db/legacy.py @@ -120,9 +120,6 @@ class LibraryDatabase(object): self.new_api.reload_from_db() self.last_update_check = utcnow() - def get_saved_searches(self): - return self.new_api._search_api.get_saved_searches() - @property def custom_column_num_map(self): return self.backend.custom_column_num_map @@ -887,6 +884,12 @@ for meth in ('get_next_series_num_for', 'has_book', 'author_sort_from_authors'): setattr(LibraryDatabase, meth, MT(getter(meth))) LibraryDatabase.move_library_to = MT(lambda self, newloc, progress=None:self.new_api.move_library_to(newloc, progress=progress)) +LibraryDatabase.saved_search_names = MT(lambda self:self.new_api.saved_search_names()) +LibraryDatabase.saved_search_lookup = MT(lambda self, x:self.new_api.saved_search_lookup(x)) +LibraryDatabase.saved_search_set_all = MT(lambda self, smap:self.new_api.saved_search_set_all(smap)) +LibraryDatabase.saved_search_delete = MT(lambda self, x:self.new_api.saved_search_delete(x)) +LibraryDatabase.saved_search_add = MT(lambda self, x, y:self.new_api.saved_search_add(x, y)) +LibraryDatabase.saved_search_rename = MT(lambda self, x, y:self.new_api.saved_search_rename(x, y)) # Cleaning is not required anymore LibraryDatabase.clean = LibraryDatabase.clean_custom = MT(lambda self:None) LibraryDatabase.clean_standard_field = MT(lambda self, field, commit=False:None) diff --git a/src/calibre/db/search.py b/src/calibre/db/search.py index 013678e3b3..f00b7102e7 100644 --- a/src/calibre/db/search.py +++ b/src/calibre/db/search.py @@ -7,15 +7,16 @@ __license__ = 'GPL v3' __copyright__ = '2013, Kovid Goyal ' __docformat__ = 'restructuredtext en' -import re +import re, weakref from functools import partial from datetime import timedelta +from calibre.constants import preferred_encoding from calibre.utils.config_base import prefs from calibre.utils.date import parse_date, UNDEFINED_DATE, now -from calibre.utils.icu import primary_find +from calibre.utils.icu import primary_find, sort_key from calibre.utils.localization import lang_map, canonicalize_lang -from calibre.utils.search_query_parser import SearchQueryParser, ParseException, SavedSearchQueries +from calibre.utils.search_query_parser import SearchQueryParser, ParseException CONTAINS_MATCH = 0 EQUALS_MATCH = 1 @@ -388,11 +389,72 @@ class KeyPairSearch(object): # {{{ # }}} +class SavedSearchQueries(object): # {{{ + queries = {} + opt_name = '' + + def __init__(self, db, _opt_name): + self.opt_name = _opt_name + try: + self._db = weakref.ref(db) + except TypeError: + # db could be None + self._db = lambda : None + self.load_from_db() + + def load_from_db(self): + db = self.db + if db is not None: + self.queries = db._pref(self.opt_name, default={}) + else: + self.queries = {} + + @property + def db(self): + return self._db() + + def force_unicode(self, x): + if not isinstance(x, unicode): + x = x.decode(preferred_encoding, 'replace') + return x + + def add(self, name, value): + db = self.db + if db is not None: + self.queries[self.force_unicode(name)] = self.force_unicode(value).strip() + db._set_pref(self.opt_name, self.queries) + + def lookup(self, name): + return self.queries.get(self.force_unicode(name), None) + + def delete(self, name): + db = self.db + if db is not None: + self.queries.pop(self.force_unicode(name), False) + db._set_pref(self.opt_name, self.queries) + + def rename(self, old_name, new_name): + db = self.db + if db is not None: + self.queries[self.force_unicode(new_name)] = self.queries.get(self.force_unicode(old_name), None) + self.queries.pop(self.force_unicode(old_name), False) + db._set_pref(self.opt_name, self.queries) + + def set_all(self, smap): + db = self.db + if db is not None: + self.queries = smap + db._set_pref(self.opt_name, smap) + + def names(self): + return sorted(self.queries.iterkeys(), key=sort_key) +# }}} + class Parser(SearchQueryParser): def __init__(self, dbcache, all_book_ids, gst, date_search, num_search, bool_search, keypair_search, limit_search_columns, limit_search_columns_to, - locations, virtual_fields, get_saved_searches): + locations, virtual_fields, lookup_saved_search): self.dbcache, self.all_book_ids = dbcache, all_book_ids self.all_search_locations = frozenset(locations) self.grouped_search_terms = gst @@ -403,7 +465,7 @@ class Parser(SearchQueryParser): self.virtual_fields = virtual_fields or {} if 'marked' not in self.virtual_fields: self.virtual_fields['marked'] = self - super(Parser, self).__init__(locations, optimize=True, get_saved_searches=get_saved_searches) + super(Parser, self).__init__(locations, optimize=True, lookup_saved_search=lookup_saved_search) @property def field_metadata(self): @@ -693,11 +755,11 @@ class Search(object): self.keypair_search, prefs['limit_search_columns'], prefs['limit_search_columns_to'], self.all_search_locations, - virtual_fields, self.get_saved_searches) + virtual_fields, self.saved_searches.lookup) try: ret = sqp.parse(q) finally: - sqp.dbcache = sqp.get_saved_searches = None + sqp.dbcache = sqp.lookup_saved_search = None return ret diff --git a/src/calibre/db/tests/legacy.py b/src/calibre/db/tests/legacy.py index 3e1a1457b3..0c4efb9f6e 100644 --- a/src/calibre/db/tests/legacy.py +++ b/src/calibre/db/tests/legacy.py @@ -751,3 +751,24 @@ class LegacyTest(BaseTest): db.close() # }}} + + def test_legacy_saved_search(self): # {{{ + ' Test legacy saved search API ' + db, ndb = self.init_old(), self.init_legacy() + run_funcs(self, db, ndb, ( + ('saved_search_set_all', {'one':'a', 'two':'b'}), + ('saved_search_names',), + ('saved_search_lookup', 'one'), + ('saved_search_lookup', 'two'), + ('saved_search_lookup', 'xxx'), + ('saved_search_rename', 'one', '1'), + ('saved_search_names',), + ('saved_search_lookup', '1'), + ('saved_search_delete', '1'), + ('saved_search_names',), + ('saved_search_add', 'n', 'm'), + ('saved_search_names',), + ('saved_search_lookup', 'n'), + )) + # }}} + diff --git a/src/calibre/gui2/dialogs/saved_search_editor.py b/src/calibre/gui2/dialogs/saved_search_editor.py index 669771e46e..40cd16c41d 100644 --- a/src/calibre/gui2/dialogs/saved_search_editor.py +++ b/src/calibre/gui2/dialogs/saved_search_editor.py @@ -14,7 +14,8 @@ from calibre.gui2.dialogs.confirm_delete import confirm class SavedSearchEditor(QDialog, Ui_SavedSearchEditor): def __init__(self, parent, initial_search=None): - from calibre.gui2.ui import saved_searches + from calibre.gui2.ui import get_gui + db = get_gui().current_db QDialog.__init__(self, parent) Ui_SavedSearchEditor.__init__(self) self.setupUi(self) @@ -27,9 +28,9 @@ class SavedSearchEditor(QDialog, Ui_SavedSearchEditor): self.current_search_name = None self.searches = {} - for name in saved_searches().names(): - self.searches[name] = saved_searches().lookup(name) - self.search_names = set([icu_lower(n) for n in saved_searches().names()]) + for name in db.saved_search_names(): + self.searches[name] = db.saved_search_lookup(name) + self.search_names = set([icu_lower(n) for n in db.saved_search_names()]) self.populate_search_list() if initial_search is not None and initial_search in self.searches: @@ -98,11 +99,10 @@ class SavedSearchEditor(QDialog, Ui_SavedSearchEditor): self.search_text.setPlainText('') def accept(self): - from calibre.gui2.ui import saved_searches + from calibre.gui2.ui import get_gui + db = get_gui().current_db if self.current_search_name: self.searches[self.current_search_name] = unicode(self.search_text.toPlainText()) - for name in saved_searches().names(): - saved_searches().delete(name) - for name in self.searches: - saved_searches().add(name, self.searches[name]) + ss = {name:self.searches[name] for name in self.searches} + db.saved_search_set_all(ss) QDialog.accept(self) diff --git a/src/calibre/gui2/search_box.py b/src/calibre/gui2/search_box.py index b10fdd40ef..edd05168f4 100644 --- a/src/calibre/gui2/search_box.py +++ b/src/calibre/gui2/search_box.py @@ -308,28 +308,35 @@ class SavedSearchBox(QComboBox): # {{{ self.saved_search_selected(self.currentText()) def saved_search_selected(self, qname): - from calibre.gui2.ui import saved_searches + from calibre.gui2.ui import get_gui + db = get_gui().current_db qname = unicode(qname) if qname is None or not qname.strip(): self.search_box.clear() return - if not saved_searches().lookup(qname): + if not db.saved_search_lookup(qname): self.search_box.clear() self.setEditText(qname) return self.search_box.set_search_string(u'search:"%s"' % qname, emit_changed=False) self.setEditText(qname) - self.setToolTip(saved_searches().lookup(qname)) + self.setToolTip(db.saved_search_lookup(qname)) def initialize_saved_search_names(self): - from calibre.gui2.ui import saved_searches - qnames = saved_searches().names() - self.addItems(qnames) + from calibre.gui2.ui import get_gui + gui = get_gui() + try: + names = gui.current_db.saved_search_names() + except AttributeError: + # Happens during gui initialization + names = [] + self.addItems(names) self.setCurrentIndex(-1) # SIGNALed from the main UI def save_search_button_clicked(self): - from calibre.gui2.ui import saved_searches + from calibre.gui2.ui import get_gui + db = get_gui().current_db name = unicode(self.currentText()) if not name.strip(): name = unicode(self.search_box.text()).replace('"', '') @@ -337,8 +344,8 @@ class SavedSearchBox(QComboBox): # {{{ error_dialog(self, _('Create saved search'), _('There is no search to save'), show=True) return - saved_searches().delete(name) - saved_searches().add(name, unicode(self.search_box.text())) + db.saved_search_delete(name) + db.saved_search_add(name, unicode(self.search_box.text())) # now go through an initialization cycle to ensure that the combobox has # the new search in it, that it is selected, and that the search box # references the new search instead of the text in the search. @@ -348,7 +355,8 @@ class SavedSearchBox(QComboBox): # {{{ self.changed.emit() def delete_current_search(self): - from calibre.gui2.ui import saved_searches + from calibre.gui2.ui import get_gui + db = get_gui().current_db idx = self.currentIndex() if idx <= 0: error_dialog(self, _('Delete current search'), @@ -358,21 +366,22 @@ class SavedSearchBox(QComboBox): # {{{ 'permanently deleted. Are you sure?') +'

', 'saved_search_delete', self): return - ss = saved_searches().lookup(unicode(self.currentText())) + ss = db.saved_search_lookup(unicode(self.currentText())) if ss is None: return - saved_searches().delete(unicode(self.currentText())) + db.saved_search_delete(unicode(self.currentText())) self.clear() self.search_box.clear() self.changed.emit() # SIGNALed from the main UI def copy_search_button_clicked(self): - from calibre.gui2.ui import saved_searches + from calibre.gui2.ui import get_gui + db = get_gui().current_db idx = self.currentIndex() if idx < 0: return - self.search_box.set_search_string(saved_searches().lookup(unicode(self.currentText()))) + self.search_box.set_search_string(db.saved_search_lookup(unicode(self.currentText()))) # }}} diff --git a/src/calibre/gui2/search_restriction_mixin.py b/src/calibre/gui2/search_restriction_mixin.py index 71e0f2f392..ceed4f1928 100644 --- a/src/calibre/gui2/search_restriction_mixin.py +++ b/src/calibre/gui2/search_restriction_mixin.py @@ -178,7 +178,7 @@ class CreateVirtualLibrary(QDialog): # {{{ self.resize(self.sizeHint()+QSize(150, 25)) def search_text_changed(self, txt): - from calibre.gui2.ui import saved_searches + db = self.gui.current_db searches = [_('Saved searches recognized in the expression:')] txt = unicode(txt) while txt: @@ -201,9 +201,9 @@ class CreateVirtualLibrary(QDialog): # {{{ search_name = possible_search[0] if search_name.startswith('='): search_name = search_name[1:] - if search_name in saved_searches().names(): + if search_name in db.saved_search_names(): searches.append(search_name + '=' + - saved_searches().lookup(search_name)) + db.saved_search_lookup(search_name)) else: txt = '' else: @@ -234,18 +234,17 @@ class CreateVirtualLibrary(QDialog): # {{{ self.vl_text.setText(self.original_search) def link_activated(self, url): - from calibre.gui2.ui import saved_searches db = self.gui.current_db f, txt = unicode(url).partition('.')[0::2] if f == 'search': - names = saved_searches().names() + names = db.saved_search_names() else: names = getattr(db, 'all_%s_names'%f)() d = SelectNames(names, txt, parent=self) if d.exec_() == d.Accepted: prefix = f+'s' if f in {'tag', 'author'} else f if f == 'search': - search = ['(%s)'%(saved_searches().lookup(x)) for x in d.names] + search = ['(%s)'%(db.saved_search_lookup(x)) for x in d.names] else: search = ['%s:"=%s"'%(prefix, x.replace('"', '\\"')) for x in d.names] if search: @@ -476,7 +475,7 @@ class SearchRestrictionMixin(object): return name[0:MAX_VIRTUAL_LIBRARY_NAME_LENGTH].strip() def build_search_restriction_list(self): - from calibre.gui2.ui import saved_searches + from calibre.gui2.ui import get_gui m = self.ar_menu m.clear() @@ -508,7 +507,7 @@ class SearchRestrictionMixin(object): add_action(current_restriction_text, 2) dex += 1 - for n in sorted(saved_searches().names(), key=sort_key): + for n in sorted(get_gui().current_db.saved_search_names(), key=sort_key): add_action(n, dex) dex += 1 diff --git a/src/calibre/gui2/tag_browser/model.py b/src/calibre/gui2/tag_browser/model.py index 7dba7cfe7d..1b291930fd 100644 --- a/src/calibre/gui2/tag_browser/model.py +++ b/src/calibre/gui2/tag_browser/model.py @@ -878,7 +878,7 @@ class TagsModel(QAbstractItemModel): # {{{ traceback.print_exc() self.db.data.change_search_locations(self.db.field_metadata.get_search_terms()) - if len(self.db.get_saved_searches().names()): + if len(self.db.saved_search_names()): tb_cats.add_search_category(label='search', name=_('Searches')) if self.filter_categories_by: @@ -1004,11 +1004,11 @@ class TagsModel(QAbstractItemModel): # {{{ _('Author names cannot contain & characters.')).exec_() return False if key == 'search': - if val in self.db.get_saved_searches().names(): + if val in self.db.saved_search_names(): error_dialog(self.gui_parent, _('Duplicate search name'), _('The saved search name %s is already used.')%val).exec_() return False - self.db.get_saved_searches().rename(unicode(item.data(role).toString()), val) + self.db.saved_search_rename(unicode(item.data(role).toString()), val) item.tag.name = val self.search_item_renamed.emit() # Does a refresh else: diff --git a/src/calibre/gui2/tag_browser/view.py b/src/calibre/gui2/tag_browser/view.py index d28b7ca848..a48cb502e4 100644 --- a/src/calibre/gui2/tag_browser/view.py +++ b/src/calibre/gui2/tag_browser/view.py @@ -354,8 +354,7 @@ class TagsView(QTreeView): # {{{ self.delete_user_category.emit(key) return if action == 'delete_search': - from calibre.gui2.ui import saved_searches - saved_searches().delete(key) + self.model().db.saved_search_delete(key) self.rebuild_saved_searches.emit() return if action == 'delete_item_from_user_category': diff --git a/src/calibre/gui2/ui.py b/src/calibre/gui2/ui.py index 05f36c2709..80aa66601b 100644 --- a/src/calibre/gui2/ui.py +++ b/src/calibre/gui2/ui.py @@ -98,16 +98,6 @@ _gui = None def get_gui(): return _gui -def saved_searches(): - 'Return the saved searches defined in the currently open library' - try: - return _gui.library_view.model().db.get_saved_searches() - except AttributeError: - # Happens during initialization of the gui - from calibre.utils.search_query_parser import saved_searches - return saved_searches() - - class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{ TagBrowserMixin, CoverFlowMixin, LibraryViewMixin, SearchBoxMixin, SavedSearchBoxMixin, SearchRestrictionMixin, LayoutMixin, UpdateMixin, @@ -312,10 +302,10 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{ ####################### Search boxes ######################## SearchRestrictionMixin.__init__(self) SavedSearchBoxMixin.__init__(self) - SearchBoxMixin.__init__(self) ####################### Library view ######################## LibraryViewMixin.__init__(self, db) + SearchBoxMixin.__init__(self) # Requires current_db if show_gui: self.show() diff --git a/src/calibre/library/cli.py b/src/calibre/library/cli.py index 7e7a234724..2978a4e169 100644 --- a/src/calibre/library/cli.py +++ b/src/calibre/library/cli.py @@ -1029,11 +1029,10 @@ def command_saved_searches(args, dbpath): prints(_('Error: You must specify an action (add|remove|list)'), file=sys.stderr) return 1 db = get_db(dbpath, opts) - ss = db.get_saved_searches() if args[0] == 'list': - for name in ss.names(): + for name in db.saved_search_names(): prints(_('Name:'), name) - prints(_('Search string:'), ss.lookup(name)) + prints(_('Search string:'), db.saved_search_lookup(name)) print elif args[0] == 'add': if len(args) < 3: @@ -1041,7 +1040,7 @@ def command_saved_searches(args, dbpath): print prints(_('Error: You must specify a name and a search string'), file=sys.stderr) return 1 - ss.add(args[1], args[2]) + db.saved_search_add(args[1], args[2]) prints(args[1], _('added')) elif args[0] == 'remove': if len(args) < 2: @@ -1049,7 +1048,7 @@ def command_saved_searches(args, dbpath): print prints(_('Error: You must specify a name'), file=sys.stderr) return 1 - ss.delete(args[1]) + db.saved_search_delete(args[1]) prints(args[1], _('removed')) else: parser.print_help() diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py index 7942c69574..749be044d2 100644 --- a/src/calibre/library/database2.py +++ b/src/calibre/library/database2.py @@ -538,8 +538,23 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): if self.user_version == 0: self.user_version = 1 - def get_saved_searches(self): - return saved_searches() + def saved_search_names(self): + return saved_searches().names() + + def saved_search_rename(self, old_name, new_name): + saved_searches().rename(old_name, new_name) + + def saved_search_lookup(self, name): + return saved_searches().lookup(name) + + def saved_search_add(self, name, val): + saved_searches().add(name, val) + + def saved_search_delete(self, name): + saved_searches().delete(name) + + def saved_search_set_all(self, smap): + saved_searches().set_all(smap) def last_modified(self): ''' Return last modified time as a UTC datetime object''' diff --git a/src/calibre/library/server/base.py b/src/calibre/library/server/base.py index a677b991f9..f8608bd2d0 100644 --- a/src/calibre/library/server/base.py +++ b/src/calibre/library/server/base.py @@ -209,7 +209,7 @@ class LibraryServer(ContentServer, MobileServer, XMLServer, OPDSServer, Cache, if sr: if sr in virt_libs: sr = virt_libs[sr] - elif sr not in self.db.get_saved_searches().names(): + elif sr not in self.db.saved_search_names(): prints('WARNING: Content server: search restriction ', sr, ' does not exist') sr = '' diff --git a/src/calibre/utils/search_query_parser.py b/src/calibre/utils/search_query_parser.py index dc2a7b51b8..4406785bb1 100644 --- a/src/calibre/utils/search_query_parser.py +++ b/src/calibre/utils/search_query_parser.py @@ -76,6 +76,11 @@ class SavedSearchQueries(object): self.queries.pop(self.force_unicode(old_name), False) db.prefs[self.opt_name] = self.queries + def set_all(self, smap): + db = self.db + if db is not None: + self.queries = db.prefs[self.opt_name] = smap + def names(self): return sorted(self.queries.keys(),key=sort_key) @@ -93,6 +98,9 @@ def saved_searches(): global ss return ss +def global_lookup_saved_search(name): + return ss.lookup(name) + ''' Parse a search expression into a series of potentially recursive operations. @@ -292,10 +300,10 @@ class SearchQueryParser(object): failed.append(test[0]) return failed - def __init__(self, locations, test=False, optimize=False, get_saved_searches=None): + def __init__(self, locations, test=False, optimize=False, lookup_saved_search=None): self.sqp_initialize(locations, test=test, optimize=optimize) self.parser = Parser() - self.get_saved_searches = saved_searches if get_saved_searches is None else get_saved_searches + self.lookup_saved_search = global_lookup_saved_search if lookup_saved_search is None else lookup_saved_search def sqp_change_locations(self, locations): self.sqp_initialize(locations, optimize=self.optimize) @@ -368,7 +376,7 @@ class SearchQueryParser(object): raise ParseException(_('Recursive saved search: {0}').format(query)) if self.recurse_level > 5: self.searches_seen.add(query) - return self._parse(self.get_saved_searches().lookup(query), candidates) + return self._parse(self.lookup_saved_search(query), candidates) except ParseException as e: raise e except: # convert all exceptions (e.g., missing key) to a parse error