85f79283 made _calculate_folder compare root identities, but sliced the
relative folder out of the identity itself -- and normalize_root_identity
folds case on Windows (or any drive-letter path), so every move recorded
the folder in lowercase. The sidebar then grew a lowercase twin branch
("Krea 2" vs "krea 2") on each move until the next disk walk healed it.
Slice from normalize_root_spelling instead, a case-preserving counterpart
that folds only separators and redundancies, with a length guard for the
rare Unicode case-fold that changes the string length.
55 lines
1.9 KiB
Python
55 lines
1.9 KiB
Python
"""Path identity helpers for model roots.
|
|
|
|
ComfyUI hands its model folders over in whatever spelling the host uses --
|
|
``os.path.normpath()`` output, which is all backslashes on Windows -- while LoRA
|
|
Manager's own root lists and cached model paths are forward-slashed. Comparing
|
|
the two spellings as raw strings makes one directory look like two roots, so every
|
|
root identity check goes through :func:`normalize_root_identity` (issue #1146).
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
import posixpath
|
|
|
|
|
|
def normalize_root_identity(path: str) -> str:
|
|
"""Return the comparable identity of a model root path.
|
|
|
|
Folds the separator style (``\\`` and ``/`` are both accepted as separators,
|
|
because the host may spell a Windows path either way), redundant and trailing
|
|
separators, and the case where the filesystem does not distinguish it: Windows
|
|
compares whole paths case-insensitively, and every platform folds a drive
|
|
letter (``E:`` == ``e:``). Symlinks are never resolved -- business paths stay
|
|
business paths, matching the rest of the codebase.
|
|
"""
|
|
|
|
if not isinstance(path, str):
|
|
return ""
|
|
candidate = path.strip()
|
|
if not candidate:
|
|
return ""
|
|
|
|
normalized = posixpath.normpath(candidate.replace("\\", "/"))
|
|
if os.name == "nt" or (len(normalized) >= 2 and normalized[1] == ":"):
|
|
return normalized.lower()
|
|
return normalized
|
|
|
|
|
|
def normalize_root_spelling(path: str) -> str:
|
|
"""Case-preserving counterpart of :func:`normalize_root_identity`.
|
|
|
|
Same separator and redundancy folding, but keeps the original casing. Use it
|
|
to slice a relative part out of a path with offsets computed from the
|
|
identity -- the identity may have folded case, so slicing it directly would
|
|
lose the on-disk spelling.
|
|
"""
|
|
|
|
if not isinstance(path, str):
|
|
return ""
|
|
candidate = path.strip()
|
|
if not candidate:
|
|
return ""
|
|
|
|
return posixpath.normpath(candidate.replace("\\", "/"))
|