from pathlib import Path from dataclasses import dataclass from typing import Optional, Dict import json import os # Check for environment variable override (used in Docker) if os.getenv("DOSFRONTEND_CONFIG_DIR"): DOSFRONTEND_CONFIG_DIR: Path = Path(os.getenv("DOSFRONTEND_CONFIG_DIR")) else: # Default to XDG config directory for regular installations XDG_CONFIG_HOME: Path = Path(Path.home()).joinpath(".config") DOSFRONTEND_CONFIG_DIR: Path = XDG_CONFIG_HOME.joinpath("dosfrontend") DOSFRONTEND_CONFIG_FILE: Path = DOSFRONTEND_CONFIG_DIR.joinpath("config.json") @dataclass class Config: path: Path = DOSFRONTEND_CONFIG_FILE rom_path: Path = DOSFRONTEND_CONFIG_DIR.joinpath("roms") metadata_path: Path = DOSFRONTEND_CONFIG_DIR.joinpath("metadata") database_path: Path = DOSFRONTEND_CONFIG_DIR.joinpath("roms.db") images_path: Path = DOSFRONTEND_CONFIG_DIR.joinpath("images") host: str = "localhost" port: int = 8080 websocket_port: int = 8081 igdb_api_key: str = "" igdb_client_id: str = "" def __init__(self, path: Optional[Path] = None): if path: self.path = path self.load() def load_env_secrets(self) -> Dict[str, str] | None: secrets: Dict[str, str] = {} igdb_api_key = os.getenv("IGDB_SECRET_KEY") igdb_client_id = os.getenv("IGDB_CLIENT_ID") if not igdb_api_key or not igdb_client_id: file_path: Path = Path(__file__) env_path: Path = file_path.parent.parent.parent.joinpath(".env") if not env_path.exists(): return else: with env_path.open('r') as f: for line in f: if line.startswith("#") or "=" not in line: continue key, value = line.strip().split("=", 1) key, value = key.strip(), value.strip('"').strip("'") secrets[key] = value f.close() if secrets.get("IGDB_SECRET_KEY") and secrets.get("IGDB_CLIENT_ID"): return secrets else: return None else: secrets = { "IGDB_SECRET_KEY": igdb_api_key, "IGDB_CLIENT_ID": igdb_client_id, } return secrets def to_dict(self) -> dict: return { "rom_path": str(self.rom_path), "metadata_path": str(self.metadata_path), "database_path": str(self.database_path), "images_path": str(self.images_path), "host": self.host, "port": self.port, "websocket_port": self.websocket_port, "igdb_api_key": self.igdb_api_key, "igdb_client_id": self.igdb_client_id, } def save(self): # Ensure config directory exists if not self.path.parent.exists(): self.path.parent.mkdir(parents=True, exist_ok=True) # Create directories if they don't exist if not self.rom_path.exists(): self.rom_path.mkdir(parents=True, exist_ok=True) if not self.metadata_path.exists(): self.metadata_path.mkdir(parents=True, exist_ok=True) if not self.images_path.exists(): self.images_path.mkdir(parents=True, exist_ok=True) # Write configuration to file with open(self.path, 'w') as f: json.dump(self.to_dict(), f, indent=4) def load(self) -> "Config": if self.path.exists(): with open(self.path, 'r') as f: data = json.load(f) self.rom_path = Path(data.get("rom_path", str(self.rom_path))) self.metadata_path = Path(data.get("metadata_path", str(self.metadata_path))) self.database_path = Path(data.get("database_path", str(self.database_path))) self.images_path = Path(data.get("images_path", str(self.images_path))) self.host = data.get("host", self.host) self.port = data.get("port", self.port) self.websocket_port = data.get("websocket_port", self.websocket_port) self.igdb_api_key = data.get("igdb_api_key", self.igdb_api_key) self.igdb_client_id = data.get("igdb_client_id", self.igdb_client_id) # Load environment secrets if API keys are still empty if self.igdb_api_key == "" or self.igdb_client_id == "": secrets = self.load_env_secrets() if secrets: self.igdb_api_key = secrets.get("IGDB_SECRET_KEY", self.igdb_api_key) self.igdb_client_id = secrets.get("IGDB_CLIENT_ID", self.igdb_client_id) else: # Config file doesn't exist, create it with defaults # Load environment secrets for initial setup secrets = self.load_env_secrets() if secrets: self.igdb_api_key = secrets.get("IGDB_SECRET_KEY", self.igdb_api_key) self.igdb_client_id = secrets.get("IGDB_CLIENT_ID", self.igdb_client_id) self.save() return self