application.py 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223
  1. # -*- coding: utf-8 -*-
  2. """
  3. Abstrakte Basis-Anwendungsklasse für Trixy.
  4. Definiert die gemeinsame Struktur für Server, Client, Standalone und Trainer.
  5. """
  6. from __future__ import annotations
  7. import asyncio
  8. import os
  9. import signal
  10. import sys
  11. from abc import ABC, abstractmethod
  12. from pathlib import Path
  13. from typing import Any, TYPE_CHECKING
  14. if TYPE_CHECKING:
  15. from trixy_core.sync.store import SyncStore
  16. from trixy_core.service.service_container import ServiceContainer
  17. from trixy_core.events.eventmanager import EventManager
  18. from trixy_core.config.config_manager import ConfigManager
  19. from trixy_core.utils.debug import pinfo, pdebug, perror, set_debug_mode, _setup_file_routing
  20. from trixy_core.utils.version import VERSION_STRING
  21. class IApplication(ABC):
  22. """
  23. Abstrakte Basisklasse für alle Trixy-Anwendungsmodi.
  24. Stellt gemeinsame Infrastruktur bereit:
  25. - ServiceContainer für Service-Management
  26. - EventManager für Pub/Sub
  27. - ConfigManager für Konfiguration
  28. """
  29. def __init__(self, debug: bool = False) -> None:
  30. """
  31. Initialisiert die Anwendung.
  32. Args:
  33. debug: Debug-Modus aktivieren
  34. """
  35. self._debug = debug
  36. set_debug_mode(debug)
  37. self._running = False
  38. self._shutdown_event = asyncio.Event()
  39. # Core-Komponenten initialisieren
  40. self._config_manager = ConfigManager()
  41. self._event_manager = EventManager(self)
  42. self._service_container = ServiceContainer(self)
  43. # Sync-Store (optional, wird von Standalone/Server gesetzt)
  44. self._sync_store: "SyncStore | None" = None
  45. # Basis-Pfade
  46. self._base_path = Path.cwd()
  47. @property
  48. def debug(self) -> bool:
  49. """Debug-Modus Status."""
  50. return self._debug
  51. @property
  52. def running(self) -> bool:
  53. """Läuft die Anwendung?"""
  54. return self._running
  55. @property
  56. def config_manager(self) -> ConfigManager:
  57. """ConfigManager-Instanz."""
  58. return self._config_manager
  59. @property
  60. def config(self) -> ConfigManager:
  61. """Alias für config_manager."""
  62. return self._config_manager
  63. @property
  64. def events(self) -> EventManager:
  65. """EventManager-Instanz."""
  66. return self._event_manager
  67. @property
  68. def services(self) -> ServiceContainer:
  69. """ServiceContainer-Instanz."""
  70. return self._service_container
  71. @property
  72. def sync_store(self) -> "SyncStore | None":
  73. """SyncStore-Instanz (falls verfuegbar)."""
  74. return self._sync_store
  75. @property
  76. def base_path(self) -> Path:
  77. """Basis-Pfad der Anwendung."""
  78. return self._base_path
  79. @abstractmethod
  80. async def initialize(self) -> None:
  81. """
  82. Initialisiert die Anwendung.
  83. Wird vor dem Start aufgerufen, um Konfiguration zu laden
  84. und Services zu registrieren.
  85. """
  86. pass
  87. @abstractmethod
  88. async def start(self) -> None:
  89. """
  90. Startet die Anwendung.
  91. Startet alle Services und beginnt die Hauptschleife.
  92. """
  93. pass
  94. @abstractmethod
  95. async def stop(self) -> None:
  96. """
  97. Stoppt die Anwendung.
  98. Stoppt alle Services und gibt Ressourcen frei.
  99. """
  100. pass
  101. async def run(self) -> None:
  102. """
  103. Hauptmethode zum Ausführen der Anwendung.
  104. Initialisiert, startet und wartet auf Shutdown-Signal.
  105. """
  106. pinfo(f"Trixy v{VERSION_STRING} wird gestartet...")
  107. # Signal-Handler einrichten
  108. loop = asyncio.get_running_loop()
  109. if sys.platform == "win32":
  110. # Windows: add_signal_handler nicht verfügbar
  111. for sig in (signal.SIGINT, signal.SIGTERM):
  112. signal.signal(sig, lambda s, f: loop.call_soon_threadsafe(
  113. lambda: asyncio.create_task(self._signal_handler())
  114. ))
  115. else:
  116. for sig in (signal.SIGINT, signal.SIGTERM):
  117. loop.add_signal_handler(
  118. sig,
  119. lambda: asyncio.create_task(self._signal_handler())
  120. )
  121. try:
  122. await self.initialize()
  123. await self.start()
  124. self._running = True
  125. pinfo("Anwendung gestartet")
  126. # Warte auf Shutdown-Signal
  127. await self._shutdown_event.wait()
  128. except Exception as e:
  129. perror(f"Fehler beim Starten: {e}")
  130. raise
  131. finally:
  132. await self.stop()
  133. pinfo("Anwendung beendet")
  134. # Force-Exit: Blockierende Executor-Threads (stdin input/read_keys)
  135. # verhindern sauberen asyncio-Shutdown (300s Timeout).
  136. # Da stop() bereits alle Services und Ressourcen aufgeräumt hat,
  137. # ist os._exit() hier sicher.
  138. os._exit(0)
  139. async def _signal_handler(self) -> None:
  140. """Behandelt Shutdown-Signale."""
  141. if not self._running:
  142. return
  143. pinfo("Shutdown-Signal empfangen...")
  144. self._running = False
  145. self._shutdown_event.set()
  146. def shutdown(self) -> None:
  147. """Löst einen Shutdown aus."""
  148. self._shutdown_event.set()
  149. def _init_file_logging(self, logging_config) -> None:
  150. """
  151. Initialisiert das File-Logging-System.
  152. Args:
  153. logging_config: LoggingConfig-Instanz mit Logging-Parametern
  154. """
  155. from trixy_core.utils.logging.file_router import init_file_router
  156. router = init_file_router(
  157. log_directory=logging_config.log_directory,
  158. file_log_level=logging_config.file_log_level,
  159. max_file_size_mb=logging_config.max_file_size_mb,
  160. backup_count=logging_config.backup_count,
  161. )
  162. _setup_file_routing(router)
  163. pdebug(f"File-Logging initialisiert: {logging_config.log_directory}/")
  164. def _shutdown_file_logging(self) -> None:
  165. """Faehrt das File-Logging-System herunter."""
  166. from trixy_core.utils.logging.file_router import shutdown_file_router
  167. _setup_file_routing(None)
  168. shutdown_file_router()
  169. def get_path(self, *parts: str) -> Path:
  170. """
  171. Gibt einen Pfad relativ zum Basis-Pfad zurück.
  172. Args:
  173. *parts: Pfad-Komponenten
  174. Returns:
  175. Vollständiger Pfad
  176. """
  177. return self._base_path.joinpath(*parts)