mirror of
https://github.com/openswarm-ai/openswarm.git
synced 2026-08-21 04:02:22 +02:00
77 lines
2.7 KiB
Python
77 lines
2.7 KiB
Python
"""Settings persistence primitives (read/write/migrate the settings.json file).
|
|
|
|
A leaf: imports only settings.models + config.paths, never service or
|
|
nine_router. Lets service.client reach load/save downward instead of looping
|
|
back up through settings.settings.
|
|
"""
|
|
|
|
import json
|
|
import os
|
|
import tempfile
|
|
import threading
|
|
import time
|
|
|
|
from backend.config.paths import SETTINGS_DIR as DATA_DIR
|
|
from backend.apps.settings.models import AppSettings, DEFAULT_SYSTEM_PROMPT
|
|
|
|
SETTINGS_FILE = os.path.join(DATA_DIR, "settings.json")
|
|
|
|
|
|
def _migrate_legacy_fields(raw: dict) -> dict:
|
|
"""Translate deprecated pre-launch field names ('managed', 'openswarm_auth_token') into production schema."""
|
|
if raw.get("connection_mode") == "managed":
|
|
raw["connection_mode"] = "openswarm-pro"
|
|
if "openswarm_auth_token" in raw and "openswarm_bearer_token" not in raw:
|
|
raw["openswarm_bearer_token"] = raw.pop("openswarm_auth_token")
|
|
return raw
|
|
|
|
|
|
def load_settings() -> AppSettings:
|
|
"""Load settings from JSON file, returning defaults if not found."""
|
|
if os.path.exists(SETTINGS_FILE):
|
|
with open(SETTINGS_FILE) as f:
|
|
raw = _migrate_legacy_fields(json.load(f))
|
|
settings = AppSettings(**raw)
|
|
if settings.default_system_prompt is None:
|
|
settings.default_system_prompt = DEFAULT_SYSTEM_PROMPT
|
|
return settings
|
|
return AppSettings()
|
|
|
|
|
|
# threading.Lock guards every SETTINGS_FILE write; works for sync paths and async run_in_executor paths.
|
|
_settings_write_lock = threading.Lock()
|
|
|
|
|
|
def _atomic_write_settings(payload: dict) -> None:
|
|
"""Atomic SETTINGS_FILE write; call via save_settings*, not directly."""
|
|
with _settings_write_lock:
|
|
os.makedirs(DATA_DIR, exist_ok=True)
|
|
fd, tmp = tempfile.mkstemp(prefix=".settings.", suffix=".tmp", dir=DATA_DIR)
|
|
try:
|
|
with os.fdopen(fd, "w", encoding="utf-8") as f:
|
|
json.dump(payload, f, indent=2)
|
|
# Windows: Defender can briefly lock the destination; one retry handles every real case.
|
|
for attempt in range(2):
|
|
try:
|
|
os.replace(tmp, SETTINGS_FILE)
|
|
return
|
|
except PermissionError:
|
|
if attempt == 1:
|
|
raise
|
|
time.sleep(0.05)
|
|
except Exception:
|
|
try:
|
|
os.unlink(tmp)
|
|
except OSError:
|
|
pass
|
|
raise
|
|
|
|
|
|
def save_settings(settings_obj: AppSettings) -> None:
|
|
"""Sync atomic persist; thread-safe. Async callers should prefer save_settings_async (Defender can stretch writes to 50-200ms)."""
|
|
_atomic_write_settings(settings_obj.model_dump())
|
|
|
|
|
|
def _save_settings(settings_obj: AppSettings) -> None:
|
|
save_settings(settings_obj)
|