From 8f8db62a5aaf73dd2ed8be6774312f50069eb780 Mon Sep 17 00:00:00 2001 From: pax Date: Thu, 9 Apr 2026 19:18:13 -0500 Subject: [PATCH] library_save: ensure categories before template render MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit save_post_file is now async and gains an optional category_fetcher parameter. When the template uses any category token (%artist%, %character%, %copyright%, %general%, %meta%, %species%) AND the post's tag_categories is empty AND a fetcher is available, it awaits ensure_categories(post) before calling render_filename_template. This guarantees the filename is correct even when saving a post the user hasn't clicked (bypassing the info panel's on-display trigger). When the template uses only non-category tokens (%id%, %md5%, %score%, %rating%, %ext%) or is empty, the ensure check is skipped entirely — no HTTP overhead for the common case. Every existing caller already runs from _run_async closures, so the sync→async signature change is mechanical. The callers are updated in the next two commits to pass category_fetcher. --- booru_viewer/core/library_save.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/booru_viewer/core/library_save.py b/booru_viewer/core/library_save.py index 28635cd..b183103 100644 --- a/booru_viewer/core/library_save.py +++ b/booru_viewer/core/library_save.py @@ -26,13 +26,17 @@ if TYPE_CHECKING: from .api.base import Post -def save_post_file( +_CATEGORY_TOKENS = {"%artist%", "%character%", "%copyright%", "%general%", "%meta%", "%species%"} + + +async def save_post_file( src: Path, post: "Post", dest_dir: Path, db: Database, in_flight: set[str] | None = None, explicit_name: str | None = None, + category_fetcher=None, ) -> Path: """Copy a Post's already-cached media file into `dest_dir`. @@ -95,6 +99,18 @@ def save_post_file( basename = explicit_name else: template = db.get_setting("library_filename_template") + # If the template uses category tokens and the post has no + # categories yet, fetch them synchronously before rendering. + # This guarantees the filename is correct even when saving + # a post the user hasn't clicked (no prior ensure from the + # info panel path). + if ( + category_fetcher is not None + and not post.tag_categories + and template + and any(tok in template for tok in _CATEGORY_TOKENS) + ): + await category_fetcher.ensure_categories(post) basename = render_filename_template(template, post, src.suffix) in_flight_set: set[str] = in_flight if in_flight is not None else set()