mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-08 02:34:06 -04:00
Add ability to create a composite column containing the virtual libraries that the book is a member of.
Intermediate commit -- still testing
This commit is contained in:
parent
7ad78fc447
commit
8a3cb9b977
@ -332,6 +332,7 @@ class SearchRestrictionMixin(object):
|
||||
virt_libs = db.prefs.get('virtual_libraries', {})
|
||||
virt_libs[name] = search
|
||||
db.prefs.set('virtual_libraries', virt_libs)
|
||||
db.data.invalidate_virtual_libraries_caches(db)
|
||||
|
||||
def do_create_edit(self, name=None):
|
||||
db = self.library_view.model().db
|
||||
@ -341,8 +342,11 @@ class SearchRestrictionMixin(object):
|
||||
if name:
|
||||
self._remove_vl(name, reapply=False)
|
||||
self.add_virtual_library(db, cd.library_name, cd.library_search)
|
||||
db.data.invalidate_virtual_libraries_caches(db)
|
||||
if not name or name == db.data.get_base_restriction_name():
|
||||
self.apply_virtual_library(cd.library_name)
|
||||
else:
|
||||
self.tags_view.recount()
|
||||
|
||||
def virtual_library_clicked(self):
|
||||
m = self.virtual_library_menu
|
||||
@ -462,6 +466,9 @@ class SearchRestrictionMixin(object):
|
||||
default_yes=False):
|
||||
return
|
||||
self._remove_vl(name, reapply=True)
|
||||
db = self.library_view.model().db
|
||||
db.data.invalidate_virtual_libraries_caches(db)
|
||||
self.tags_view.recount()
|
||||
|
||||
def _remove_vl(self, name, reapply=True):
|
||||
db = self.library_view.model().db
|
||||
|
@ -144,7 +144,8 @@ def force_to_bool(val):
|
||||
|
||||
class CacheRow(list): # {{{
|
||||
|
||||
def __init__(self, db, composites, val, series_col, series_sort_col):
|
||||
def __init__(self, db, composites, val, series_col, series_sort_col,
|
||||
virtual_library_col):
|
||||
self.db = db
|
||||
self._composites = composites
|
||||
list.__init__(self, val)
|
||||
@ -152,6 +153,8 @@ class CacheRow(list): # {{{
|
||||
self._series_col = series_col
|
||||
self._series_sort_col = series_sort_col
|
||||
self._series_sort = None
|
||||
self._virt_lib_col = virtual_library_col
|
||||
self._virt_libs = None
|
||||
|
||||
def __getitem__(self, col):
|
||||
if self._must_do:
|
||||
@ -179,6 +182,26 @@ class CacheRow(list): # {{{
|
||||
else:
|
||||
self._series_sort = ''
|
||||
self[self._series_sort_col] = ''
|
||||
|
||||
if col == self._virt_lib_col and self._virt_libs is None:
|
||||
try:
|
||||
if not getattr(self.db.data, '_virt_libs_computed', False):
|
||||
self.db.data._ids_in_virt_libs = {}
|
||||
for v,s in self.db.prefs.get('virtual_libraries', {}).iteritems():
|
||||
self.db.data._ids_in_virt_libs[v] = self.db.data.search_raw(s)
|
||||
self.db.data._virt_libs_computed = True
|
||||
r = []
|
||||
for v in self.db.prefs.get('virtual_libraries', {}).keys():
|
||||
# optimize the lookup of the ID -- it is always zero
|
||||
if self[0] in self.db.data._ids_in_virt_libs[v]:
|
||||
r.append(v)
|
||||
from calibre.utils.icu import sort_key
|
||||
self._virt_libs = ", ".join(sorted(r, key=sort_key))
|
||||
self[self._virt_lib_col] = self._virt_libs
|
||||
except:
|
||||
print len(self)
|
||||
traceback.print_exc()
|
||||
|
||||
return list.__getitem__(self, col)
|
||||
|
||||
def __getslice__(self, i, j):
|
||||
@ -206,6 +229,7 @@ class ResultCache(SearchQueryParser): # {{{
|
||||
self.composites[field_metadata[key]['rec_index']] = key
|
||||
self.series_col = field_metadata['series']['rec_index']
|
||||
self.series_sort_col = field_metadata['series_sort']['rec_index']
|
||||
self.virtual_libraries_col = field_metadata['virtual_libraries']['rec_index']
|
||||
self._data = []
|
||||
self._map = self._map_filtered = []
|
||||
self.first_sort = True
|
||||
@ -816,6 +840,13 @@ class ResultCache(SearchQueryParser): # {{{
|
||||
current_candidates -= matches
|
||||
return matches
|
||||
|
||||
def invalidate_virtual_libraries_caches(self, db):
|
||||
self.refresh(db)
|
||||
|
||||
def search_raw(self, query):
|
||||
matches = self.parse(query)
|
||||
return matches
|
||||
|
||||
def search(self, query, return_matches=False):
|
||||
ans = self.search_getting_ids(query, self.search_restriction,
|
||||
set_restriction_count=True)
|
||||
@ -973,10 +1004,11 @@ class ResultCache(SearchQueryParser): # {{{
|
||||
try:
|
||||
self._data[id] = CacheRow(db, self.composites,
|
||||
db.conn.get('SELECT * from meta2 WHERE id=?', (id,))[0],
|
||||
self.series_col, self.series_sort_col)
|
||||
self.series_col, self.series_sort_col,
|
||||
self.virtual_libraries_col)
|
||||
self._data[id].append(db.book_on_device_string(id))
|
||||
self._data[id].append(self.marked_ids_dict.get(id, None))
|
||||
self._data[id].append(None)
|
||||
self._data[id].extend((self.marked_ids_dict.get(id, None), None, None))
|
||||
self._virt_libs_computed = False
|
||||
self._uuid_map[self._data[id][self._uuid_column_index]] = id
|
||||
except IndexError:
|
||||
return None
|
||||
@ -993,10 +1025,11 @@ class ResultCache(SearchQueryParser): # {{{
|
||||
for id in ids:
|
||||
self._data[id] = CacheRow(db, self.composites,
|
||||
db.conn.get('SELECT * from meta2 WHERE id=?', (id,))[0],
|
||||
self.series_col, self.series_sort_col)
|
||||
self.series_col, self.series_sort_col,
|
||||
self.virtual_libraries_col)
|
||||
self._data[id].append(db.book_on_device_string(id))
|
||||
self._data[id].append(self.marked_ids_dict.get(id, None))
|
||||
self._data[id].append(None) # Series sort column
|
||||
self._data[id].extend((self.marked_ids_dict.get(id, None), None, None))
|
||||
self._virt_libs_computed = False
|
||||
self._uuid_map[self._data[id][self._uuid_column_index]] = id
|
||||
self._map[0:0] = ids
|
||||
self._map_filtered[0:0] = ids
|
||||
@ -1023,15 +1056,17 @@ class ResultCache(SearchQueryParser): # {{{
|
||||
self._data = list(itertools.repeat(None, temp[-1][0] + 2)) if temp else []
|
||||
for r in temp:
|
||||
self._data[r[0]] = CacheRow(db, self.composites, r,
|
||||
self.series_col, self.series_sort_col)
|
||||
self.series_col, self.series_sort_col,
|
||||
self.virtual_libraries_col)
|
||||
self._uuid_map[self._data[r[0]][self._uuid_column_index]] = r[0]
|
||||
|
||||
for item in self._data:
|
||||
if item is not None:
|
||||
item.append(db.book_on_device_string(item[0]))
|
||||
# Temp mark and series_sort columns
|
||||
item.extend((None, None))
|
||||
# Temp mark, series_sort, virtual_library columns
|
||||
item.extend((None, None, None))
|
||||
|
||||
self._virt_libs_computed = False
|
||||
marked_col = self.FIELD_MAP['marked']
|
||||
for id_, val in self.marked_ids_dict.iteritems():
|
||||
try:
|
||||
|
@ -575,7 +575,7 @@ def command_set_metadata(args, dbpath):
|
||||
for key in sorted(db.field_metadata.all_field_keys()):
|
||||
m = db.field_metadata[key]
|
||||
if (key not in {'formats', 'series_sort', 'ondevice', 'path',
|
||||
'last_modified'} and m['is_editable'] and m['name']):
|
||||
'virtual_libraries', 'last_modified'} and m['is_editable'] and m['name']):
|
||||
yield key, m
|
||||
if m['datatype'] == 'series':
|
||||
si = m.copy()
|
||||
|
@ -449,6 +449,8 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
||||
self.field_metadata.set_field_record_index('marked', base, prefer_custom=False)
|
||||
self.FIELD_MAP['series_sort'] = base = base+1
|
||||
self.field_metadata.set_field_record_index('series_sort', base, prefer_custom=False)
|
||||
self.FIELD_MAP['virtual_libraries'] = base = base+1
|
||||
self.field_metadata.set_field_record_index('virtual_libraries', base, prefer_custom=False)
|
||||
|
||||
script = '''
|
||||
DROP VIEW IF EXISTS meta2;
|
||||
@ -992,6 +994,10 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
||||
mi.book_size = row[fm['size']]
|
||||
mi.ondevice_col= row[fm['ondevice']]
|
||||
mi.last_modified = row[fm['last_modified']]
|
||||
|
||||
mi._base_db_row = row # So the formatter functions can see the underlying data
|
||||
mi._virt_lib_column = fm['virtual_libraries']
|
||||
|
||||
formats = row[fm['formats']]
|
||||
mi.format_metadata = {}
|
||||
if not formats:
|
||||
|
@ -387,6 +387,16 @@ class FieldMetadata(dict):
|
||||
'is_custom':False,
|
||||
'is_category':False,
|
||||
'is_csp': False}),
|
||||
('virtual_libraries', {'table':None,
|
||||
'column':None,
|
||||
'datatype':'text',
|
||||
'is_multiple':{},
|
||||
'kind':'field',
|
||||
'name':_('Virtual Libraries'),
|
||||
'search_terms':['virtual_libraries'],
|
||||
'is_custom':False,
|
||||
'is_category':False,
|
||||
'is_csp': False}),
|
||||
]
|
||||
# }}}
|
||||
|
||||
|
@ -1209,9 +1209,19 @@ class BuiltinFinishFormatting(BuiltinFormatterFunction):
|
||||
return val
|
||||
return prefix + formatter._do_format(val, fmt) + suffix
|
||||
|
||||
class BuiltinBookInVirtualLibraries(BuiltinFormatterFunction):
|
||||
name = 'book_in_virtual_libraries'
|
||||
arg_count = 0
|
||||
category = 'Get values from metadata'
|
||||
__doc__ = doc = _('book_in_virtual_libraries() -- returns a list of '
|
||||
'virtual libraries that this book is in.')
|
||||
|
||||
def evaluate(self, formatter, kwargs, mi, locals_):
|
||||
return mi._base_db_row[mi._virt_lib_column ]
|
||||
|
||||
_formatter_builtins = [
|
||||
BuiltinAdd(), BuiltinAnd(), BuiltinApproximateFormats(),
|
||||
BuiltinAssign(), BuiltinBooksize(),
|
||||
BuiltinAssign(), BuiltinBookInVirtualLibraries(), BuiltinBooksize(),
|
||||
BuiltinCapitalize(), BuiltinCmp(), BuiltinContains(), BuiltinCount(),
|
||||
BuiltinCurrentLibraryName(), BuiltinCurrentLibraryPath(),
|
||||
BuiltinDaysBetween(), BuiltinDivide(), BuiltinEval(), BuiltinFirstNonEmpty(),
|
||||
|
@ -294,6 +294,7 @@ class SearchQueryParser(object):
|
||||
|
||||
def __init__(self, locations, test=False, optimize=False):
|
||||
self.sqp_initialize(locations, test=test, optimize=optimize)
|
||||
self.sqp_parsed_search_cache = {}
|
||||
self.parser = Parser()
|
||||
|
||||
def sqp_change_locations(self, locations):
|
||||
@ -308,8 +309,7 @@ class SearchQueryParser(object):
|
||||
# empty the list of searches used for recursion testing
|
||||
self.recurse_level = 0
|
||||
self.searches_seen = set([])
|
||||
candidates = self.universal_set()
|
||||
return self._parse(query, candidates)
|
||||
return self._parse(query)
|
||||
|
||||
# this parse is used internally because it doesn't clear the
|
||||
# recursive search test list. However, we permit seeing the
|
||||
@ -317,8 +317,11 @@ class SearchQueryParser(object):
|
||||
# another search.
|
||||
def _parse(self, query, candidates=None):
|
||||
self.recurse_level += 1
|
||||
res = self.sqp_parsed_search_cache.get(query, None)
|
||||
if res is None:
|
||||
try:
|
||||
res = self.parser.parse(query, self.locations)
|
||||
self.sqp_parsed_search_cache[query] = res
|
||||
except RuntimeError:
|
||||
raise ParseException(_('Failed to parse query, recursion limit reached: %s')%repr(query))
|
||||
if candidates is None:
|
||||
|
Loading…
x
Reference in New Issue
Block a user