Applying a filename template to a large library re-did O(library) work for
every renamed file: a full natsort resort plus whole-table SQLite rewrite and
download-history resync after each rename, and a full scan plus resort of the
entire recipe collection per renamed LoRA. On a 20k-model library with 300k
recipes on a HDD this pushed "Apply to Library" into multi-day runs.
- ModelScanner.defer_cache_persist(): bulk loops update the in-memory entry
and indexes only; resort + persist + download-history sync run once at
context exit, forced even on cancellation/error since files are already
renamed on disk. Single-rename callers keep immediate per-call behavior.
- RecipeScanner.build_lora_hash_index(): one-shot hash -> recipes index so
per-file lookups are O(1); update_lora_filename_by_hash gains hash_index /
defer_maintenance params, with a single finalize_bulk_filename_updates()
resort at the end of a bulk session.
- ModelLifecycleService.bulk_rename_session() / BulkRenameContext wire the
deferred path through rename_model (hash index built lazily on first
recipe-touching rename).
- Blocking os.rename sequence offloaded via asyncio.to_thread so one file's
HDD I/O no longer stalls the event loop (no cross-file parallelism).
- Skip logic, per-batch WebSocket progress, cancellation, and result
counters unchanged.
Phase 1 of #1045 (optional centralized sidecar storage): introduce
py/utils/sidecar_paths.py as the single place that resolves .metadata.json
and preview locations, and replace all inline splitext-based derivations
across scanners, services, download manager, and route handlers.
No behavior change: the default 'alongside' storage mode resolves every
path exactly as before. .civitai.info (third-party sidecar) derivation is
intentionally left co-located.
Redefine the empty download filename template from a no-op to a bulk
revert: FilenameTemplateUseCase resolves the target from each model's
recorded original_file_name sidecar entry (skipping models without one),
which resolves follow-ups 1 and 2 with a single coherent semantic shared
by the download and bulk-apply paths.
Also replace the browser-native confirm() with a self-managed
confirmation modal (filenameTemplateConfirmModal) that stacks above the
settings modal, since ModalManager would close the settings modal when
opening a registered one.
Add per-model-type filename templates ({model_name}, {version_name},
{base_model}, {author}, {first_tag}, {hash_short}, {original_name}) so
downloaded files get informative names instead of e.g. V1.safetensors.
Empty template keeps the current filename (opt-in, off by default).
- apply template automatically after downloads; rename conflicts keep
the original name and never fail the download
- record original_file_name in metadata on rename for traceability
- bulk apply via GET|POST /api/lm/{prefix}/apply-filename-template with
WebSocket progress, sharing the auto-organize lock
- settings UI lives in the new Organization tab with validation, live
preview, and per-type 'apply to library' actions