Merge branch 'kovidgoyal/master'

This commit is contained in:
Charles Haley 2013-07-19 14:02:11 +02:00
commit 34aa6e68b2
16 changed files with 213 additions and 102 deletions

View File

@ -70,6 +70,10 @@ class DBPrefs(dict): # {{{
self.db = db self.db = db
self.defaults = {} self.defaults = {}
self.disable_setting = False 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'): for key, val in self.db.conn.get('SELECT key,val FROM preferences'):
try: try:
val = self.raw_to_object(val) val = self.raw_to_object(val)
@ -136,28 +140,10 @@ class DBPrefs(dict): # {{{
@classmethod @classmethod
def read_serialized(cls, library_path, recreate_prefs=False): def read_serialized(cls, library_path, recreate_prefs=False):
try: from_filename = os.path.join(library_path,
from_filename = os.path.join(library_path, 'metadata_db_prefs_backup.json')
'metadata_db_prefs_backup.json') with open(from_filename, "rb") as f:
with open(from_filename, "rb") as f: return json.load(f, object_hook=from_json)
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
# }}} # }}}
# Extra collators {{{ # Extra collators {{{

View File

@ -89,7 +89,6 @@ class Cache(object):
self.formatter_template_cache = {} self.formatter_template_cache = {}
self.dirtied_cache = {} self.dirtied_cache = {}
self.dirtied_sequence = 0 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 # Implement locking for all simple read/write API methods
# An unlocked version of the method is stored with the name starting # 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 lock = self.read_lock if ira else self.write_lock
setattr(self, name, wrap_simple(lock, func)) setattr(self, name, wrap_simple(lock, func))
self._search_api = Search(self, 'saved_searches', self.field_metadata.get_search_terms())
self.initialize_dynamic() self.initialize_dynamic()
@write_api @write_api
@ -127,7 +127,7 @@ class Cache(object):
except: except:
traceback.print_exc() 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_search_category(label='search', name=_('Searches'))
self.field_metadata.add_grouped_search_terms( self.field_metadata.add_grouped_search_terms(
@ -140,11 +140,6 @@ class Cache(object):
if self.dirtied_cache: if self.dirtied_cache:
self.dirtied_sequence = max(self.dirtied_cache.itervalues())+1 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 @write_api
def initialize_template_cache(self): def initialize_template_cache(self):
self.formatter_template_cache = {} self.formatter_template_cache = {}
@ -161,6 +156,8 @@ class Cache(object):
def reload_from_db(self, clear_caches=True): def reload_from_db(self, clear_caches=True):
if clear_caches: if clear_caches:
self._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(): for field in self.fields.itervalues():
if hasattr(field, 'table'): if hasattr(field, 'table'):
field.table.read(self.backend) # Reread data from metadata.db 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()} 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) 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): # {{{ class SortKey(object): # {{{

View File

@ -228,11 +228,10 @@ def get_categories(dbcache, sort='name', book_ids=None, icon_map=None):
icon = None icon = None
if icon_map and 'search' in icon_map: if icon_map and 'search' in icon_map:
icon = icon_map['search'] icon = icon_map['search']
ss = dbcache._search_api.get_saved_searches() queries = dbcache._search_api.saved_searches.queries
for srch in ss.names(): for srch in sorted(queries, key=sort_key):
items.append(Tag(srch, tooltip=ss.lookup(srch), items.append(Tag(srch, tooltip=queries[srch], sort=srch, icon=icon,
sort=srch, icon=icon, category='search', category='search', is_editable=False))
is_editable=False))
if len(items): if len(items):
categories['search'] = items categories['search'] = items

View File

@ -120,9 +120,6 @@ class LibraryDatabase(object):
self.new_api.reload_from_db() self.new_api.reload_from_db()
self.last_update_check = utcnow() self.last_update_check = utcnow()
def get_saved_searches(self):
return self.new_api._search_api.get_saved_searches()
@property @property
def custom_column_num_map(self): def custom_column_num_map(self):
return self.backend.custom_column_num_map 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))) 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.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 # Cleaning is not required anymore
LibraryDatabase.clean = LibraryDatabase.clean_custom = MT(lambda self:None) LibraryDatabase.clean = LibraryDatabase.clean_custom = MT(lambda self:None)
LibraryDatabase.clean_standard_field = MT(lambda self, field, commit=False:None) LibraryDatabase.clean_standard_field = MT(lambda self, field, commit=False:None)

