# -*- coding: utf-8 -*- """ Abstrakte Basis-Anwendungsklasse für Trixy. Definiert die gemeinsame Struktur für Server, Client, Standalone und Trainer. """ from __future__ import annotations import asyncio import os import signal import sys from abc import ABC, abstractmethod from pathlib import Path from typing import Any, TYPE_CHECKING if TYPE_CHECKING: from trixy_core.sync.store import SyncStore from trixy_core.service.service_container import ServiceContainer from trixy_core.events.eventmanager import EventManager from trixy_core.config.config_manager import ConfigManager from trixy_core.utils.debug import pinfo, pdebug, perror, set_debug_mode, _setup_file_routing from trixy_core.utils.version import VERSION_STRING class IApplication(ABC): """ Abstrakte Basisklasse für alle Trixy-Anwendungsmodi. Stellt gemeinsame Infrastruktur bereit: - ServiceContainer für Service-Management - EventManager für Pub/Sub - ConfigManager für Konfiguration """ def __init__(self, debug: bool = False) -> None: """ Initialisiert die Anwendung. Args: debug: Debug-Modus aktivieren """ self._debug = debug set_debug_mode(debug) self._running = False self._shutdown_event = asyncio.Event() # Core-Komponenten initialisieren self._config_manager = ConfigManager() self._event_manager = EventManager(self) self._service_container = ServiceContainer(self) # Sync-Store (optional, wird von Standalone/Server gesetzt) self._sync_store: "SyncStore | None" = None # Basis-Pfade self._base_path = Path.cwd() @property def debug(self) -> bool: """Debug-Modus Status.""" return self._debug @property def running(self) -> bool: """Läuft die Anwendung?""" return self._running @property def config_manager(self) -> ConfigManager: """ConfigManager-Instanz.""" return self._config_manager @property def config(self) -> ConfigManager: """Alias für config_manager.""" return self._config_manager @property def events(self) -> EventManager: """EventManager-Instanz.""" return self._event_manager @property def services(self) -> ServiceContainer: """ServiceContainer-Instanz.""" return self._service_container @property def sync_store(self) -> "SyncStore | None": """SyncStore-Instanz (falls verfuegbar).""" return self._sync_store @property def base_path(self) -> Path: """Basis-Pfad der Anwendung.""" return self._base_path @abstractmethod async def initialize(self) -> None: """ Initialisiert die Anwendung. Wird vor dem Start aufgerufen, um Konfiguration zu laden und Services zu registrieren. """ pass @abstractmethod async def start(self) -> None: """ Startet die Anwendung. Startet alle Services und beginnt die Hauptschleife. """ pass @abstractmethod async def stop(self) -> None: """ Stoppt die Anwendung. Stoppt alle Services und gibt Ressourcen frei. """ pass async def run(self) -> None: """ Hauptmethode zum Ausführen der Anwendung. Initialisiert, startet und wartet auf Shutdown-Signal. """ pinfo(f"Trixy v{VERSION_STRING} wird gestartet...") # Signal-Handler einrichten loop = asyncio.get_running_loop() if sys.platform == "win32": # Windows: add_signal_handler nicht verfügbar for sig in (signal.SIGINT, signal.SIGTERM): signal.signal(sig, lambda s, f: loop.call_soon_threadsafe( lambda: asyncio.create_task(self._signal_handler()) )) else: for sig in (signal.SIGINT, signal.SIGTERM): loop.add_signal_handler( sig, lambda: asyncio.create_task(self._signal_handler()) ) try: await self.initialize() await self.start() self._running = True pinfo("Anwendung gestartet") # Warte auf Shutdown-Signal await self._shutdown_event.wait() except Exception as e: perror(f"Fehler beim Starten: {e}") raise finally: await self.stop() pinfo("Anwendung beendet") # Force-Exit: Blockierende Executor-Threads (stdin input/read_keys) # verhindern sauberen asyncio-Shutdown (300s Timeout). # Da stop() bereits alle Services und Ressourcen aufgeräumt hat, # ist os._exit() hier sicher. os._exit(0) async def _signal_handler(self) -> None: """Behandelt Shutdown-Signale.""" if not self._running: return pinfo("Shutdown-Signal empfangen...") self._running = False self._shutdown_event.set() def shutdown(self) -> None: """Löst einen Shutdown aus.""" self._shutdown_event.set() def _init_file_logging(self, logging_config) -> None: """ Initialisiert das File-Logging-System. Args: logging_config: LoggingConfig-Instanz mit Logging-Parametern """ from trixy_core.utils.logging.file_router import init_file_router router = init_file_router( log_directory=logging_config.log_directory, file_log_level=logging_config.file_log_level, max_file_size_mb=logging_config.max_file_size_mb, backup_count=logging_config.backup_count, ) _setup_file_routing(router) pdebug(f"File-Logging initialisiert: {logging_config.log_directory}/") def _shutdown_file_logging(self) -> None: """Faehrt das File-Logging-System herunter.""" from trixy_core.utils.logging.file_router import shutdown_file_router _setup_file_routing(None) shutdown_file_router() def get_path(self, *parts: str) -> Path: """ Gibt einen Pfad relativ zum Basis-Pfad zurück. Args: *parts: Pfad-Komponenten Returns: Vollständiger Pfad """ return self._base_path.joinpath(*parts)