feat(thumbnails): keep previews in one folder, set from config.json (#320)

thumbnails.location in config.json can now name one folder for every
source's previews: <folder>/<source key>/<rel>_thumb<ext>. A source key is
a readable slug plus a short hash of the source's identity, stored in the
folder's marker (.promptmanager-thumbnails.json) with the install that
made it. Serving, on-demand and capture writes, listings, clear, delete,
duplicates, the temp sweep and History covers all follow it; a leftover in
<source>/thumbnails is never served and other text under thumbnails/ is
refused.

A missing, unplugged or foreign folder is unavailable (a probe checks it
every 5 s within 2 s; a stalled or failing store call flips it at once):
originals are served, nothing is written anywhere, the folder is never
re-created, and generate, convert and clear answer 409
location-unavailable. A bad config value is unavailable too, never a
fall-back to the sources.

No UI yet: POST /settings refuses thumbnail_location (409
location-needs-move); GET /settings reports it with its path, the
thumbnail status without one. Sources inside or around the folder and a
library move into it are refused.
This commit is contained in:
Vito Sansevero
2026-10-31 19:35:30 -07:00
parent 38f5491da1
commit 57ab85b002
21 changed files with 2211 additions and 40 deletions
+22 -1
View File
@@ -30,6 +30,8 @@ import ntpath
import os
import re
from .preview_location import HOLDS_THUMBNAILS_REASON
logger = logging.getLogger("prompt_manager.api")
MAX_SOURCE_PATH = 4096 # characters
@@ -114,7 +116,26 @@ def source_problem(text):
code = source_form_error(text)
if code is not None:
return code, FORM_REASONS[code]
return GalleryConfig.gallery_root_problem(text)
code, reason = GalleryConfig.gallery_root_problem(text)
if code is None and _holds_thumbnails(GalleryConfig.resolve_gallery_root(text)):
return "holds-thumbnails", HOLDS_THUMBNAILS_REASON
return code, reason
def _holds_thumbnails(path):
"""Whether *path* is inside or around the thumbnail folder in use (#320):
a source there would list Prompt Manager's own previews."""
from . import preview_store
try:
from ...utils.library_location import _inside_any
except ImportError: # tests import the package top-level
from utils.library_location import _inside_any
setting = preview_store.setting()
if setting.mode != preview_store.FOLDER_MODE or not setting.path:
return False
return _inside_any(path, [setting.path]) or _inside_any(setting.path, [path])
def public_spelling(text):