fix: address review — injective mirror roots, root relocation, full preview coverage, EXDEV-safe rollback

Codex review on #1124:

- P1: mirror layout root component is now <basename>-<roothash>
  (sha256 of the normalized root path), so two roots sharing a basename
  no longer map to the same mirror directory and overwrite each other's
  sidecars
- P1: changing sidecar_storage_path while centralized no longer strands
  assets in the old root — new relocate_root migration direction moves
  the whole mirror tree, rewrites preview_url prefixes inside sidecars,
  reconciles scanner caches, and prunes the emptied old tree; the
  settings UI detects the path change and offers the relocation
- P2: migration enumerates the same preview candidates as
  find_preview_file — case-insensitive variants (model.WEBP) and the
  legacy .example.0.jpeg suffix — instead of exact lowercase
  PREVIEW_EXTENSIONS only
- P2: _rollback_model_staging restores staged files with the
  EXDEV-tolerant mover, so a failed undoable-delete staging no longer
  strands a cross-filesystem centralized sidecar copy

Tests: same-basename root injectivity, mixed-case/example preview
migration, relocate_root happy path + guards + route 400, frontend
relocation prompt flow. Verified end-to-end in a sandboxed standalone
server: uppercase/legacy previews migrate, root relocation moves the
tree and the list API serves the new locations immediately without a
rescan.
This commit is contained in:
Will Miao
2026-09-26 12:24:17 +08:00
parent 16430aef21
commit a6fca8612f
22 changed files with 592 additions and 71 deletions
+3 -2
View File
@@ -23,12 +23,13 @@ By default, `.metadata.json` sidecars and preview images live **alongside** thei
In centralized mode, sidecars and previews mirror the library-relative directory structure:
```
<sidecar_root>/<library>/<root_basename>/<rel_dir>/<name>.metadata.json
<sidecar_root>/<library>/<root_basename-roothash>/<rel_dir>/<name>.metadata.json
```
- `<library>` is the active library name, `<root_basename>` the basename of the model root containing the file, and `<rel_dir>` the model's directory relative to that root. Each component is sanitized to filesystem-safe characters.
- `<library>` is the active library name and `<rel_dir>` the model's directory relative to the model root containing the file. `<root_basename-roothash>` combines the root's basename with a short hash of its full path so two roots sharing a basename (e.g. `/mnt/a/loras` and `/mnt/b/loras`) never collide. Each component is sanitized to filesystem-safe characters.
- `.civitai.info` files always stay next to the model file, in both modes.
- Changing the mode does **not** move existing files automatically — run the migration (`POST /api/lm/sidecars/migrate` with `{"direction": "to_centralized" | "to_alongside"}`, or the "Migrate Sidecars Now" button in settings).
- Changing `sidecar_storage_path` while centralized likewise needs a root relocation: `{"direction": "relocate_root", "old_root": "<previous path>"}` moves the whole mirror tree to the new root (the settings UI offers this automatically).
- All sidecar/preview path derivation goes through the helpers in `py/utils/sidecar_paths.py`; never construct paths inline.
---