View File

@ -7,15 +7,16 @@ __license__ = 'GPL v3'
__copyright__ = '2013, Kovid Goyal <kovid at kovidgoyal.net>' __copyright__ = '2013, Kovid Goyal <kovid at kovidgoyal.net>'
__docformat__ = 'restructuredtext en' __docformat__ = 'restructuredtext en'
import re import re, weakref
from functools import partial from functools import partial
from datetime import timedelta from datetime import timedelta
from calibre.constants import preferred_encoding
from calibre.utils.config_base import prefs from calibre.utils.config_base import prefs
from calibre.utils.date import parse_date, UNDEFINED_DATE, now 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.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 CONTAINS_MATCH = 0
EQUALS_MATCH = 1 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): class Parser(SearchQueryParser):
def __init__(self, dbcache, all_book_ids, gst, date_search, num_search, def __init__(self, dbcache, all_book_ids, gst, date_search, num_search,
bool_search, keypair_search, limit_search_columns, limit_search_columns_to, 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.dbcache, self.all_book_ids = dbcache, all_book_ids
self.all_search_locations = frozenset(locations) self.all_search_locations = frozenset(locations)
self.grouped_search_terms = gst self.grouped_search_terms = gst
@ -403,7 +465,7 @@ class Parser(SearchQueryParser):
self.virtual_fields = virtual_fields or {} self.virtual_fields = virtual_fields or {}
if 'marked' not in self.virtual_fields: if 'marked' not in self.virtual_fields:
self.virtual_fields['marked'] = self 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 @property
def field_metadata(self): def field_metadata(self):
@ -693,11 +755,11 @@ class Search(object):
self.keypair_search, self.keypair_search,
prefs['limit_search_columns'], prefs['limit_search_columns'],
prefs['limit_search_columns_to'], self.all_search_locations, prefs['limit_search_columns_to'], self.all_search_locations,
virtual_fields, self.get_saved_searches) virtual_fields, self.saved_searches.lookup)
try: try:
ret = sqp.parse(q) ret = sqp.parse(q)
finally: finally:
sqp.dbcache = sqp.get_saved_searches = None sqp.dbcache = sqp.lookup_saved_search = None
return ret return ret

View File

@ -751,3 +751,24 @@ class LegacyTest(BaseTest):
db.close() 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'),
))
# }}}

View File

@ -14,7 +14,8 @@ from calibre.gui2.dialogs.confirm_delete import confirm
class SavedSearchEditor(QDialog, Ui_SavedSearchEditor): class SavedSearchEditor(QDialog, Ui_SavedSearchEditor):
def __init__(self, parent, initial_search=None): 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) QDialog.__init__(self, parent)
Ui_SavedSearchEditor.__init__(self) Ui_SavedSearchEditor.__init__(self)
self.setupUi(self) self.setupUi(self)
@ -27,9 +28,9 @@ class SavedSearchEditor(QDialog, Ui_SavedSearchEditor):
self.current_search_name = None self.current_search_name = None
self.searches = {} self.searches = {}
for name in saved_searches().names(): for name in db.saved_search_names():
self.searches[name] = saved_searches().lookup(name) self.searches[name] = db.saved_search_lookup(name)
self.search_names = set([icu_lower(n) for n in saved_searches().names()]) self.search_names = set([icu_lower(n) for n in db.saved_search_names()])
self.populate_search_list() self.populate_search_list()
if initial_search is not None and initial_search in self.searches: 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('') self.search_text.setPlainText('')
def accept(self): 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: if self.current_search_name:
self.searches[self.current_search_name] = unicode(self.search_text.toPlainText()) self.searches[self.current_search_name] = unicode(self.search_text.toPlainText())
for name in saved_searches().names(): ss = {name:self.searches[name] for name in self.searches}
saved_searches().delete(name) db.saved_search_set_all(ss)
for name in self.searches:
saved_searches().add(name, self.searches[name])
QDialog.accept(self) QDialog.accept(self)

View File

