mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-09-29 06:54:09 -03:00
fix(sidecars): keep mirrored sidecars when a model root moves
Centralized sidecars were addressed by a hash of the model root's absolute path, so moving or renaming a root produced a new mirror directory. The scanner then found no sidecar there, rebuilt default metadata, and silently lost favorites, notes, tags and usage tips for every model under that root, leaving the old metadata orphaned on disk. Mirrors are now addressed by a root identity pinned in <sidecar_root>/.lm-sidecar-roots.json. The identity starts as the existing deterministic <basename>-<path digest> -- so pre-existing mirrors keep resolving even if the map is lost, and a relocated sidecar root keeps its names -- and is re-anchored to the root's new path when it moves, matched by basename and recorded sample directories. Ambiguous matches are never guessed: the mirror is left untouched and reported. Also: - drop the <library> path segment (single-library direction); a legacy library prefix is only recognised while adopting an existing mirror, which also keeps the unreleased centralized layout usable - surface stranded mirrors in Doctor and in the log instead of silently rebuilding sidecars - keep the default alongside mode untouched: the identity map is loaded and reconciled lazily, only while centralized storage is in use
This commit is contained in:
@@ -75,6 +75,8 @@ from ...utils.sidecar_paths import (
|
||||
get_configured_sidecar_root,
|
||||
get_metadata_path,
|
||||
get_preview_dir,
|
||||
get_storage_mode,
|
||||
get_unmatched_sidecar_components,
|
||||
)
|
||||
from ...utils.usage_stats import UsageStats
|
||||
from .base_model_handlers import BaseModelHandlerSet
|
||||
@@ -806,6 +808,7 @@ class DoctorHandler:
|
||||
await self._check_civitai_api_key(),
|
||||
await self._check_cache_health(),
|
||||
await self._check_filename_conflicts(),
|
||||
self._check_sidecar_mirror_orphans(),
|
||||
self._check_ui_version(client_version, app_version),
|
||||
]
|
||||
|
||||
@@ -1042,6 +1045,71 @@ class DoctorHandler:
|
||||
logger.error("Error exporting doctor bundle: %s", exc, exc_info=True)
|
||||
return web.json_response({"success": False, "error": str(exc)}, status=500)
|
||||
|
||||
def _check_sidecar_mirror_orphans(self) -> dict[str, Any]:
|
||||
"""Flag centralized sidecars stranded by a moved/removed model root.
|
||||
|
||||
Centralized sidecars live under a per-root mirror directory. A root
|
||||
that was moved, renamed, or dropped from the configuration leaves its
|
||||
mirror behind; without this check the loss is silent, because the
|
||||
scanner simply rebuilds default metadata at the new location.
|
||||
"""
|
||||
|
||||
actions = [{"id": "open-settings", "label": "Open Settings"}]
|
||||
try:
|
||||
mode = get_storage_mode()
|
||||
except Exception as exc: # pragma: no cover - defensive fallback
|
||||
logger.debug("Doctor: sidecar mode lookup failed: %s", exc)
|
||||
mode = "alongside"
|
||||
|
||||
if mode != "centralized":
|
||||
return {
|
||||
"id": "sidecar_mirror_orphans",
|
||||
"title": "Centralized Sidecars",
|
||||
"status": "ok",
|
||||
"summary": "Sidecar metadata is stored alongside the models.",
|
||||
"details": [],
|
||||
"actions": actions,
|
||||
}
|
||||
|
||||
try:
|
||||
orphans = get_unmatched_sidecar_components()
|
||||
except Exception as exc: # pragma: no cover - defensive fallback
|
||||
logger.warning("Doctor: sidecar orphan check failed: %s", exc)
|
||||
orphans = []
|
||||
|
||||
if not orphans:
|
||||
return {
|
||||
"id": "sidecar_mirror_orphans",
|
||||
"title": "Centralized Sidecars",
|
||||
"status": "ok",
|
||||
"summary": "Every mirrored sidecar directory is linked to a model root.",
|
||||
"details": [f"Root: {describe_sidecar_root().get('root', '')}"],
|
||||
"actions": actions,
|
||||
}
|
||||
|
||||
details = [
|
||||
"Metadata (favorites, notes, tags, usage tips) for these models is on disk but is not being read.",
|
||||
"This usually means a model root was moved, renamed, or removed. Restore the original root path in Settings; the mirror is re-linked automatically.",
|
||||
]
|
||||
details.extend(
|
||||
f"{item['component']} — last known root: {item['last_path'] or 'unknown'}"
|
||||
for item in orphans[:5]
|
||||
)
|
||||
if len(orphans) > 5:
|
||||
details.append(f"… and {len(orphans) - 5} more")
|
||||
return {
|
||||
"id": "sidecar_mirror_orphans",
|
||||
"title": "Centralized Sidecars",
|
||||
"status": "warning",
|
||||
"summary": (
|
||||
f"{len(orphans)} sidecar "
|
||||
f"director{'y' if len(orphans) == 1 else 'ies'} could not be "
|
||||
"linked to a configured model root."
|
||||
),
|
||||
"details": details,
|
||||
"actions": actions,
|
||||
}
|
||||
|
||||
async def _check_civitai_api_key(self) -> dict[str, Any]:
|
||||
api_key = (self._settings.get("civitai_api_key", "") or "").strip()
|
||||
if not api_key:
|
||||
|
||||
Reference in New Issue
Block a user