mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-03-21 13:12:12 -03:00
feat(settings): migrate settings to user config dir
This commit is contained in:
13
README.md
13
README.md
@@ -140,8 +140,11 @@ Enhance your Civitai browsing experience with our companion browser extension! S
|
|||||||
### Option 2: **Portable Standalone Edition** (No ComfyUI required)
|
### Option 2: **Portable Standalone Edition** (No ComfyUI required)
|
||||||
|
|
||||||
1. Download the [Portable Package](https://github.com/willmiao/ComfyUI-Lora-Manager/releases/download/v0.9.2/lora_manager_portable.7z)
|
1. Download the [Portable Package](https://github.com/willmiao/ComfyUI-Lora-Manager/releases/download/v0.9.2/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
|
2. Copy the provided `settings.json.example` file to your LoRA Manager settings folder and rename it to `settings.json`:
|
||||||
3. Edit `settings.json` to include your correct model folder paths and CivitAI API key
|
- **Windows:** `%APPDATA%/ComfyUI-LoRA-Manager/settings.json`
|
||||||
|
- **macOS:** `~/Library/Application Support/ComfyUI-LoRA-Manager/settings.json`
|
||||||
|
- **Linux:** `${XDG_CONFIG_HOME:-~/.config}/ComfyUI-LoRA-Manager/settings.json`
|
||||||
|
3. Edit the new `settings.json` to include your correct model folder paths and CivitAI API key
|
||||||
4. Run run.bat
|
4. Run run.bat
|
||||||
- To change the startup port, edit `run.bat` and modify the parameter (e.g. `--port 9001`)
|
- To change the startup port, edit `run.bat` and modify the parameter (e.g. `--port 9001`)
|
||||||
|
|
||||||
@@ -209,7 +212,7 @@ You can combine multiple patterns to create detailed, organized filenames for yo
|
|||||||
You can now run LoRA Manager independently from ComfyUI:
|
You can now run LoRA Manager independently from ComfyUI:
|
||||||
|
|
||||||
1. **For ComfyUI users**:
|
1. **For ComfyUI users**:
|
||||||
- Launch ComfyUI with LoRA Manager at least once to initialize the necessary path information in the `settings.json` file.
|
- Launch ComfyUI with LoRA Manager at least once to initialize the necessary path information in the `settings.json` file located in your user settings folder (see paths above).
|
||||||
- Make sure dependencies are installed: `pip install -r requirements.txt`
|
- Make sure dependencies are installed: `pip install -r requirements.txt`
|
||||||
- From your ComfyUI root directory, run:
|
- From your ComfyUI root directory, run:
|
||||||
```bash
|
```bash
|
||||||
@@ -222,7 +225,7 @@ You can now run LoRA Manager independently from ComfyUI:
|
|||||||
```
|
```
|
||||||
|
|
||||||
2. **For non-ComfyUI users**:
|
2. **For non-ComfyUI users**:
|
||||||
- Copy the provided `settings.json.example` file to create a new file named `settings.json`
|
- Copy the provided `settings.json.example` file to the LoRA Manager settings folder (`%APPDATA%/ComfyUI-LoRA-Manager/`, `~/Library/Application Support/ComfyUI-LoRA-Manager/`, or `${XDG_CONFIG_HOME:-~/.config}/ComfyUI-LoRA-Manager/`) and rename it to `settings.json`
|
||||||
- Edit `settings.json` to include your correct model folder paths and CivitAI API key
|
- Edit `settings.json` to include your correct model folder paths and CivitAI API key
|
||||||
- Install required dependencies: `pip install -r requirements.txt`
|
- Install required dependencies: `pip install -r requirements.txt`
|
||||||
- Run standalone mode:
|
- Run standalone mode:
|
||||||
@@ -231,6 +234,8 @@ You can now run LoRA Manager independently from ComfyUI:
|
|||||||
```
|
```
|
||||||
- Access the interface through your browser at: `http://localhost:8188/loras`
|
- Access the interface through your browser at: `http://localhost:8188/loras`
|
||||||
|
|
||||||
|
> **Note:** Existing installations automatically migrate the legacy `settings.json` from the plugin folder to the user settings directory the first time you launch this version.
|
||||||
|
|
||||||
This standalone mode provides a lightweight option for managing your model and recipe collection without needing to run the full ComfyUI environment, making it useful even for users who primarily use other stable diffusion interfaces.
|
This standalone mode provides a lightweight option for managing your model and recipe collection without needing to run the full ComfyUI environment, making it useful even for users who primarily use other stable diffusion interfaces.
|
||||||
|
|
||||||
## Testing & Coverage
|
## Testing & Coverage
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import logging
|
|||||||
import json
|
import json
|
||||||
import urllib.parse
|
import urllib.parse
|
||||||
|
|
||||||
|
from py.utils.settings_paths import ensure_settings_file
|
||||||
|
|
||||||
# Use an environment variable to control standalone mode
|
# Use an environment variable to control standalone mode
|
||||||
standalone_mode = os.environ.get("HF_HUB_DISABLE_TELEMETRY", "0") == "0"
|
standalone_mode = os.environ.get("HF_HUB_DISABLE_TELEMETRY", "0") == "0"
|
||||||
|
|
||||||
@@ -40,12 +42,12 @@ class Config:
|
|||||||
try:
|
try:
|
||||||
# Check if we're running in ComfyUI mode (not standalone)
|
# Check if we're running in ComfyUI mode (not standalone)
|
||||||
# Load existing settings
|
# Load existing settings
|
||||||
settings_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'settings.json')
|
settings_path = ensure_settings_file(logger)
|
||||||
settings = {}
|
settings = {}
|
||||||
if os.path.exists(settings_path):
|
if os.path.exists(settings_path):
|
||||||
with open(settings_path, 'r', encoding='utf-8') as f:
|
with open(settings_path, 'r', encoding='utf-8') as f:
|
||||||
settings = json.load(f)
|
settings = json.load(f)
|
||||||
|
|
||||||
# Update settings with paths
|
# Update settings with paths
|
||||||
settings['folder_paths'] = {
|
settings['folder_paths'] = {
|
||||||
'loras': self.loras_roots,
|
'loras': self.loras_roots,
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ import tempfile
|
|||||||
import asyncio
|
import asyncio
|
||||||
from aiohttp import web, ClientError
|
from aiohttp import web, ClientError
|
||||||
from typing import Dict, List
|
from typing import Dict, List
|
||||||
|
|
||||||
|
from py.utils.settings_paths import ensure_settings_file
|
||||||
from ..services.downloader import get_downloader
|
from ..services.downloader import get_downloader
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -121,7 +123,7 @@ class UpdateRoutes:
|
|||||||
current_dir = os.path.dirname(os.path.abspath(__file__))
|
current_dir = os.path.dirname(os.path.abspath(__file__))
|
||||||
plugin_root = os.path.dirname(os.path.dirname(current_dir))
|
plugin_root = os.path.dirname(os.path.dirname(current_dir))
|
||||||
|
|
||||||
settings_path = os.path.join(plugin_root, 'settings.json')
|
settings_path = ensure_settings_file(logger)
|
||||||
settings_backup = None
|
settings_backup = None
|
||||||
if os.path.exists(settings_path):
|
if os.path.exists(settings_path):
|
||||||
with open(settings_path, 'r', encoding='utf-8') as f:
|
with open(settings_path, 'r', encoding='utf-8') as f:
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
import os
|
|
||||||
import json
|
import json
|
||||||
|
import os
|
||||||
import logging
|
import logging
|
||||||
from typing import Any, Dict
|
from typing import Any, Dict
|
||||||
|
|
||||||
|
from py.utils.settings_paths import ensure_settings_file
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@@ -36,7 +38,7 @@ DEFAULT_SETTINGS: Dict[str, Any] = {
|
|||||||
|
|
||||||
class SettingsManager:
|
class SettingsManager:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.settings_file = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), 'settings.json')
|
self.settings_file = ensure_settings_file(logger)
|
||||||
self.settings = self._load_settings()
|
self.settings = self._load_settings()
|
||||||
self._migrate_setting_keys()
|
self._migrate_setting_keys()
|
||||||
self._ensure_default_settings()
|
self._ensure_default_settings()
|
||||||
|
|||||||
84
py/utils/settings_paths.py
Normal file
84
py/utils/settings_paths.py
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
"""Utilities for locating and migrating the LoRA Manager settings file."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from platformdirs import user_config_dir
|
||||||
|
|
||||||
|
|
||||||
|
APP_NAME = "ComfyUI-LoRA-Manager"
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def get_project_root() -> str:
|
||||||
|
"""Return the root directory of the project repository."""
|
||||||
|
|
||||||
|
return os.path.dirname(os.path.dirname(os.path.dirname(__file__)))
|
||||||
|
|
||||||
|
|
||||||
|
def get_legacy_settings_path() -> str:
|
||||||
|
"""Return the legacy location of ``settings.json`` within the project tree."""
|
||||||
|
|
||||||
|
return os.path.join(get_project_root(), "settings.json")
|
||||||
|
|
||||||
|
|
||||||
|
def get_settings_dir(create: bool = True) -> str:
|
||||||
|
"""Return the user configuration directory for the application.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
create: Whether to create the directory if it does not already exist.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The absolute path to the user configuration directory.
|
||||||
|
"""
|
||||||
|
|
||||||
|
config_dir = user_config_dir(APP_NAME, appauthor=False)
|
||||||
|
if create:
|
||||||
|
os.makedirs(config_dir, exist_ok=True)
|
||||||
|
return config_dir
|
||||||
|
|
||||||
|
|
||||||
|
def get_settings_file_path(create_dir: bool = True) -> str:
|
||||||
|
"""Return the path to ``settings.json`` in the user configuration directory."""
|
||||||
|
|
||||||
|
return os.path.join(get_settings_dir(create=create_dir), "settings.json")
|
||||||
|
|
||||||
|
|
||||||
|
def ensure_settings_file(logger: Optional[logging.Logger] = None) -> str:
|
||||||
|
"""Ensure the settings file resides in the user configuration directory.
|
||||||
|
|
||||||
|
If a legacy ``settings.json`` is detected in the project root it is migrated to
|
||||||
|
the platform-specific user configuration folder. The caller receives the path
|
||||||
|
to the settings file irrespective of whether a migration was needed.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
logger: Optional logger used for migration messages. Falls back to a
|
||||||
|
module level logger when omitted.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The absolute path to ``settings.json`` in the user configuration folder.
|
||||||
|
"""
|
||||||
|
|
||||||
|
logger = logger or _LOGGER
|
||||||
|
target_path = get_settings_file_path(create_dir=True)
|
||||||
|
legacy_path = get_legacy_settings_path()
|
||||||
|
|
||||||
|
if os.path.exists(legacy_path) and not os.path.exists(target_path):
|
||||||
|
try:
|
||||||
|
os.makedirs(os.path.dirname(target_path), exist_ok=True)
|
||||||
|
shutil.move(legacy_path, target_path)
|
||||||
|
logger.info("Migrated settings.json to %s", target_path)
|
||||||
|
except Exception as exc: # pragma: no cover - defensive fallback path
|
||||||
|
logger.warning("Failed to move legacy settings.json: %s", exc)
|
||||||
|
try:
|
||||||
|
shutil.copy2(legacy_path, target_path)
|
||||||
|
logger.info("Copied legacy settings.json to %s", target_path)
|
||||||
|
except Exception as copy_exc: # pragma: no cover - defensive fallback path
|
||||||
|
logger.error("Could not migrate settings.json: %s", copy_exc)
|
||||||
|
|
||||||
|
return target_path
|
||||||
|
|
||||||
@@ -13,7 +13,8 @@ dependencies = [
|
|||||||
"toml",
|
"toml",
|
||||||
"natsort",
|
"natsort",
|
||||||
"GitPython",
|
"GitPython",
|
||||||
"aiosqlite"
|
"aiosqlite",
|
||||||
|
"platformdirs"
|
||||||
]
|
]
|
||||||
|
|
||||||
[project.urls]
|
[project.urls]
|
||||||
|
|||||||
@@ -10,3 +10,4 @@ natsort
|
|||||||
GitPython
|
GitPython
|
||||||
aiosqlite
|
aiosqlite
|
||||||
beautifulsoup4
|
beautifulsoup4
|
||||||
|
platformdirs
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import os
|
|||||||
import sys
|
import sys
|
||||||
import json
|
import json
|
||||||
from py.middleware.cache_middleware import cache_control
|
from py.middleware.cache_middleware import cache_control
|
||||||
|
from py.utils.settings_paths import ensure_settings_file, get_settings_dir
|
||||||
|
|
||||||
# Set environment variable to indicate standalone mode
|
# Set environment variable to indicate standalone mode
|
||||||
os.environ["COMFYUI_LORA_MANAGER_STANDALONE"] = "1"
|
os.environ["COMFYUI_LORA_MANAGER_STANDALONE"] = "1"
|
||||||
@@ -32,7 +33,7 @@ class MockFolderPaths:
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
def get_folder_paths(folder_name):
|
def get_folder_paths(folder_name):
|
||||||
# Load paths from settings.json
|
# Load paths from settings.json
|
||||||
settings_path = os.path.join(os.path.dirname(__file__), 'settings.json')
|
settings_path = ensure_settings_file()
|
||||||
try:
|
try:
|
||||||
if os.path.exists(settings_path):
|
if os.path.exists(settings_path):
|
||||||
with open(settings_path, 'r', encoding='utf-8') as f:
|
with open(settings_path, 'r', encoding='utf-8') as f:
|
||||||
@@ -159,7 +160,7 @@ class StandaloneServer:
|
|||||||
self.app.router.add_get('/', self.handle_status)
|
self.app.router.add_get('/', self.handle_status)
|
||||||
|
|
||||||
# Add static route for example images if the path exists in settings
|
# Add static route for example images if the path exists in settings
|
||||||
settings_path = os.path.join(os.path.dirname(__file__), 'settings.json')
|
settings_path = ensure_settings_file(logger)
|
||||||
if os.path.exists(settings_path):
|
if os.path.exists(settings_path):
|
||||||
with open(settings_path, 'r', encoding='utf-8') as f:
|
with open(settings_path, 'r', encoding='utf-8') as f:
|
||||||
settings = json.load(f)
|
settings = json.load(f)
|
||||||
@@ -219,16 +220,19 @@ from py.lora_manager import LoraManager
|
|||||||
|
|
||||||
def validate_settings():
|
def validate_settings():
|
||||||
"""Validate that settings.json exists and has required configuration"""
|
"""Validate that settings.json exists and has required configuration"""
|
||||||
settings_path = os.path.join(os.path.dirname(__file__), 'settings.json')
|
settings_path = ensure_settings_file(logger)
|
||||||
if not os.path.exists(settings_path):
|
if not os.path.exists(settings_path):
|
||||||
logger.error("=" * 80)
|
logger.error("=" * 80)
|
||||||
logger.error("CONFIGURATION ERROR: settings.json file not found!")
|
logger.error("CONFIGURATION ERROR: settings.json file not found!")
|
||||||
logger.error("")
|
logger.error("")
|
||||||
|
logger.error("Expected location: %s", settings_path)
|
||||||
|
logger.error("")
|
||||||
logger.error("To run in standalone mode, you need to create a settings.json file.")
|
logger.error("To run in standalone mode, you need to create a settings.json file.")
|
||||||
logger.error("Please follow these steps:")
|
logger.error("Please follow these steps:")
|
||||||
logger.error("")
|
logger.error("")
|
||||||
logger.error("1. Copy the provided settings.json.example file to create a new file")
|
logger.error("1. Copy the provided settings.json.example file to create a new file")
|
||||||
logger.error(" named settings.json in the comfyui-lora-manager folder")
|
logger.error(" named settings.json inside the LoRA Manager settings folder:")
|
||||||
|
logger.error(" %s", get_settings_dir())
|
||||||
logger.error("")
|
logger.error("")
|
||||||
logger.error("2. Edit settings.json to include your correct model folder paths")
|
logger.error("2. Edit settings.json to include your correct model folder paths")
|
||||||
logger.error(" and CivitAI API key")
|
logger.error(" and CivitAI API key")
|
||||||
|
|||||||
@@ -3,21 +3,32 @@ import json
|
|||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from py.services.settings_manager import SettingsManager
|
from py.services.settings_manager import SettingsManager
|
||||||
|
from py.utils import settings_paths
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def manager(tmp_path, monkeypatch):
|
def manager(tmp_path, monkeypatch):
|
||||||
monkeypatch.setattr(SettingsManager, "_save_settings", lambda self: None)
|
monkeypatch.setattr(SettingsManager, "_save_settings", lambda self: None)
|
||||||
|
fake_settings_path = tmp_path / "settings.json"
|
||||||
|
monkeypatch.setattr(
|
||||||
|
"py.services.settings_manager.ensure_settings_file",
|
||||||
|
lambda logger=None: str(fake_settings_path),
|
||||||
|
)
|
||||||
mgr = SettingsManager()
|
mgr = SettingsManager()
|
||||||
mgr.settings_file = str(tmp_path / "settings.json")
|
mgr.settings_file = str(fake_settings_path)
|
||||||
return mgr
|
return mgr
|
||||||
|
|
||||||
|
|
||||||
def test_environment_variable_overrides_settings(tmp_path, monkeypatch):
|
def test_environment_variable_overrides_settings(tmp_path, monkeypatch):
|
||||||
monkeypatch.setattr(SettingsManager, "_save_settings", lambda self: None)
|
monkeypatch.setattr(SettingsManager, "_save_settings", lambda self: None)
|
||||||
monkeypatch.setenv("CIVITAI_API_KEY", "secret")
|
monkeypatch.setenv("CIVITAI_API_KEY", "secret")
|
||||||
|
fake_settings_path = tmp_path / "settings.json"
|
||||||
|
monkeypatch.setattr(
|
||||||
|
"py.services.settings_manager.ensure_settings_file",
|
||||||
|
lambda logger=None: str(fake_settings_path),
|
||||||
|
)
|
||||||
mgr = SettingsManager()
|
mgr = SettingsManager()
|
||||||
mgr.settings_file = str(tmp_path / "settings.json")
|
mgr.settings_file = str(fake_settings_path)
|
||||||
|
|
||||||
assert mgr.get("civitai_api_key") == "secret"
|
assert mgr.get("civitai_api_key") == "secret"
|
||||||
|
|
||||||
@@ -59,3 +70,21 @@ def test_delete_setting(manager):
|
|||||||
manager.set("example", 1)
|
manager.set("example", 1)
|
||||||
manager.delete("example")
|
manager.delete("example")
|
||||||
assert manager.get("example") is None
|
assert manager.get("example") is None
|
||||||
|
|
||||||
|
|
||||||
|
def test_migrates_legacy_settings_file(tmp_path, monkeypatch):
|
||||||
|
legacy_root = tmp_path / "legacy"
|
||||||
|
legacy_root.mkdir()
|
||||||
|
legacy_file = legacy_root / "settings.json"
|
||||||
|
legacy_file.write_text("{\"value\": 1}", encoding="utf-8")
|
||||||
|
|
||||||
|
target_dir = tmp_path / "config"
|
||||||
|
|
||||||
|
monkeypatch.setattr(settings_paths, "get_project_root", lambda: str(legacy_root))
|
||||||
|
monkeypatch.setattr(settings_paths, "user_config_dir", lambda *_, **__: str(target_dir))
|
||||||
|
|
||||||
|
migrated_path = settings_paths.ensure_settings_file()
|
||||||
|
|
||||||
|
assert migrated_path == str(target_dir / "settings.json")
|
||||||
|
assert (target_dir / "settings.json").exists()
|
||||||
|
assert not legacy_file.exists()
|
||||||
|
|||||||
Reference in New Issue
Block a user