Files
Button/webconfig/config_store.py
T
2026-06-17 04:55:15 +03:00

122 lines
3.3 KiB
Python

#!/usr/bin/env python3
"""Config storage for the ButtonTask web configurator.
Reads/writes the same JSON config file consumed by the Qt5 application.
Defaults here must stay in sync with ConfigManager::ensureDefaults() in C++.
"""
import copy
import json
import os
import tempfile
from pathlib import Path
ALLOWED_ICON_EXT = {".png", ".jpg", ".jpeg", ".bmp", ".svg", ".gif"}
def config_path():
"""Resolve the active config path.
Priority:
1. BUTTONTASK_CONFIG environment variable.
2. <repo>/config/config.json next to this package (dev fallback).
"""
env = os.environ.get("BUTTONTASK_CONFIG")
if env:
return Path(env)
# webconfig/ -> repo root -> config/config.json
return (Path(__file__).resolve().parent.parent / "config" / "config.json")
def _defaults():
return {
"version": 2,
"layout": {"mode": "grid", "columns": 2, "spacing": 10, "showLabels": True},
"background": {
"type": "gradient",
"color1": "#00007b",
"color2": "#7b007b",
"animated": True,
"imagePath": "",
},
"feedback": {
"okColor": "#00cc44",
"errorColor": "#cc2200",
"okWidth": 6,
"errorWidth": 6,
"glowRadius": 20,
},
"settings": {
"darkMode": True,
"password": "admin",
"kioskMode": False,
"iconsDir": "./icons",
"webPort": 8080,
},
"buttons": [],
}
def ensure_defaults(cfg):
"""Fill in any missing sections/keys without overwriting existing values."""
base = _defaults()
if not isinstance(cfg, dict):
return base
cfg.setdefault("version", base["version"])
for section in ("layout", "background", "feedback", "settings"):
sec = cfg.setdefault(section, {})
if not isinstance(sec, dict):
sec = {}
cfg[section] = sec
for k, v in base[section].items():
sec.setdefault(k, v)
if not isinstance(cfg.get("buttons"), list):
cfg["buttons"] = []
return cfg
def load_config():
p = config_path()
if not p.exists():
cfg = ensure_defaults(_defaults())
# point iconsDir at a sensible absolute default for the web side
cfg["settings"]["iconsDir"] = str((p.parent.parent / "icons"))
return cfg
with p.open("r", encoding="utf-8") as f:
cfg = json.load(f)
return ensure_defaults(cfg)
def save_config(cfg):
p = config_path()
p.parent.mkdir(parents=True, exist_ok=True)
fd, tmp = tempfile.mkstemp(prefix=".cfg.", suffix=".tmp", dir=str(p.parent))
try:
with os.fdopen(fd, "w", encoding="utf-8") as f:
json.dump(cfg, f, indent=4, ensure_ascii=False)
os.replace(tmp, p)
except Exception:
if os.path.exists(tmp):
os.unlink(tmp)
raise
def icons_dir():
cfg = load_config()
d = cfg.get("settings", {}).get("iconsDir") or "./icons"
p = Path(d)
if not p.is_absolute():
p = (config_path().parent.parent / d).resolve()
p.mkdir(parents=True, exist_ok=True)
return p
def list_icons():
return sorted(
f.name for f in icons_dir().iterdir()
if f.is_file() and f.suffix.lower() in ALLOWED_ICON_EXT
)
def default_config():
return copy.deepcopy(_defaults())