@ -308,28 +308,35 @@ class SavedSearchBox(QComboBox): # {{{
self.saved_search_selected(self.currentText()) self.saved_search_selected(self.currentText())
def saved_search_selected(self, qname): 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) qname = unicode(qname)
if qname is None or not qname.strip(): if qname is None or not qname.strip():
self.search_box.clear() self.search_box.clear()
return return
if not saved_searches().lookup(qname): if not db.saved_search_lookup(qname):
self.search_box.clear() self.search_box.clear()
self.setEditText(qname) self.setEditText(qname)
return return
self.search_box.set_search_string(u'search:"%s"' % qname, emit_changed=False) self.search_box.set_search_string(u'search:"%s"' % qname, emit_changed=False)
self.setEditText(qname) self.setEditText(qname)
self.setToolTip(saved_searches().lookup(qname)) self.setToolTip(db.saved_search_lookup(qname))
def initialize_saved_search_names(self): def initialize_saved_search_names(self):
from calibre.gui2.ui import saved_searches from calibre.gui2.ui import get_gui
qnames = saved_searches().names() gui = get_gui()
self.addItems(qnames) try:
names = gui.current_db.saved_search_names()
except AttributeError:
# Happens during gui initialization
names = []
self.addItems(names)
self.setCurrentIndex(-1) self.setCurrentIndex(-1)
# SIGNALed from the main UI # SIGNALed from the main UI
def save_search_button_clicked(self): 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()) name = unicode(self.currentText())
if not name.strip(): if not name.strip():
name = unicode(self.search_box.text()).replace('"', '') name = unicode(self.search_box.text()).replace('"', '')
@ -337,8 +344,8 @@ class SavedSearchBox(QComboBox): # {{{
error_dialog(self, _('Create saved search'), error_dialog(self, _('Create saved search'),
_('There is no search to save'), show=True) _('There is no search to save'), show=True)
return return
saved_searches().delete(name) db.saved_search_delete(name)
saved_searches().add(name, unicode(self.search_box.text())) db.saved_search_add(name, unicode(self.search_box.text()))
# now go through an initialization cycle to ensure that the combobox has # 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 # 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. # references the new search instead of the text in the search.
@ -348,7 +355,8 @@ class SavedSearchBox(QComboBox): # {{{
self.changed.emit() self.changed.emit()
def delete_current_search(self): 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() idx = self.currentIndex()
if idx <= 0: if idx <= 0:
error_dialog(self, _('Delete current search'), error_dialog(self, _('Delete current search'),
@ -358,21 +366,22 @@ class SavedSearchBox(QComboBox): # {{{
'<b>permanently deleted</b>. Are you sure?') '<b>permanently deleted</b>. Are you sure?')
+'</p>', 'saved_search_delete', self): +'</p>', 'saved_search_delete', self):
return return
ss = saved_searches().lookup(unicode(self.currentText())) ss = db.saved_search_lookup(unicode(self.currentText()))
if ss is None: if ss is None:
return return
saved_searches().delete(unicode(self.currentText())) db.saved_search_delete(unicode(self.currentText()))
self.clear() self.clear()
self.search_box.clear() self.search_box.clear()
self.changed.emit() self.changed.emit()
# SIGNALed from the main UI # SIGNALed from the main UI
def copy_search_button_clicked(self): 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() idx = self.currentIndex()
if idx < 0: if idx < 0:
return 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())))
# }}} # }}}

View File

