fix(settings): normalize default root path comparisons

This commit is contained in:
Will Miao
2026-04-21 09:43:37 +08:00
parent 1eeba666f5
commit ef4923fd94
4 changed files with 96 additions and 8 deletions

View File

@@ -1,5 +1,6 @@
import os import os
import platform import platform
import posixpath
import threading import threading
from pathlib import Path from pathlib import Path
import folder_paths # type: ignore import folder_paths # type: ignore
@@ -25,6 +26,15 @@ standalone_mode = (
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
def _normalize_root_identity(path: str) -> str:
"""Normalize a root path for comparisons across slash styles."""
normalized = posixpath.normpath(path.strip().replace("\\", "/"))
if len(normalized) >= 2 and normalized[1] == ":":
return normalized.lower()
return normalized
def _resolve_valid_default_root( def _resolve_valid_default_root(
current: str, primary_paths: List[str], allowed_paths: List[str], name: str current: str, primary_paths: List[str], allowed_paths: List[str], name: str
) -> str: ) -> str:
@@ -37,14 +47,17 @@ def _resolve_valid_default_root(
if not isinstance(path, str): if not isinstance(path, str):
continue continue
stripped = path.strip() stripped = path.strip()
if not stripped or stripped in seen: if not stripped:
continue continue
seen.add(stripped) identity = _normalize_root_identity(stripped)
if identity in seen:
continue
seen.add(identity)
fallback_paths.append(stripped) fallback_paths.append(stripped)
allowed = set(fallback_paths) allowed = {_normalize_root_identity(path) for path in fallback_paths}
if current and current in allowed: if current and _normalize_root_identity(current) in allowed:
return current return current
if not valid_paths: if not valid_paths:

View File

@@ -2,6 +2,7 @@ import asyncio
import copy import copy
import json import json
import os import os
import posixpath
import shutil import shutil
import tempfile import tempfile
import logging import logging
@@ -103,6 +104,15 @@ DEFAULT_SETTINGS: Dict[str, Any] = {
} }
def _normalize_root_identity(path: str) -> str:
"""Normalize a root path for equality checks across slash styles."""
normalized = posixpath.normpath(path.strip().replace("\\", "/"))
if len(normalized) >= 2 and normalized[1] == ":":
return normalized.lower()
return normalized
class SettingsManager: class SettingsManager:
def __init__(self): def __init__(self):
self.settings_file = ensure_settings_file(logger) self.settings_file = ensure_settings_file(logger)
@@ -773,7 +783,7 @@ class SettingsManager:
return False return False
allowed_roots = self._get_allowed_roots(key) allowed_roots = self._get_allowed_roots(key)
if current and current in allowed_roots: if current and _normalize_root_identity(current) in allowed_roots:
return False return False
self.settings[setting_key] = primary_candidates[0] self.settings[setting_key] = primary_candidates[0]
@@ -824,16 +834,19 @@ class SettingsManager:
if not isinstance(value, str): if not isinstance(value, str):
continue continue
normalized = value.strip() normalized = value.strip()
if not normalized or normalized in seen: if not normalized:
continue continue
seen.add(normalized) identity = _normalize_root_identity(normalized)
if identity in seen:
continue
seen.add(identity)
candidates.append(normalized) candidates.append(normalized)
return candidates return candidates
def _get_allowed_roots(self, key: str) -> set[str]: def _get_allowed_roots(self, key: str) -> set[str]:
"""Return all valid roots for a model type, including extra roots.""" """Return all valid roots for a model type, including extra roots."""
return set(self._get_valid_root_candidates(key)) return {_normalize_root_identity(path) for path in self._get_valid_root_candidates(key)}
def _check_environment_variables(self) -> None: def _check_environment_variables(self) -> None:
"""Check for environment variables and update settings if needed""" """Check for environment variables and update settings if needed"""

View File

@@ -402,6 +402,49 @@ def test_save_paths_keeps_default_roots_in_extra_paths(monkeypatch: pytest.Monke
assert fake_settings.payload["activate"] is True assert fake_settings.payload["activate"] is True
def test_save_paths_keeps_default_roots_in_extra_paths_with_windows_slash_mismatch(
monkeypatch: pytest.MonkeyPatch, tmp_path
):
folder_paths = _setup_config_environment(monkeypatch, tmp_path)
class FakeSettingsService:
active_library = "comfyui"
def get_libraries(self):
return {
"comfyui": {
"folder_paths": {key: list(value) for key, value in folder_paths.items()},
"extra_folder_paths": {
"loras": ["U:\\Lora7\\Loras"],
"checkpoints": ["U:\\Lora7\\Models"],
"embeddings": [],
},
"default_lora_root": "U:/Lora7/Loras",
"default_checkpoint_root": "U:/Lora7/Models",
"default_embedding_root": folder_paths["embeddings"][0],
}
}
def rename_library(self, *_):
raise AssertionError("rename_library should not be invoked")
def get_active_library_name(self):
return self.active_library
def upsert_library(self, name: str, **payload):
self.name = name
self.payload = payload
fake_settings = FakeSettingsService()
monkeypatch.setattr(settings_manager_module, "settings", fake_settings)
config_module.Config()
assert fake_settings.name == "comfyui"
assert fake_settings.payload["default_lora_root"] == "U:/Lora7/Loras"
assert fake_settings.payload["default_checkpoint_root"] == "U:/Lora7/Models"
def test_save_paths_repairs_empty_default_roots_to_extra_paths_when_primary_missing( def test_save_paths_repairs_empty_default_roots_to_extra_paths_when_primary_missing(
monkeypatch: pytest.MonkeyPatch, tmp_path monkeypatch: pytest.MonkeyPatch, tmp_path
): ):

View File

@@ -359,6 +359,25 @@ def test_auto_set_default_roots_keeps_valid_extra_values(manager):
assert manager.get("default_embedding_root") == "/extra-embeddings" assert manager.get("default_embedding_root") == "/extra-embeddings"
def test_auto_set_default_roots_keeps_valid_extra_values_with_windows_slash_mismatch(manager):
manager.settings["default_lora_root"] = "U:/Lora7/Loras"
manager.settings["default_checkpoint_root"] = "U:/Lora7/Models"
manager.settings["folder_paths"] = {
"loras": ["R:/ComfyUI/models/loras"],
"checkpoints": ["R:/ComfyUI/models/checkpoints"],
}
manager.settings["extra_folder_paths"] = {
"loras": ["U:\\Lora7\\Loras"],
"checkpoints": ["U:\\Lora7\\Models"],
}
manager._auto_set_default_roots()
assert manager.get("default_lora_root") == "U:/Lora7/Loras"
assert manager.get("default_checkpoint_root") == "U:/Lora7/Models"
def test_auto_set_default_roots_falls_back_to_extra_when_primary_missing(manager): def test_auto_set_default_roots_falls_back_to_extra_when_primary_missing(manager):
manager.settings["default_lora_root"] = "" manager.settings["default_lora_root"] = ""
manager.settings["folder_paths"] = {"loras": []} manager.settings["folder_paths"] = {"loras": []}