Files
KVM_Switch/app/config.py
T
2026-03-27 15:43:44 +01:00

106 lines
3.1 KiB
Python

from __future__ import annotations
from dataclasses import dataclass
import json
from pathlib import Path
from threading import Lock
PROJECT_ROOT = Path(__file__).resolve().parent.parent
CONFIG_PATH = PROJECT_ROOT / "config.json"
SUPPORTED_TARGET_PORTS = {
"DP1": {"ddm_input": "DP1", "input_codes": {15}},
"DP2": {"ddm_input": "DP2", "input_codes": {19, 16}},
"HDMI": {"ddm_input": "HDMI", "input_codes": {17}},
}
@dataclass(slots=True)
class AppConfig:
device_port: str = "DP1"
auxiliary_monitor_id: str | None = None
@classmethod
def from_dict(cls, data: dict[str, object]) -> "AppConfig":
return cls(
device_port=_coerce_port_name(data.get("device_port"), "DP1"),
auxiliary_monitor_id=_coerce_aux_monitor_id(data.get("auxiliary_monitor_id")),
)
def to_dict(self) -> dict[str, str | None]:
return {
"device_port": self.device_port,
"auxiliary_monitor_id": self.auxiliary_monitor_id,
}
def validate(self) -> list[str]:
errors: list[str] = []
supported = ", ".join(SUPPORTED_TARGET_PORTS)
if self.device_port not in SUPPORTED_TARGET_PORTS:
errors.append(f"Device Port must be one of: {supported}.")
return errors
class ConfigStore:
def __init__(self, path: Path = CONFIG_PATH):
self.path = path
self._lock = Lock()
self._config = self._load_from_disk()
def get(self) -> AppConfig:
with self._lock:
return AppConfig(
device_port=self._config.device_port,
auxiliary_monitor_id=self._config.auxiliary_monitor_id,
)
def save(self, config: AppConfig) -> AppConfig:
errors = config.validate()
if errors:
raise ValueError("; ".join(errors))
with self._lock:
self.path.write_text(
json.dumps(config.to_dict(), indent=2),
encoding="utf-8",
)
self._config = AppConfig(
device_port=config.device_port,
auxiliary_monitor_id=config.auxiliary_monitor_id,
)
return AppConfig(
device_port=self._config.device_port,
auxiliary_monitor_id=self._config.auxiliary_monitor_id,
)
def _load_from_disk(self) -> AppConfig:
if not self.path.exists():
return AppConfig()
try:
data = json.loads(self.path.read_text(encoding="utf-8"))
except (OSError, json.JSONDecodeError):
return AppConfig()
if not isinstance(data, dict):
return AppConfig()
return AppConfig.from_dict(data)
def _coerce_port_name(value: object, default: str) -> str:
if isinstance(value, str):
normalized = value.strip().upper()
if normalized in SUPPORTED_TARGET_PORTS:
return normalized
return default
def _coerce_aux_monitor_id(value: object) -> str | None:
if isinstance(value, str):
normalized = value.strip()
if normalized:
return normalized
return None