Bug #1924247: Possible to create recursive VL that crashes Calibre

The actual fix is in cache.py line 2218. Another change added the virtual fields to vl:x searches. The rest are improvements to error messages.
This commit is contained in:
Charles Haley 2021-04-15 11:12:52 +01:00
parent caca59de29
commit a6f4b77cfd
5 changed files with 19 additions and 14 deletions

View File

@ -1086,17 +1086,18 @@ class Cache(object):
return self._search_api(self, query, restriction, virtual_fields=virtual_fields, book_ids=book_ids)
@read_api
def books_in_virtual_library(self, vl, search_restriction=None):
def books_in_virtual_library(self, vl, search_restriction=None, virtual_fields=None):
' Return the set of books in the specified virtual library '
vl = self._pref('virtual_libraries', {}).get(vl) if vl else None
if not vl and not search_restriction:
return self.all_book_ids()
# We utilize the search restriction cache to speed this up
srch = partial(self._search, virtual_fields=virtual_fields)
if vl:
if search_restriction:
return frozenset(self._search('', vl) & self._search('', search_restriction))
return frozenset(self._search('', vl))
return frozenset(self._search('', search_restriction))
return frozenset(srch('', vl) & srch('', search_restriction))
return frozenset(srch('', vl))
return frozenset(srch('', search_restriction))
@read_api
def number_of_books_in_virtual_library(self, vl=None, search_restriction=None):
@ -2214,8 +2215,11 @@ class Cache(object):
c = defaultdict(list)
libraries = self._pref('virtual_libraries', {})
for lib, expr in libraries.items():
try:
for book in self._search(expr, virtual_fields=virtual_fields):
c[book].append(lib)
except Exception as e:
c[book].append(_('[Error in virtual library {0}: {1}]').format(lib, str(e)))
self.vls_for_books_cache = {b:tuple(sorted(libs, key=sort_key)) for b, libs in c.items()}
if not book_ids:
book_ids = self._all_book_ids()

View File

@ -513,7 +513,8 @@ class Parser(SearchQueryParser): # {{{
if not vl:
raise ParseException(_('No such Virtual library: {}').format(query))
try:
return candidates & self.dbcache.books_in_virtual_library(query)
return candidates & self.dbcache.books_in_virtual_library(
query, virtual_fields=self.virtual_fields)
except RuntimeError:
raise ParseException(_('Virtual library search is recursive: {}').format(query))

View File

@ -312,7 +312,7 @@ class TagsModel(QAbstractItemModel): # {{{
search_item_renamed = pyqtSignal()
tag_item_renamed = pyqtSignal()
refresh_required = pyqtSignal()
restriction_error = pyqtSignal()
restriction_error = pyqtSignal(object)
drag_drop_finished = pyqtSignal(object)
user_categories_edited = pyqtSignal(object, object)
user_category_added = pyqtSignal()
@ -1113,12 +1113,11 @@ class TagsModel(QAbstractItemModel): # {{{
data = self.db.new_api.get_categories(sort=sort,
book_ids=self.get_book_ids_to_use(),
first_letter_sort=self.collapse_model == 'first letter')
except:
import traceback
except Exception as e:
traceback.print_exc()
data = self.db.new_api.get_categories(sort=sort,
first_letter_sort=self.collapse_model == 'first letter')
self.restriction_error.emit()
self.restriction_error.emit(str(e))
if self.filter_categories_by:
if self.filter_categories_by.startswith('='):

View File

@ -99,9 +99,10 @@ class TagBrowserMixin(object): # {{{
def user_categories_edited(self):
self.library_view.model().refresh()
def do_restriction_error(self):
def do_restriction_error(self, e):
error_dialog(self.tags_view, _('Invalid search restriction'),
_('The current search restriction is invalid'), show=True)
_('The current search restriction is invalid'),
det_msg=str(e) if e else '', show=True)
def do_add_subcategory(self, on_category_key, new_category_name=None):
'''

View File

@ -172,7 +172,7 @@ class TagsView(QTreeView): # {{{
tag_item_renamed = pyqtSignal()
search_item_renamed = pyqtSignal()
drag_drop_finished = pyqtSignal(object)
restriction_error = pyqtSignal()
restriction_error = pyqtSignal(object)
tag_item_delete = pyqtSignal(object, object, object, object, object)
tag_identifier_delete = pyqtSignal(object, object)
apply_tag_to_selected = pyqtSignal(object, object, object)