@ -178,7 +178,7 @@ class CreateVirtualLibrary(QDialog): # {{{
self.resize(self.sizeHint()+QSize(150, 25)) self.resize(self.sizeHint()+QSize(150, 25))
def search_text_changed(self, txt): 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:')] searches = [_('Saved searches recognized in the expression:')]
txt = unicode(txt) txt = unicode(txt)
while txt: while txt:
@ -201,9 +201,9 @@ class CreateVirtualLibrary(QDialog): # {{{
search_name = possible_search[0] search_name = possible_search[0]
if search_name.startswith('='): if search_name.startswith('='):
search_name = search_name[1:] search_name = search_name[1:]
if search_name in saved_searches().names(): if search_name in db.saved_search_names():
searches.append(search_name + '=' + searches.append(search_name + '=' +
saved_searches().lookup(search_name)) db.saved_search_lookup(search_name))
else: else:
txt = '' txt = ''
else: else:
@ -234,18 +234,17 @@ class CreateVirtualLibrary(QDialog): # {{{
self.vl_text.setText(self.original_search) self.vl_text.setText(self.original_search)
def link_activated(self, url): def link_activated(self, url):
from calibre.gui2.ui import saved_searches
db = self.gui.current_db db = self.gui.current_db
f, txt = unicode(url).partition('.')[0::2] f, txt = unicode(url).partition('.')[0::2]
if f == 'search': if f == 'search':
names = saved_searches().names() names = db.saved_search_names()
else: else:
names = getattr(db, 'all_%s_names'%f)() names = getattr(db, 'all_%s_names'%f)()
d = SelectNames(names, txt, parent=self) d = SelectNames(names, txt, parent=self)
if d.exec_() == d.Accepted: if d.exec_() == d.Accepted:
prefix = f+'s' if f in {'tag', 'author'} else f prefix = f+'s' if f in {'tag', 'author'} else f
if f == 'search': 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: else:
search = ['%s:"=%s"'%(prefix, x.replace('"', '\\"')) for x in d.names] search = ['%s:"=%s"'%(prefix, x.replace('"', '\\"')) for x in d.names]
if search: if search:
@ -476,7 +475,7 @@ class SearchRestrictionMixin(object):
return name[0:MAX_VIRTUAL_LIBRARY_NAME_LENGTH].strip() return name[0:MAX_VIRTUAL_LIBRARY_NAME_LENGTH].strip()
def build_search_restriction_list(self): 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 = self.ar_menu
m.clear() m.clear()
@ -508,7 +507,7 @@ class SearchRestrictionMixin(object):
add_action(current_restriction_text, 2) add_action(current_restriction_text, 2)
dex += 1 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) add_action(n, dex)
dex += 1 dex += 1

View File

@ -878,7 +878,7 @@ class TagsModel(QAbstractItemModel): # {{{
traceback.print_exc() traceback.print_exc()
self.db.data.change_search_locations(self.db.field_metadata.get_search_terms()) 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')) tb_cats.add_search_category(label='search', name=_('Searches'))
if self.filter_categories_by: if self.filter_categories_by:
@ -1004,11 +1004,11 @@ class TagsModel(QAbstractItemModel): # {{{
_('Author names cannot contain & characters.')).exec_() _('Author names cannot contain & characters.')).exec_()
return False return False
if key == 'search': 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'), error_dialog(self.gui_parent, _('Duplicate search name'),
_('The saved search name %s is already used.')%val).exec_() _('The saved search name %s is already used.')%val).exec_()
return False 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 item.tag.name = val
self.search_item_renamed.emit() # Does a refresh self.search_item_renamed.emit() # Does a refresh
else: else:

View File

@ -354,8 +354,7 @@ class TagsView(QTreeView): # {{{
self.delete_user_category.emit(key) self.delete_user_category.emit(key)
return return
if action == 'delete_search': if action == 'delete_search':
from calibre.gui2.ui import saved_searches self.model().db.saved_search_delete(key)
saved_searches().delete(key)
self.rebuild_saved_searches.emit() self.rebuild_saved_searches.emit()
return return
if action == 'delete_item_from_user_category': if action == 'delete_item_from_user_category':

View File

