diff --git a/README.md b/README.md index 7f02d6df..6095696d 100644 --- a/README.md +++ b/README.md @@ -151,6 +151,7 @@ Enhance your Civitai browsing experience with our companion browser extension! S 1. Download the [Portable Package](https://github.com/willmiao/ComfyUI-Lora-Manager/releases/download/v0.9.8/lora_manager_portable.7z) 2. Copy the provided `settings.json.example` file to create a new file named `settings.json` in `comfyui-lora-manager` folder. Only adjust the API key, optional language, and folder paths—the library registry is generated automatically at runtime. 3. Edit the new `settings.json` to include your correct model folder paths and CivitAI API key (or keep the placeholders until you are ready to configure them) + - Set `"use_portable_settings": true` if you want the configuration to remain inside the repository folder instead of your user settings directory. 4. Run run.bat - To change the startup port, edit `run.bat` and modify the parameter (e.g. `--port 9001`) @@ -233,6 +234,7 @@ You can now run LoRA Manager independently from ComfyUI: 2. **For non-ComfyUI users**: - Copy the provided `settings.json.example` file to create a new file named `settings.json`. Update the API key, optional language, and folder paths only—the library registry is created automatically when LoRA Manager starts. - Edit `settings.json` to include your correct model folder paths and CivitAI API key (you can leave the defaults until ready to configure them) + - Enable portable mode by setting `"use_portable_settings": true` if you prefer LoRA Manager to read and write the `settings.json` located in the project directory. - Install required dependencies: `pip install -r requirements.txt` - Run standalone mode: ```bash diff --git a/py/services/settings_manager.py b/py/services/settings_manager.py index ee4ed459..de9f3183 100644 --- a/py/services/settings_manager.py +++ b/py/services/settings_manager.py @@ -28,6 +28,7 @@ CORE_USER_SETTING_KEYS: Tuple[str, ...] = ( DEFAULT_SETTINGS: Dict[str, Any] = { "civitai_api_key": "", + "use_portable_settings": False, "language": "en", "show_only_sfw": False, "enable_metadata_archive_db": False, diff --git a/py/utils/settings_paths.py b/py/utils/settings_paths.py index 290fd353..2ebc4cf4 100644 --- a/py/utils/settings_paths.py +++ b/py/utils/settings_paths.py @@ -2,6 +2,7 @@ from __future__ import annotations +import json import logging import os import shutil @@ -64,6 +65,11 @@ def ensure_settings_file(logger: Optional[logging.Logger] = None) -> str: """ logger = logger or _LOGGER + legacy_path = get_legacy_settings_path() + + if _should_use_portable_settings(legacy_path, logger): + return legacy_path + target_path = get_settings_file_path(create_dir=True) preferred_dir = user_config_dir(APP_NAME, appauthor=False) preferred_path = os.path.join(preferred_dir, "settings.json") @@ -71,7 +77,6 @@ def ensure_settings_file(logger: Optional[logging.Logger] = None) -> str: if os.path.abspath(target_path) != os.path.abspath(preferred_path): os.makedirs(preferred_dir, exist_ok=True) target_path = preferred_path - legacy_path = get_legacy_settings_path() if os.path.exists(legacy_path) and not os.path.exists(target_path): try: @@ -88,3 +93,34 @@ def ensure_settings_file(logger: Optional[logging.Logger] = None) -> str: return target_path + +def _should_use_portable_settings(path: str, logger: logging.Logger) -> bool: + """Return ``True`` when the repository settings file enables portable mode.""" + + if not os.path.exists(path): + return False + + try: + with open(path, "r", encoding="utf-8") as handle: + payload = json.load(handle) + except json.JSONDecodeError as exc: + logger.warning("Failed to parse %s for portable mode flag: %s", path, exc) + return False + except OSError as exc: + logger.warning("Could not read %s to determine portable mode: %s", path, exc) + return False + + if not isinstance(payload, dict): + logger.debug("Portable settings file %s does not contain a JSON object", path) + return False + + flag = payload.get("use_portable_settings") + if isinstance(flag, bool): + return flag + + if flag is not None: + logger.warning( + "Ignoring non-boolean use_portable_settings value in %s", path + ) + return False + diff --git a/settings.json.example b/settings.json.example index 673aa76d..f2479209 100644 --- a/settings.json.example +++ b/settings.json.example @@ -1,4 +1,5 @@ { + "use_portable_settings": false, "civitai_api_key": "your_civitai_api_key_here", "folder_paths": { "loras": [ diff --git a/tests/services/test_settings_manager.py b/tests/services/test_settings_manager.py index 1643f24f..c1095e8d 100644 --- a/tests/services/test_settings_manager.py +++ b/tests/services/test_settings_manager.py @@ -246,6 +246,27 @@ def test_migrates_legacy_settings_file(tmp_path, monkeypatch): assert not legacy_file.exists() +def test_uses_portable_settings_file_when_enabled(tmp_path, monkeypatch): + repo_root = tmp_path / "repo" + repo_root.mkdir() + repo_settings = repo_root / "settings.json" + repo_settings.write_text( + json.dumps({"use_portable_settings": True, "value": 1}), + encoding="utf-8", + ) + + user_dir = tmp_path / "user" + + monkeypatch.setattr(settings_paths, "get_project_root", lambda: str(repo_root)) + monkeypatch.setattr(settings_paths, "user_config_dir", lambda *_, **__: str(user_dir)) + + resolved = settings_paths.ensure_settings_file() + + assert resolved == str(repo_settings) + assert repo_settings.exists() + assert not user_dir.exists() + + def test_migrate_creates_default_library(manager): libraries = manager.get_libraries() assert "default" in libraries