"""Read-only host and configured environment inspection.""" from __future__ import annotations from dataclasses import asdict, dataclass from datetime import datetime import os from pathlib import Path import platform import shutil import sys from typing import Any from .config import AppConfig from .prefix import proton_wineserver_candidates @dataclass(frozen=True) class EnvironmentCheck: name: str ok: bool value: str required: bool = True def to_dict(self) -> dict[str, str | bool]: return asdict(self) def collect_environment(config: AppConfig) -> dict[str, Any]: checks = environment_checks(config) return { "collected_at": datetime.now().astimezone().isoformat(), "host": { "platform": platform.platform(), "machine": platform.machine(), "python": sys.version, "executable": sys.executable, "cwd": os.getcwd(), }, "config": config.to_dict(), "checks": [check.to_dict() for check in checks], "ok": all(check.ok for check in checks if check.required), } def environment_checks(config: AppConfig) -> list[EnvironmentCheck]: checks = [ _path_check("app_root", config.paths.app_root, "directory"), _path_check("live_prefix", config.paths.live_prefix, "directory"), _path_check("live_game", config.paths.live_game, "directory"), _path_check("logs", config.paths.logs, "directory"), _path_check("native_logs", config.paths.native_logs, "directory"), _path_check("proton7", config.proton7.proton, "file"), _any_path_check("proton7_wineserver", proton_wineserver_candidates(config)), _path_check("steam_root", config.proton7.steam_root, "directory"), _path_check("compat", config.proton7.compat, "directory"), _path_check("dedicated_prefix", config.proton7.prefix, "directory"), _path_check("game_exe", config.paths.live_game / config.game.exe, "file"), EnvironmentCheck( "game_argument", config.game.args == ("PTEU_REBORN",), " ".join(config.game.args), ), EnvironmentCheck( "virtual_desktop", config.game.virtual_desktop == "1600x900", config.game.virtual_desktop, ), _tool_check("strace", required=False), _tool_check("gdb", required=False), _i386_vulkan_check(), ] return checks def _path_check(name: str, path: Path, kind: str) -> EnvironmentCheck: ok = path.is_file() if kind == "file" else path.is_dir() return EnvironmentCheck(name, ok, str(path)) def _tool_check(name: str, required: bool) -> EnvironmentCheck: path = shutil.which(name) return EnvironmentCheck(name, path is not None, path or "not found", required) def _any_path_check(name: str, candidates: tuple[Path, ...]) -> EnvironmentCheck: found = next((path for path in candidates if path.is_file()), None) return EnvironmentCheck( name, found is not None, str(found) if found is not None else "not found: " + ", ".join(map(str, candidates)), ) def _i386_vulkan_check() -> EnvironmentCheck: candidates = ( Path("/usr/lib/i386-linux-gnu/libvulkan.so.1"), Path("/usr/lib/i386-linux-gnu/libvulkan.so"), ) found = next((path for path in candidates if path.exists()), None) return EnvironmentCheck( "i386_vulkan_loader", found is not None, str(found) if found is not None else "not found", )