@ -98,16 +98,6 @@ _gui = None
def get_gui(): def get_gui():
return _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, # {{{ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{
TagBrowserMixin, CoverFlowMixin, LibraryViewMixin, SearchBoxMixin, TagBrowserMixin, CoverFlowMixin, LibraryViewMixin, SearchBoxMixin,
SavedSearchBoxMixin, SearchRestrictionMixin, LayoutMixin, UpdateMixin, SavedSearchBoxMixin, SearchRestrictionMixin, LayoutMixin, UpdateMixin,
@ -312,10 +302,10 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{
####################### Search boxes ######################## ####################### Search boxes ########################
SearchRestrictionMixin.__init__(self) SearchRestrictionMixin.__init__(self)
SavedSearchBoxMixin.__init__(self) SavedSearchBoxMixin.__init__(self)
SearchBoxMixin.__init__(self)
####################### Library view ######################## ####################### Library view ########################
LibraryViewMixin.__init__(self, db) LibraryViewMixin.__init__(self, db)
SearchBoxMixin.__init__(self) # Requires current_db
if show_gui: if show_gui:
self.show() self.show()

View File

@ -1029,11 +1029,10 @@ def command_saved_searches(args, dbpath):
prints(_('Error: You must specify an action (add|remove|list)'), file=sys.stderr) prints(_('Error: You must specify an action (add|remove|list)'), file=sys.stderr)
return 1 return 1
db = get_db(dbpath, opts) db = get_db(dbpath, opts)
ss = db.get_saved_searches()
if args[0] == 'list': if args[0] == 'list':
for name in ss.names(): for name in db.saved_search_names():
prints(_('Name:'), name) prints(_('Name:'), name)
prints(_('Search string:'), ss.lookup(name)) prints(_('Search string:'), db.saved_search_lookup(name))
print print
elif args[0] == 'add': elif args[0] == 'add':
if len(args) < 3: if len(args) < 3:
@ -1041,7 +1040,7 @@ def command_saved_searches(args, dbpath):
print print
prints(_('Error: You must specify a name and a search string'), file=sys.stderr) prints(_('Error: You must specify a name and a search string'), file=sys.stderr)
return 1 return 1
ss.add(args[1], args[2]) db.saved_search_add(args[1], args[2])
prints(args[1], _('added')) prints(args[1], _('added'))
elif args[0] == 'remove': elif args[0] == 'remove':
if len(args) < 2: if len(args) < 2:
@ -1049,7 +1048,7 @@ def command_saved_searches(args, dbpath):
print print
prints(_('Error: You must specify a name'), file=sys.stderr) prints(_('Error: You must specify a name'), file=sys.stderr)
return 1 return 1
ss.delete(args[1]) db.saved_search_delete(args[1])
prints(args[1], _('removed')) prints(args[1], _('removed'))
else: else:
parser.print_help() parser.print_help()

View File

@ -538,8 +538,23 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
if self.user_version == 0: if self.user_version == 0:
self.user_version = 1 self.user_version = 1
def get_saved_searches(self): def saved_search_names(self):
return saved_searches() 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): def last_modified(self):
''' Return last modified time as a UTC datetime object''' ''' Return last modified time as a UTC datetime object'''

View File

@ -209,7 +209,7 @@ class LibraryServer(ContentServer, MobileServer, XMLServer, OPDSServer, Cache,
if sr: if sr:
if sr in virt_libs: if sr in virt_libs:
sr = virt_libs[sr] 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 ', prints('WARNING: Content server: search restriction ',
sr, ' does not exist') sr, ' does not exist')
sr = '' sr = ''

View File

@ -76,6 +76,11 @@ class SavedSearchQueries(object):
self.queries.pop(self.force_unicode(old_name), False) self.queries.pop(self.force_unicode(old_name), False)
db.prefs[self.opt_name] = self.queries 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): def names(self):
return sorted(self.queries.keys(),key=sort_key) return sorted(self.queries.keys(),key=sort_key)
@ -93,6 +98,9 @@ def saved_searches():
global ss global ss
return ss return ss
def global_lookup_saved_search(name):
return ss.lookup(name)
''' '''
Parse a search expression into a series of potentially recursive operations. Parse a search expression into a series of potentially recursive operations.
@ -292,10 +300,10 @@ class SearchQueryParser(object):
failed.append(test[0]) failed.append(test[0])
return failed 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.sqp_initialize(locations, test=test, optimize=optimize)
self.parser = Parser() 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): def sqp_change_locations(self, locations):
self.sqp_initialize(locations, optimize=self.optimize) self.sqp_initialize(locations, optimize=self.optimize)
@ -368,7 +376,7 @@ class SearchQueryParser(object):
raise ParseException(_('Recursive saved search: {0}').format(query)) raise ParseException(_('Recursive saved search: {0}').format(query))
if self.recurse_level > 5: if self.recurse_level > 5:
self.searches_seen.add(query) 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: except ParseException as e:
raise e raise e
except: # convert all exceptions (e.g., missing key) to a parse error except: # convert all exceptions (e.g., missing key) to a parse error