search_controller: cache lookup sets across infinite scroll appends

Build the cache-dir listing, bookmark ID set, and saved-post ID
set once in on_search_done and reuse in _drain_append_queue.
Previously these were rebuilt from scratch on every infinite
scroll append — a full directory listing and two DB queries per
page load.

Caches are invalidated on new search, site change, and
bookmark/save operations via invalidate_lookup_caches().
This commit is contained in:
pax 2026-04-11 22:48:54 -05:00
parent b00f3ff95c
commit 3a95b6817d

View File

@ -124,11 +124,29 @@ class SearchController:
self._search = SearchState() self._search = SearchState()
self._last_scroll_page = 0 self._last_scroll_page = 0
self._infinite_scroll = app._db.get_setting_bool("infinite_scroll") self._infinite_scroll = app._db.get_setting_bool("infinite_scroll")
# Cached lookup sets — rebuilt once per search, reused in
# _drain_append_queue to avoid repeated DB queries and directory
# listings on every infinite-scroll append.
self._cached_names: set[str] | None = None
self._bookmarked_ids: set[int] | None = None
self._saved_ids: set[int] | None = None
def reset(self) -> None: def reset(self) -> None:
"""Reset search state for a site change.""" """Reset search state for a site change."""
self._search.shown_post_ids.clear() self._search.shown_post_ids.clear()
self._search.page_cache.clear() self._search.page_cache.clear()
self._cached_names = None
self._bookmarked_ids = None
self._saved_ids = None
def invalidate_lookup_caches(self) -> None:
"""Clear cached bookmark/saved/cache-dir sets.
Call after a bookmark or save operation so the next
``_drain_append_queue`` picks up the change.
"""
self._bookmarked_ids = None
self._saved_ids = None
def clear_loading(self) -> None: def clear_loading(self) -> None:
self._loading = False self._loading = False
@ -140,6 +158,9 @@ class SearchController:
self._app._page_spin.setValue(1) self._app._page_spin.setValue(1)
self._current_page = 1 self._current_page = 1
self._search = SearchState() self._search = SearchState()
self._cached_names = None
self._bookmarked_ids = None
self._saved_ids = None
self._min_score = self._app._score_spin.value() self._min_score = self._app._score_spin.value()
self._app._preview.clear() self._app._preview.clear()
self._app._next_page_btn.setVisible(True) self._app._next_page_btn.setVisible(True)
@ -296,22 +317,22 @@ class SearchController:
from ..core.cache import cached_path_for, cache_dir from ..core.cache import cached_path_for, cache_dir
site_id = self._app._site_combo.currentData() site_id = self._app._site_combo.currentData()
_saved_ids = self._app._db.get_saved_post_ids() self._saved_ids = self._app._db.get_saved_post_ids()
_favs = self._app._db.get_bookmarks(site_id=site_id) if site_id else [] _favs = self._app._db.get_bookmarks(site_id=site_id) if site_id else []
_bookmarked_ids: set[int] = {f.post_id for f in _favs} self._bookmarked_ids = {f.post_id for f in _favs}
_cd = cache_dir() _cd = cache_dir()
_cached_names: set[str] = set() self._cached_names = set()
if _cd.exists(): if _cd.exists():
_cached_names = {f.name for f in _cd.iterdir() if f.is_file()} self._cached_names = {f.name for f in _cd.iterdir() if f.is_file()}
for i, (post, thumb) in enumerate(zip(posts, thumbs)): for i, (post, thumb) in enumerate(zip(posts, thumbs)):
if post.id in _bookmarked_ids: if post.id in self._bookmarked_ids:
thumb.set_bookmarked(True) thumb.set_bookmarked(True)
thumb.set_saved_locally(post.id in _saved_ids) thumb.set_saved_locally(post.id in self._saved_ids)
cached = cached_path_for(post.file_url) cached = cached_path_for(post.file_url)
if cached.name in _cached_names: if cached.name in self._cached_names:
thumb._cached_path = str(cached) thumb._cached_path = str(cached)
if post.preview_url: if post.preview_url:
@ -449,16 +470,23 @@ class SearchController:
self._loading = False self._loading = False
return return
from ..core.cache import cached_path_for, cache_dir from ..core.cache import cached_path_for
site_id = self._app._site_combo.currentData()
_saved_ids = self._app._db.get_saved_post_ids()
# Reuse the lookup sets built in on_search_done. They stay valid
# within an infinite-scroll session — bookmarks/saves don't change
# during passive scrolling, and the cache directory only grows.
if self._saved_ids is None:
self._saved_ids = self._app._db.get_saved_post_ids()
if self._bookmarked_ids is None:
site_id = self._app._site_combo.currentData()
_favs = self._app._db.get_bookmarks(site_id=site_id) if site_id else [] _favs = self._app._db.get_bookmarks(site_id=site_id) if site_id else []
_bookmarked_ids: set[int] = {f.post_id for f in _favs} self._bookmarked_ids = {f.post_id for f in _favs}
if self._cached_names is None:
from ..core.cache import cache_dir
_cd = cache_dir() _cd = cache_dir()
_cached_names: set[str] = set() self._cached_names = set()
if _cd.exists(): if _cd.exists():
_cached_names = {f.name for f in _cd.iterdir() if f.is_file()} self._cached_names = {f.name for f in _cd.iterdir() if f.is_file()}
posts = ss.append_queue[:] posts = ss.append_queue[:]
ss.append_queue.clear() ss.append_queue.clear()
@ -468,11 +496,11 @@ class SearchController:
for i, (post, thumb) in enumerate(zip(posts, thumbs)): for i, (post, thumb) in enumerate(zip(posts, thumbs)):
idx = start_idx + i idx = start_idx + i
if post.id in _bookmarked_ids: if post.id in self._bookmarked_ids:
thumb.set_bookmarked(True) thumb.set_bookmarked(True)
thumb.set_saved_locally(post.id in _saved_ids) thumb.set_saved_locally(post.id in self._saved_ids)
cached = cached_path_for(post.file_url) cached = cached_path_for(post.file_url)
if cached.name in _cached_names: if cached.name in self._cached_names:
thumb._cached_path = str(cached) thumb._cached_path = str(cached)
if post.preview_url: if post.preview_url:
self.fetch_thumbnail(idx, post.preview_url) self.fetch_thumbnail(idx, post.preview_url)