Files
ComfyUI-Lora-Manager/py/utils/recipes_paths.py
T
Will Miao 1805519f4f fix(sidebar): keep the recipes storage dir out of folder management
The default recipes dir (<first lora root>/recipes) is auto-created by the
recipe scanner and recorded by the model-root walks as an empty folder, so
it showed up in the folder sidebar — and since recipe files are not model
weight files, the folder delete endpoint's model_count check let a
right-click delete wipe the whole recipe library via shutil.rmtree.

- scan walks (full scan, reconcile, all-folders backfill) no longer descend
  into the effective recipes dir, and get_all_folders() filters it out of
  snapshots persisted before the exclusion existed
- delete_folder/rename_folder refuse the recipes dir and any ancestor of
  it with code "protected"
- the delete modal renders a protected state (confirm hidden, per-row
  checkbox disabled) instead of a deletable 'no models' prediction
- RecipeScanner.recipes_dir now shares the side-effect-free resolver in
  py/utils/recipes_paths.py
2026-10-07 19:38:28 +08:00

66 lines
2.3 KiB
Python

"""Resolution of the recipe library's storage directory.
Kept side-effect free (no directory creation): model scanners and file
services consult these helpers on hot paths, where silently materializing
the folder would be a surprise. ``RecipeScanner.recipes_dir`` remains the
write endpoint and adds the ``makedirs`` itself.
"""
from __future__ import annotations
import os
def get_effective_recipes_dir() -> str:
"""Resolve the directory recipe data lives in. May not exist on disk.
Mirrors ``RecipeScanner.recipes_dir`` minus the auto-create: the
``recipes_path`` setting wins; otherwise the default is ``recipes``
under the first configured lora root (``config.loras_roots`` is already
sorted case-insensitively). Returns ``""`` when no lora root exists.
"""
# Local imports: services -> config -> utils dependency direction must
# not pick up a utils -> services edge at module import time.
from ..services.settings_manager import get_settings_manager
from ..config import config
custom_recipes_dir = get_settings_manager().get("recipes_path", "")
if isinstance(custom_recipes_dir, str) and custom_recipes_dir.strip():
return os.path.abspath(
os.path.normpath(os.path.expanduser(custom_recipes_dir.strip()))
)
lora_roots = [
path
for path in (getattr(config, "loras_roots", None) or [])
if isinstance(path, str) and path.strip()
]
if not lora_roots:
return ""
return os.path.abspath(os.path.join(lora_roots[0], "recipes"))
def normalized_recipes_dir_key() -> str:
"""Comparison key (normcase + abspath) for the recipes dir; "" if unknown."""
try:
recipes_dir = get_effective_recipes_dir()
except Exception: # pragma: no cover - never break a scan over settings access
return ""
if not recipes_dir:
return ""
return os.path.normcase(os.path.abspath(recipes_dir))
def path_is_or_contains_recipes_dir(path: str) -> bool:
"""True when *path* is the recipes dir or one of its ancestors.
Deleting or renaming an ancestor wipes the recipe library just the same,
so folder operations must refuse both shapes.
"""
key = normalized_recipes_dir_key()
if not key:
return False
candidate = os.path.normcase(os.path.abspath(path))
return candidate == key or key.startswith(candidate + os.sep)