mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-10-12 12:32:11 -03:00
fix(paths): keep business paths forward-slashed on Windows
Four places produced the host's separator (or a mixed path) where the app's
business form is forward-slashed, so on Windows the value could not be compared
with the one the rest of the app stores:
* `ModelScanner._calculate_folder` matched `file_path.startswith(root)` as raw
strings, so every forward-slashed path resolved to the library root and a
moved model was filed under the root instead of its folder. It now compares
root identities and slices the relative part off.
* `normalize_relative_folder` ran `os.path.normpath`, and ntpath folds forward
slashes back to backslashes: every folder parameter (scoped scan, create,
rename, delete) came back host-spelled and mismatched the stored folders.
* `_format_model_name_for_comfyui` returned `os.path.relpath` unchanged, so the
checkpoint/unet nodes and the metadata node emitted `unet\base.safetensors`
on Windows -- a name no ComfyUI widget list matches.
* `resolve_centralized_dir_for_dir` passed a component that spans two
directories ("<library>/<component>") whole to `os.path.join`, leaving a mixed
path ("...\comfyui/loras-1a2b3c4d\sub") that no native path equals.
Also hands the rename re-key callback business paths, like every other absolute
path the move service returns.
This commit is contained in:
@@ -1,7 +1,8 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
import fnmatch
|
import fnmatch
|
||||||
import os
|
|
||||||
import logging
|
import logging
|
||||||
|
import os
|
||||||
|
import posixpath
|
||||||
import shutil
|
import shutil
|
||||||
from typing import Any, Dict, List, Optional, Sequence, Set
|
from typing import Any, Dict, List, Optional, Sequence, Set
|
||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
@@ -36,12 +37,11 @@ def normalize_relative_folder(folder: str) -> str:
|
|||||||
if normalized.startswith("/") or (len(normalized) > 1 and normalized[1] == ":"):
|
if normalized.startswith("/") or (len(normalized) > 1 and normalized[1] == ":"):
|
||||||
raise ValueError("Folder path must be relative to a library root")
|
raise ValueError("Folder path must be relative to a library root")
|
||||||
|
|
||||||
normalized = os.path.normpath(normalized)
|
# posixpath, not os.path: ntpath.normpath would fold the business form back
|
||||||
if (
|
# to backslashes, and every stored folder, cache entry and comparison in the
|
||||||
normalized == ".."
|
# app is forward-slashed.
|
||||||
or normalized.startswith("../")
|
normalized = posixpath.normpath(normalized)
|
||||||
or normalized.startswith(".." + os.sep)
|
if normalized == ".." or normalized.startswith("../"):
|
||||||
):
|
|
||||||
raise ValueError("Folder path must stay inside the library root")
|
raise ValueError("Folder path must stay inside the library root")
|
||||||
|
|
||||||
return normalized
|
return normalized
|
||||||
@@ -1062,7 +1062,12 @@ class ModelMoveService:
|
|||||||
|
|
||||||
new_relative = self._calculate_relative_folder(target)
|
new_relative = self._calculate_relative_folder(target)
|
||||||
await self._rename_folder_records(
|
await self._rename_folder_records(
|
||||||
previous_relative, new_relative, absolute_path, target
|
previous_relative,
|
||||||
|
new_relative,
|
||||||
|
# Business paths, like every other absolute path this service
|
||||||
|
# hands out: the scanner re-keys cache entries by prefix.
|
||||||
|
absolute_path.replace(os.sep, "/"),
|
||||||
|
target.replace(os.sep, "/"),
|
||||||
)
|
)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -3094,12 +3094,28 @@ class ModelScanner:
|
|||||||
return await MetadataManager.create_default_metadata(file_path, self.model_class)
|
return await MetadataManager.create_default_metadata(file_path, self.model_class)
|
||||||
|
|
||||||
def _calculate_folder(self, file_path: str) -> str:
|
def _calculate_folder(self, file_path: str) -> str:
|
||||||
"""Calculate the folder path for a model file"""
|
"""Return the library-relative folder holding *file_path*.
|
||||||
|
|
||||||
|
The path arrives in whichever spelling its caller holds: ComfyUI's own
|
||||||
|
(backslashes on Windows) or the forward-slashed business form used for
|
||||||
|
cache entries. A raw ``startswith(root)`` matched neither the mixed nor
|
||||||
|
the opposite spelling, so every forward-slashed path on Windows resolved
|
||||||
|
to the root folder and a moved model was filed under the library root.
|
||||||
|
Compare identities instead, then slice the relative part off.
|
||||||
|
"""
|
||||||
|
candidate = _root_identity(file_path)
|
||||||
|
if not candidate:
|
||||||
|
return ""
|
||||||
for root in self.get_model_roots():
|
for root in self.get_model_roots():
|
||||||
if file_path.startswith(root):
|
identity = _root_identity(root)
|
||||||
rel_path = os.path.relpath(file_path, root)
|
if not identity:
|
||||||
return os.path.dirname(rel_path).replace(os.path.sep, '/')
|
continue
|
||||||
return ''
|
if candidate == identity:
|
||||||
|
return ""
|
||||||
|
if candidate.startswith(f"{identity}/"):
|
||||||
|
relative = candidate[len(identity) + 1:]
|
||||||
|
return relative.rsplit("/", 1)[0] if "/" in relative else ""
|
||||||
|
return ""
|
||||||
|
|
||||||
def adjust_metadata(self, metadata, file_path, root_path):
|
def adjust_metadata(self, metadata, file_path, root_path):
|
||||||
"""Hook for subclasses: adjust metadata during scanning"""
|
"""Hook for subclasses: adjust metadata during scanning"""
|
||||||
|
|||||||
@@ -480,6 +480,19 @@ def _legacy_component(root_path: str) -> str:
|
|||||||
return f"{sanitize_path_component(os.path.basename(normalized))}-{digest}"
|
return f"{sanitize_path_component(os.path.basename(normalized))}-{digest}"
|
||||||
|
|
||||||
|
|
||||||
|
def _join_component(root: str, component: str) -> str:
|
||||||
|
"""Join a mirror component name onto *root* as a native path.
|
||||||
|
|
||||||
|
A component adopted from the pre-identity-map layout is reported as
|
||||||
|
``<library>/<component>`` -- one *name* spanning two directories. Handing it
|
||||||
|
whole to ``os.path.join`` left a mixed path on Windows
|
||||||
|
(``...\\comfyui/loras-1a2b3c4d\\sub``), which no natively built path
|
||||||
|
compares equal to, so it has to be split first.
|
||||||
|
"""
|
||||||
|
|
||||||
|
return os.path.join(root, *(part for part in component.split("/") if part))
|
||||||
|
|
||||||
|
|
||||||
def _list_component_dirs(sidecar_root: str) -> Set[str]:
|
def _list_component_dirs(sidecar_root: str) -> Set[str]:
|
||||||
"""Return the mirror component directories present under ``sidecar_root``.
|
"""Return the mirror component directories present under ``sidecar_root``.
|
||||||
|
|
||||||
@@ -556,7 +569,7 @@ def _mirror_dir_overlap(sidecar_root: str, component: str, root: str) -> int:
|
|||||||
|
|
||||||
if not component:
|
if not component:
|
||||||
return 0
|
return 0
|
||||||
mirror_names = _subdir_names(os.path.join(sidecar_root, component))
|
mirror_names = _subdir_names(_join_component(sidecar_root, component))
|
||||||
if not mirror_names:
|
if not mirror_names:
|
||||||
return 0
|
return 0
|
||||||
root_names = _subdir_names(root)
|
root_names = _subdir_names(root)
|
||||||
@@ -958,7 +971,7 @@ def resolve_centralized_dir_for_dir(
|
|||||||
if not component:
|
if not component:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
parts = [root, component]
|
parts = [_join_component(root, component)]
|
||||||
if rel_dir and rel_dir != os.curdir:
|
if rel_dir and rel_dir != os.curdir:
|
||||||
parts.extend(
|
parts.extend(
|
||||||
sanitize_path_component(part)
|
sanitize_path_component(part)
|
||||||
|
|||||||
+4
-2
@@ -303,8 +303,10 @@ def _format_model_name_for_comfyui(file_path: str, model_roots: list[str]) -> st
|
|||||||
norm_root += os.sep
|
norm_root += os.sep
|
||||||
|
|
||||||
if norm_file.startswith(norm_root):
|
if norm_file.startswith(norm_root):
|
||||||
# Use os.path.relpath to get relative path with OS-native separator
|
# ComfyUI names a model with forward slashes on every platform
|
||||||
return os.path.relpath(file_path, root)
|
# ("unet/base.safetensors"); os.path.relpath alone returns the
|
||||||
|
# host's separator, which no ComfyUI widget list matches on Windows.
|
||||||
|
return os.path.relpath(file_path, root).replace(os.sep, "/")
|
||||||
except (ValueError, TypeError):
|
except (ValueError, TypeError):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user