fix: remove early-exit on non-empty tag_categories in ensure path

Two places checked `if post.tag_categories: return` before doing
a full cache-coverage check, causing posts with partial cache
composes (e.g. 5/40 tags from the background prefetch) to get
stuck at low coverage forever:

  ensure_categories: removed the post.tag_categories early exit.
    Now ALWAYS runs try_compose_from_cache first. Only the 100%
    coverage return (True) is trusted as "done." Partial composes
    return False and fall through to the fetch path.

  _ensure_post_categories_async: removed the post.tag_categories
    guard. Danbooru/e621 are filtered by the client.category_fetcher
    is None check instead (they categorize inline, no fetcher).
    For Gelbooru-style sites, always schedules ensure_categories
    regardless of current post state.

Root cause: the partial-compose fix (try_compose_from_cache
populates tag_categories even when cache coverage is <100%)
conflicted with the early-exit guards that assumed non-empty
tag_categories = fully categorized. Now the only "fully done"
signal is try_compose_from_cache returning True (100% coverage).
This commit is contained in:
pax 2026-04-09 19:40:09 -05:00
parent 762d73dc4f
commit 1547cbe55a
2 changed files with 17 additions and 12 deletions

View File

@ -289,18 +289,20 @@ class CategoryFetcher:
# ----- dispatch: ensure (single post) -----
async def ensure_categories(self, post: "Post") -> None:
"""Idempotent. Guarantee ``post.tag_categories`` is populated.
"""Guarantee ``post.tag_categories`` is fully populated.
Dispatch:
1. Already populated return.
2. Cache compose return if complete.
3. Batch tag API (if available + probe passed) return.
4. Per-post HTML scrape return.
1. Cache compose with 100% coverage return.
2. Batch tag API (if available + probe passed) return.
3. Per-post HTML scrape return.
Does NOT short-circuit on non-empty ``post.tag_categories``
because partial cache composes can leave the post at e.g.
5/40 coverage. Only the 100%-coverage return from
``try_compose_from_cache`` is trusted as "done."
Coalesces concurrent calls for the same ``post.id``.
"""
if post.tag_categories:
return
if self.try_compose_from_cache(post):
return

View File

@ -161,13 +161,16 @@ class BooruApp(QMainWindow):
return client.category_fetcher if client else None
def _ensure_post_categories_async(self, post) -> None:
"""Schedule an async ensure_categories if the post needs it.
"""Schedule an async ensure_categories for the post.
No-op if the post already has categories, or if the active
client doesn't have a CategoryFetcher attached.
No-op if the active client doesn't have a CategoryFetcher
(Danbooru/e621 categorize inline, no fetcher needed).
Does NOT check post.tag_categories partial cache composes
from the background prefetch can leave the post at e.g.
5/40 coverage. ensure_categories checks 100% cache coverage
internally and fetches the remainder if needed.
"""
if post.tag_categories:
return
client = self._make_client()
if client is None or client.category_fetcher is None:
return