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.
93 lines
3.2 KiB
Python
93 lines
3.2 KiB
Python
"""The thumbnail location on the test server (#320), undone by every reset.
|
|
|
|
Folder mode is reachable from config.json and tests only until the Move
|
|
thumbnails job lands, so page tests set it here, inside the server's root:
|
|
|
|
- ``POST /__test__/thumbnail-location`` ``{"mode": "folder"}`` (or
|
|
``"source"``): previews live in ``<root>/thumbs/<key>`` from now on (a new
|
|
folder with Prompt Manager's marker).
|
|
- ``GET /__test__/thumbnail-files``: the preview files in the thumbnail
|
|
folder and whether any source grew a ``thumbnails/`` folder.
|
|
- ``POST /__test__/thumbnail-folder/remove``: the thumbnail folder goes, as
|
|
when its drive is unplugged; the server notices it on its next check.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import shutil
|
|
from pathlib import Path
|
|
from typing import TYPE_CHECKING, Callable
|
|
|
|
from aiohttp import web
|
|
|
|
if TYPE_CHECKING:
|
|
from py.api import PromptManagerAPI
|
|
|
|
FOLDER_NAME = "thumbs"
|
|
|
|
|
|
def register_thumbnail_location_commands(
|
|
routes: web.RouteTableDef, api: PromptManagerAPI, root: Path
|
|
) -> Callable[[], None]:
|
|
"""Register the commands; returns the reset every reset runs."""
|
|
from py.api import preview_location, preview_store
|
|
|
|
folder = root / FOLDER_NAME
|
|
|
|
def reset() -> None:
|
|
preview_store.configure(None)
|
|
shutil.rmtree(folder, ignore_errors=True)
|
|
|
|
def use(mode: str) -> dict:
|
|
reset()
|
|
if mode == "source":
|
|
return {"mode": "source"}
|
|
folder.mkdir()
|
|
marker_id = preview_location.init_folder(str(folder), created_by_pm=True)
|
|
location = preview_store.configure(
|
|
{
|
|
"mode": "folder",
|
|
"path": str(folder),
|
|
"id": marker_id,
|
|
"install": "browser-tests",
|
|
}
|
|
)
|
|
return {"mode": location.mode, "available": location.available}
|
|
|
|
@routes.post("/__test__/thumbnail-location")
|
|
async def set_location(request: web.Request) -> web.Response:
|
|
data = await request.json()
|
|
mode = data.get("mode") if isinstance(data, dict) else None
|
|
if mode not in ("source", "folder"):
|
|
raise web.HTTPBadRequest()
|
|
return web.json_response(await api._run_in_executor(use, mode))
|
|
|
|
def files() -> dict:
|
|
previews = sorted(
|
|
path.relative_to(folder).as_posix()
|
|
for path in folder.rglob("*_thumb.*")
|
|
if path.is_file()
|
|
)
|
|
sources = [str(path) for path in api._get_all_output_dirs()]
|
|
return {
|
|
"folder": previews,
|
|
"source_thumbnails": any(
|
|
(Path(source) / "thumbnails").exists() for source in sources
|
|
),
|
|
}
|
|
|
|
@routes.get("/__test__/thumbnail-files")
|
|
async def thumbnail_files(_request: web.Request) -> web.Response:
|
|
return web.json_response(await api._run_in_executor(files))
|
|
|
|
def remove() -> bool:
|
|
shutil.rmtree(folder, ignore_errors=True)
|
|
return preview_store.refresh()
|
|
|
|
@routes.post("/__test__/thumbnail-folder/remove")
|
|
async def remove_folder(_request: web.Request) -> web.Response:
|
|
available = await api._run_in_executor(remove)
|
|
return web.json_response({"available": available})
|
|
|
|
return reset
|