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

View File

@ -513,7 +513,8 @@ class Parser(SearchQueryParser): # {{{
if not vl: if not vl:
raise ParseException(_('No such Virtual library: {}').format(query)) raise ParseException(_('No such Virtual library: {}').format(query))
try: 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: except RuntimeError:
raise ParseException(_('Virtual library search is recursive: {}').format(query)) raise ParseException(_('Virtual library search is recursive: {}').format(query))

View File

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

View File

@ -99,9 +99,10 @@ class TagBrowserMixin(object): # {{{
def user_categories_edited(self): def user_categories_edited(self):
self.library_view.model().refresh() self.library_view.model().refresh()
def do_restriction_error(self): def do_restriction_error(self, e):
error_dialog(self.tags_view, _('Invalid search restriction'), 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): 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() tag_item_renamed = pyqtSignal()
search_item_renamed = pyqtSignal() search_item_renamed = pyqtSignal()
drag_drop_finished = pyqtSignal(object) drag_drop_finished = pyqtSignal(object)
restriction_error = pyqtSignal() restriction_error = pyqtSignal(object)
tag_item_delete = pyqtSignal(object, object, object, object, object) tag_item_delete = pyqtSignal(object, object, object, object, object)
tag_identifier_delete = pyqtSignal(object, object) tag_identifier_delete = pyqtSignal(object, object)
apply_tag_to_selected = pyqtSignal(object, object, object) apply_tag_to_selected = pyqtSignal(object, object, object)