138 lines
3.8 KiB
Python
138 lines
3.8 KiB
Python
import json
|
|
from pathlib import Path
|
|
from typing import Any, Callable, NotRequired, TypedDict, cast
|
|
|
|
from utils import get_bundle_dir
|
|
|
|
class ConfigData(TypedDict):
|
|
discord_user_id: NotRequired[str]
|
|
volume: NotRequired[int]
|
|
|
|
Validator = Callable[[Any], bool]
|
|
Normalizer = Callable[[Any], Any]
|
|
|
|
class ConfigField(TypedDict):
|
|
default: Any
|
|
validator: Validator
|
|
normalizer: Normalizer
|
|
|
|
CONFIG_PATH = get_bundle_dir() / "config.json"
|
|
|
|
DISCORD_USER_KEY = "discord_user_id"
|
|
VOLUME_KEY = "volume"
|
|
|
|
CONFIG_SCHEMA: dict[str, ConfigField] = {
|
|
DISCORD_USER_KEY: {
|
|
"default": "",
|
|
"validator": lambda value: isinstance(value, str),
|
|
"normalizer": lambda value: str(value).strip(),
|
|
},
|
|
VOLUME_KEY: {
|
|
"default": 30,
|
|
"validator": lambda value: isinstance(value, int) and 0 <= value <= 100,
|
|
"normalizer": lambda value: max(0, min(int(value), 100)),
|
|
},
|
|
}
|
|
|
|
class ConfigManager:
|
|
def __init__(self, path: Path | None = None) -> None:
|
|
self.path = path or CONFIG_PATH
|
|
self._data: ConfigData = self._load()
|
|
self._dirty = False
|
|
|
|
# Lecture du fichier de configuration
|
|
def _load(self) -> ConfigData:
|
|
if not self.path.exists():
|
|
return {}
|
|
|
|
try:
|
|
with self.path.open("r", encoding="utf-8") as file:
|
|
data = json.load(file)
|
|
except (json.JSONDecodeError, OSError):
|
|
return {}
|
|
|
|
if not isinstance(data, dict):
|
|
return {}
|
|
|
|
return cast(ConfigData, data)
|
|
|
|
# Sauvegarde du fichier de configuration
|
|
def save(self) -> None:
|
|
if not self._dirty:
|
|
return
|
|
|
|
self.path.parent.mkdir(parents=True, exist_ok=True)
|
|
|
|
with self.path.open("w", encoding="utf-8") as file:
|
|
json.dump(self._data, file, indent=4, ensure_ascii=False)
|
|
|
|
self._dirty = False
|
|
|
|
def _get_field(self, key: str) -> ConfigField:
|
|
if key not in CONFIG_SCHEMA:
|
|
raise KeyError(f"Unknown config key: {key}")
|
|
return CONFIG_SCHEMA[key]
|
|
|
|
def get(self, key: str) -> Any:
|
|
field = self._get_field(key)
|
|
value = self._data.get(key, field["default"])
|
|
|
|
if not field["validator"](value):
|
|
return field["default"]
|
|
|
|
return value
|
|
|
|
def set(self, key: str, value: Any) -> None:
|
|
field = self._get_field(key)
|
|
|
|
normalized = field["normalizer"](value)
|
|
|
|
if not field["validator"](normalized):
|
|
raise ValueError(f"Invalid value for {key}")
|
|
|
|
if self._data.get(key) == normalized:
|
|
return
|
|
|
|
self._data[key] = normalized
|
|
self._dirty = True
|
|
|
|
def reset_all(self) -> None:
|
|
defaults: ConfigData = cast(
|
|
ConfigData,
|
|
{key: field["default"] for key, field in CONFIG_SCHEMA.items()},
|
|
)
|
|
self.save(defaults)
|
|
|
|
def get_all(self) -> ConfigData:
|
|
return cast(
|
|
ConfigData,
|
|
{key: self.get(key) for key in CONFIG_SCHEMA},
|
|
)
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# SETTERS MÉTIER
|
|
# ---------------------------------------------------------------------------
|
|
|
|
# Set Discord ID
|
|
def set_discord_user(self, user_id: str) -> None:
|
|
self.set(DISCORD_USER_KEY, user_id)
|
|
|
|
# Set volume
|
|
def set_volume(self, volume: int) -> None:
|
|
self.set(VOLUME_KEY, volume)
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# GETTERS MÉTIER
|
|
# ---------------------------------------------------------------------------
|
|
|
|
# Get discord ID
|
|
def get_default(self, key: str):
|
|
return CONFIG_SCHEMA[key]["default"]
|
|
|
|
# Get volume value
|
|
def get_discord_user(self) -> str:
|
|
return cast(str, self.get(DISCORD_USER_KEY))
|
|
|
|
def get_volume(self) -> int:
|
|
return cast(int, self.get(VOLUME_KEY))
|