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}}, "HDMI": {"ddm_input": "HDMI", "input_codes": {17}}, } @dataclass(slots=True) class AppConfig: device_port: str = "DP1" @classmethod def from_dict(cls, data: dict[str, object]) -> "AppConfig": return cls(device_port=_coerce_port_name(data.get("device_port"), "DP1")) def to_dict(self) -> dict[str, str]: return {"device_port": self.device_port} 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) 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) return AppConfig(device_port=self._config.device_port) 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