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:
@@ -12,6 +12,7 @@ import pytest
|
||||
from aiohttp import web
|
||||
|
||||
from py.services.model_hash_index import ModelHashIndex
|
||||
from py.routes.handlers import misc_handlers
|
||||
from py.routes.handlers.misc_handlers import (
|
||||
BackupHandler,
|
||||
DoctorHandler,
|
||||
@@ -209,6 +210,108 @@ async def test_doctor_handler_reports_key_cache_and_ui_issues():
|
||||
assert diagnostic_map["ui_version"]["status"] == "warning"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_doctor_handler_flags_orphaned_centralized_sidecars(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
):
|
||||
async def civitai_factory():
|
||||
return DummyCivitaiClient()
|
||||
|
||||
handler = DoctorHandler(
|
||||
settings_service=DummySettings({"civitai_api_key": "token"}),
|
||||
civitai_client_factory=civitai_factory,
|
||||
scanner_factories=(),
|
||||
)
|
||||
monkeypatch.setattr(misc_handlers, "get_storage_mode", lambda: "centralized")
|
||||
monkeypatch.setattr(
|
||||
misc_handlers,
|
||||
"get_unmatched_sidecar_components",
|
||||
lambda: [
|
||||
{
|
||||
"component": "loras-abc12345",
|
||||
"basename": "loras",
|
||||
"last_path": "/mnt/old/loras",
|
||||
},
|
||||
{"component": "loras-def67890", "basename": "loras", "last_path": ""},
|
||||
],
|
||||
)
|
||||
|
||||
response = await handler.get_doctor_diagnostics(
|
||||
FakeRequest(method="GET") # pyright: ignore[reportArgumentType]
|
||||
)
|
||||
payload = _json_payload(response)
|
||||
item = {entry["id"]: entry for entry in payload["diagnostics"]}[
|
||||
"sidecar_mirror_orphans"
|
||||
]
|
||||
|
||||
assert item["status"] == "warning"
|
||||
assert "2 sidecar directories" in item["summary"]
|
||||
assert any("/mnt/old/loras" in line for line in item["details"])
|
||||
assert any("unknown" in line for line in item["details"])
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_doctor_handler_sidecar_check_ok_when_all_linked(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
):
|
||||
async def civitai_factory():
|
||||
return DummyCivitaiClient()
|
||||
|
||||
handler = DoctorHandler(
|
||||
settings_service=DummySettings({"civitai_api_key": "token"}),
|
||||
civitai_client_factory=civitai_factory,
|
||||
scanner_factories=(),
|
||||
)
|
||||
monkeypatch.setattr(misc_handlers, "get_storage_mode", lambda: "centralized")
|
||||
monkeypatch.setattr(misc_handlers, "get_unmatched_sidecar_components", lambda: [])
|
||||
monkeypatch.setattr(
|
||||
misc_handlers, "describe_sidecar_root", lambda: {"root": "/sidecars"}
|
||||
)
|
||||
|
||||
response = await handler.get_doctor_diagnostics(
|
||||
FakeRequest(method="GET") # pyright: ignore[reportArgumentType]
|
||||
)
|
||||
payload = _json_payload(response)
|
||||
item = {entry["id"]: entry for entry in payload["diagnostics"]}[
|
||||
"sidecar_mirror_orphans"
|
||||
]
|
||||
|
||||
assert item["status"] == "ok"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_doctor_handler_sidecar_check_skipped_alongside(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
):
|
||||
async def civitai_factory():
|
||||
return DummyCivitaiClient()
|
||||
|
||||
handler = DoctorHandler(
|
||||
settings_service=DummySettings({"civitai_api_key": "token"}),
|
||||
civitai_client_factory=civitai_factory,
|
||||
scanner_factories=(),
|
||||
)
|
||||
monkeypatch.setattr(misc_handlers, "get_storage_mode", lambda: "alongside")
|
||||
|
||||
def _unexpected():
|
||||
raise AssertionError("orphan lookup must not run in alongside mode")
|
||||
|
||||
monkeypatch.setattr(
|
||||
misc_handlers, "get_unmatched_sidecar_components", _unexpected
|
||||
)
|
||||
|
||||
response = await handler.get_doctor_diagnostics(
|
||||
FakeRequest(method="GET") # pyright: ignore[reportArgumentType]
|
||||
)
|
||||
payload = _json_payload(response)
|
||||
item = {entry["id"]: entry for entry in payload["diagnostics"]}[
|
||||
"sidecar_mirror_orphans"
|
||||
]
|
||||
|
||||
assert item["status"] == "ok"
|
||||
assert "alongside" in item["summary"]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_doctor_handler_can_repair_cache():
|
||||
scanner = DummyDoctorScanner(model_type="lora", raw_data=[])
|
||||
|
||||
Reference in New Issue
Block a user