manage-your-printer/utils/timer_manager.py
2025-06-04 10:03:22 +02:00

675 lines
24 KiB
Python

"""
Timer-Manager für Countdown-Zähler mit Force-Quit-Funktionalität
Dieses Modul verwaltet System-Timer für verschiedene Anwendungsfälle:
- Kiosk-Timer für automatische Session-Beendigung
- Job-Timer für Druckaufträge mit Timeout
- Session-Timer für Benutzerinaktivität
- Wartungs-Timer für geplante System-Shutdowns
Autor: System
Erstellt: 2025
"""
import threading
import time
import json
import logging
from datetime import datetime, timedelta
from typing import Dict, List, Optional, Callable, Any
from enum import Enum
from contextlib import contextmanager
from models import SystemTimer, get_db_session, get_cached_session
from utils.logging_config import get_logger
logger = get_logger("timer_manager")
class TimerType(Enum):
"""Verfügbare Timer-Typen"""
KIOSK = "kiosk"
SESSION = "session"
JOB = "job"
SYSTEM = "system"
MAINTENANCE = "maintenance"
class ForceQuitAction(Enum):
"""Verfügbare Force-Quit-Aktionen"""
LOGOUT = "logout"
RESTART = "restart"
SHUTDOWN = "shutdown"
CUSTOM = "custom"
class TimerStatus(Enum):
"""Timer-Status-Werte"""
STOPPED = "stopped"
RUNNING = "running"
PAUSED = "paused"
EXPIRED = "expired"
FORCE_QUIT = "force_quit"
class TimerManager:
"""
Zentraler Timer-Manager für alle System-Timer.
Verwaltet Timer-Instanzen und führt automatische Cleanup-Operationen durch.
"""
def __init__(self):
self._timers: Dict[str, SystemTimer] = {}
self._timer_callbacks: Dict[str, List[Callable]] = {}
self._force_quit_handlers: Dict[str, Callable] = {}
self._background_thread: Optional[threading.Thread] = None
self._shutdown_flag = threading.Event()
self._update_interval = 1.0 # Sekunden zwischen Updates
# Standard Force-Quit-Handler registrieren
self._register_default_handlers()
# Background-Thread für Timer-Updates starten
self._start_background_thread()
logger.info("Timer-Manager initialisiert")
def _register_default_handlers(self):
"""Registriert Standard-Handler für Force-Quit-Aktionen"""
def logout_handler(timer: SystemTimer) -> bool:
"""Standard-Handler für Logout-Aktion"""
try:
logger.info(f"Logout-Handler für Timer '{timer.name}' ausgeführt")
# Hier würde der tatsächliche Logout implementiert werden
# Das wird in app.py über die API-Endpunkte gemacht
return True
except Exception as e:
logger.error(f"Fehler im Logout-Handler: {str(e)}")
return False
def restart_handler(timer: SystemTimer) -> bool:
"""Standard-Handler für System-Restart"""
try:
logger.warning(f"System-Restart durch Timer '{timer.name}' ausgelöst")
# Implementierung würde über System-API erfolgen
return True
except Exception as e:
logger.error(f"Fehler im Restart-Handler: {str(e)}")
return False
def shutdown_handler(timer: SystemTimer) -> bool:
"""Standard-Handler für System-Shutdown"""
try:
logger.warning(f"System-Shutdown durch Timer '{timer.name}' ausgelöst")
# Implementierung würde über System-API erfolgen
return True
except Exception as e:
logger.error(f"Fehler im Shutdown-Handler: {str(e)}")
return False
# Handler registrieren
self._force_quit_handlers[ForceQuitAction.LOGOUT.value] = logout_handler
self._force_quit_handlers[ForceQuitAction.RESTART.value] = restart_handler
self._force_quit_handlers[ForceQuitAction.SHUTDOWN.value] = shutdown_handler
def _start_background_thread(self):
"""Startet den Background-Thread für Timer-Updates"""
if self._background_thread is None or not self._background_thread.is_alive():
self._background_thread = threading.Thread(
target=self._background_worker,
name="TimerManager-Background",
daemon=True
)
self._background_thread.start()
logger.debug("Background-Thread für Timer-Updates gestartet")
def _background_worker(self):
"""Background-Worker für kontinuierliche Timer-Updates"""
logger.debug("Timer-Manager Background-Worker gestartet")
while not self._shutdown_flag.is_set():
try:
self._update_all_timers()
self._process_expired_timers()
# Warte bis zum nächsten Update
self._shutdown_flag.wait(self._update_interval)
except Exception as e:
logger.error(f"Fehler im Timer-Background-Worker: {str(e)}")
time.sleep(5) # Kurze Pause bei Fehlern
logger.debug("Timer-Manager Background-Worker beendet")
def _update_all_timers(self):
"""Aktualisiert alle Timer aus der Datenbank"""
try:
with get_cached_session() as session:
# Lade alle aktiven Timer aus der Datenbank
db_timers = session.query(SystemTimer).filter(
SystemTimer.status.in_([TimerStatus.RUNNING.value, TimerStatus.PAUSED.value])
).all()
# Update lokale Timer-Cache
current_timer_names = set(self._timers.keys())
db_timer_names = {timer.name for timer in db_timers}
# Entferne Timer die nicht mehr in der DB sind
for name in current_timer_names - db_timer_names:
if name in self._timers:
del self._timers[name]
logger.debug(f"Timer '{name}' aus lokalem Cache entfernt")
# Aktualisiere/füge Timer hinzu
for timer in db_timers:
self._timers[timer.name] = timer
# Callback-Funktionen aufrufen wenn verfügbar
if timer.name in self._timer_callbacks:
for callback in self._timer_callbacks[timer.name]:
try:
callback(timer)
except Exception as e:
logger.error(f"Fehler in Timer-Callback für '{timer.name}': {str(e)}")
except Exception as e:
logger.error(f"Fehler beim Update der Timer: {str(e)}")
def _process_expired_timers(self):
"""Verarbeitet abgelaufene Timer und führt Force-Quit-Aktionen aus"""
try:
expired_timers = SystemTimer.get_expired_timers()
for timer in expired_timers:
try:
logger.warning(f"Timer '{timer.name}' ist abgelaufen - führe Force-Quit aus")
# Force-Quit-Aktion ausführen
success = self._execute_force_quit(timer)
if success:
# Timer als abgelaufen markieren
with get_cached_session() as session:
db_timer = session.query(SystemTimer).filter(
SystemTimer.id == timer.id
).first()
if db_timer:
db_timer.status = TimerStatus.EXPIRED.value
db_timer.updated_at = datetime.now()
session.commit()
except Exception as e:
logger.error(f"Fehler beim Verarbeiten des abgelaufenen Timers '{timer.name}': {str(e)}")
except Exception as e:
logger.error(f"Fehler beim Verarbeiten abgelaufener Timer: {str(e)}")
def _execute_force_quit(self, timer: SystemTimer) -> bool:
"""Führt die Force-Quit-Aktion für einen Timer aus"""
try:
action = timer.force_quit_action
# Custom-Endpoint prüfen
if action == ForceQuitAction.CUSTOM.value and timer.custom_action_endpoint:
return self._execute_custom_action(timer)
# Standard-Handler verwenden
if action in self._force_quit_handlers:
handler = self._force_quit_handlers[action]
return handler(timer)
logger.warning(f"Unbekannte Force-Quit-Aktion: {action}")
return False
except Exception as e:
logger.error(f"Fehler beim Ausführen der Force-Quit-Aktion für Timer '{timer.name}': {str(e)}")
return False
def _execute_custom_action(self, timer: SystemTimer) -> bool:
"""Führt eine benutzerdefinierte Force-Quit-Aktion aus"""
try:
# Hier würde ein HTTP-Request an den Custom-Endpoint gemacht werden
# Das wird über die Flask-App-Routen implementiert
logger.info(f"Custom-Action für Timer '{timer.name}': {timer.custom_action_endpoint}")
return True
except Exception as e:
logger.error(f"Fehler bei Custom-Action für Timer '{timer.name}': {str(e)}")
return False
def create_timer(self, name: str, timer_type: TimerType, duration_seconds: int,
force_quit_action: ForceQuitAction = ForceQuitAction.LOGOUT,
auto_start: bool = False, **kwargs) -> Optional[SystemTimer]:
"""
Erstellt einen neuen Timer.
Args:
name: Eindeutiger Name des Timers
timer_type: Typ des Timers
duration_seconds: Dauer in Sekunden
force_quit_action: Aktion bei Force-Quit
auto_start: Automatisch starten
**kwargs: Zusätzliche Timer-Konfiguration
Returns:
SystemTimer-Instanz oder None bei Fehler
"""
try:
with get_cached_session() as session:
# Prüfe ob Timer bereits existiert
existing = session.query(SystemTimer).filter(
SystemTimer.name == name
).first()
if existing:
logger.warning(f"Timer '{name}' existiert bereits")
return existing
# Neuen Timer erstellen
timer = SystemTimer(
name=name,
timer_type=timer_type.value,
duration_seconds=duration_seconds,
remaining_seconds=duration_seconds,
target_timestamp=datetime.now() + timedelta(seconds=duration_seconds),
force_quit_action=force_quit_action.value,
auto_start=auto_start,
**kwargs
)
session.add(timer)
session.commit()
# Zu lokalem Cache hinzufügen
self._timers[name] = timer
if auto_start:
timer.start_timer()
logger.info(f"Timer '{name}' erstellt - Typ: {timer_type.value}, Dauer: {duration_seconds}s")
return timer
except Exception as e:
logger.error(f"Fehler beim Erstellen des Timers '{name}': {str(e)}")
return None
def get_timer(self, name: str) -> Optional[SystemTimer]:
"""
Holt einen Timer anhand des Namens.
Args:
name: Name des Timers
Returns:
SystemTimer-Instanz oder None
"""
try:
# Erst aus lokalem Cache prüfen
if name in self._timers:
return self._timers[name]
# Aus Datenbank laden
timer = SystemTimer.get_by_name(name)
if timer:
self._timers[name] = timer
return timer
except Exception as e:
logger.error(f"Fehler beim Laden des Timers '{name}': {str(e)}")
return None
def start_timer(self, name: str) -> bool:
"""Startet einen Timer"""
try:
timer = self.get_timer(name)
if not timer:
logger.error(f"Timer '{name}' nicht gefunden")
return False
success = timer.start_timer()
if success:
with get_cached_session() as session:
# Timer in Datenbank aktualisieren
db_timer = session.merge(timer)
session.commit()
logger.info(f"Timer '{name}' gestartet")
return success
except Exception as e:
logger.error(f"Fehler beim Starten des Timers '{name}': {str(e)}")
return False
def pause_timer(self, name: str) -> bool:
"""Pausiert einen Timer"""
try:
timer = self.get_timer(name)
if not timer:
logger.error(f"Timer '{name}' nicht gefunden")
return False
success = timer.pause_timer()
if success:
with get_cached_session() as session:
db_timer = session.merge(timer)
session.commit()
logger.info(f"Timer '{name}' pausiert")
return success
except Exception as e:
logger.error(f"Fehler beim Pausieren des Timers '{name}': {str(e)}")
return False
def stop_timer(self, name: str) -> bool:
"""Stoppt einen Timer"""
try:
timer = self.get_timer(name)
if not timer:
logger.error(f"Timer '{name}' nicht gefunden")
return False
success = timer.stop_timer()
if success:
with get_cached_session() as session:
db_timer = session.merge(timer)
session.commit()
logger.info(f"Timer '{name}' gestoppt")
return success
except Exception as e:
logger.error(f"Fehler beim Stoppen des Timers '{name}': {str(e)}")
return False
def reset_timer(self, name: str) -> bool:
"""Setzt einen Timer zurück"""
try:
timer = self.get_timer(name)
if not timer:
logger.error(f"Timer '{name}' nicht gefunden")
return False
success = timer.reset_timer()
if success:
with get_cached_session() as session:
db_timer = session.merge(timer)
session.commit()
logger.info(f"Timer '{name}' zurückgesetzt")
return success
except Exception as e:
logger.error(f"Fehler beim Zurücksetzen des Timers '{name}': {str(e)}")
return False
def extend_timer(self, name: str, additional_seconds: int) -> bool:
"""Verlängert einen Timer"""
try:
timer = self.get_timer(name)
if not timer:
logger.error(f"Timer '{name}' nicht gefunden")
return False
success = timer.extend_timer(additional_seconds)
if success:
with get_cached_session() as session:
db_timer = session.merge(timer)
session.commit()
logger.info(f"Timer '{name}' um {additional_seconds} Sekunden verlängert")
return success
except Exception as e:
logger.error(f"Fehler beim Verlängern des Timers '{name}': {str(e)}")
return False
def delete_timer(self, name: str) -> bool:
"""Löscht einen Timer"""
try:
with get_cached_session() as session:
timer = session.query(SystemTimer).filter(
SystemTimer.name == name
).first()
if not timer:
logger.error(f"Timer '{name}' nicht gefunden")
return False
session.delete(timer)
session.commit()
# Aus lokalem Cache entfernen
if name in self._timers:
del self._timers[name]
# Callbacks entfernen
if name in self._timer_callbacks:
del self._timer_callbacks[name]
logger.info(f"Timer '{name}' gelöscht")
return True
except Exception as e:
logger.error(f"Fehler beim Löschen des Timers '{name}': {str(e)}")
return False
def register_callback(self, timer_name: str, callback: Callable[[SystemTimer], None]):
"""
Registriert eine Callback-Funktion für Timer-Updates.
Args:
timer_name: Name des Timers
callback: Callback-Funktion die bei Updates aufgerufen wird
"""
if timer_name not in self._timer_callbacks:
self._timer_callbacks[timer_name] = []
self._timer_callbacks[timer_name].append(callback)
logger.debug(f"Callback für Timer '{timer_name}' registriert")
def register_force_quit_handler(self, action: str, handler: Callable[[SystemTimer], bool]):
"""
Registriert einen benutzerdefinierten Force-Quit-Handler.
Args:
action: Name der Aktion
handler: Handler-Funktion
"""
self._force_quit_handlers[action] = handler
logger.debug(f"Force-Quit-Handler für Aktion '{action}' registriert")
def get_all_timers(self) -> List[SystemTimer]:
"""Gibt alle Timer zurück"""
try:
with get_cached_session() as session:
timers = session.query(SystemTimer).all()
return timers
except Exception as e:
logger.error(f"Fehler beim Laden aller Timer: {str(e)}")
return []
def get_timers_by_type(self, timer_type: TimerType) -> List[SystemTimer]:
"""Gibt alle Timer eines bestimmten Typs zurück"""
try:
return SystemTimer.get_by_type(timer_type.value)
except Exception as e:
logger.error(f"Fehler beim Laden der Timer vom Typ '{timer_type.value}': {str(e)}")
return []
def get_running_timers(self) -> List[SystemTimer]:
"""Gibt alle laufenden Timer zurück"""
try:
return SystemTimer.get_running_timers()
except Exception as e:
logger.error(f"Fehler beim Laden der laufenden Timer: {str(e)}")
return []
def create_kiosk_timer(self, duration_minutes: int = 30, auto_start: bool = True) -> Optional[SystemTimer]:
"""
Erstellt einen Standard-Kiosk-Timer.
Args:
duration_minutes: Timer-Dauer in Minuten
auto_start: Automatisch starten
Returns:
SystemTimer-Instanz oder None
"""
return self.create_timer(
name="kiosk_session",
timer_type=TimerType.KIOSK,
duration_seconds=duration_minutes * 60,
force_quit_action=ForceQuitAction.LOGOUT,
auto_start=auto_start,
force_quit_warning_seconds=30,
show_warning=True,
warning_message="Kiosk-Session läuft ab. Bitte speichern Sie Ihre Arbeit."
)
def create_session_timer(self, user_id: int, duration_minutes: int = 120,
auto_start: bool = True) -> Optional[SystemTimer]:
"""
Erstellt einen Session-Timer für einen Benutzer.
Args:
user_id: Benutzer-ID
duration_minutes: Timer-Dauer in Minuten
auto_start: Automatisch starten
Returns:
SystemTimer-Instanz oder None
"""
return self.create_timer(
name=f"session_{user_id}",
timer_type=TimerType.SESSION,
duration_seconds=duration_minutes * 60,
force_quit_action=ForceQuitAction.LOGOUT,
auto_start=auto_start,
context_id=user_id,
force_quit_warning_seconds=60,
show_warning=True,
warning_message="Ihre Session läuft ab. Aktivität erforderlich."
)
def update_session_activity(self, user_id: int) -> bool:
"""
Aktualisiert die Aktivität eines Session-Timers.
Args:
user_id: Benutzer-ID
Returns:
True wenn erfolgreich
"""
try:
timer = self.get_timer(f"session_{user_id}")
if timer and timer.timer_type == TimerType.SESSION.value:
success = timer.update_activity()
if success:
with get_cached_session() as session:
db_timer = session.merge(timer)
session.commit()
return success
return False
except Exception as e:
logger.error(f"Fehler beim Aktualisieren der Session-Aktivität für User {user_id}: {str(e)}")
return False
def shutdown(self):
"""Beendet den Timer-Manager sauber"""
logger.info("Timer-Manager wird heruntergefahren...")
self._shutdown_flag.set()
if self._background_thread and self._background_thread.is_alive():
self._background_thread.join(timeout=5)
self._timers.clear()
self._timer_callbacks.clear()
logger.info("Timer-Manager heruntergefahren")
# Globale Timer-Manager-Instanz
_timer_manager: Optional[TimerManager] = None
def get_timer_manager() -> TimerManager:
"""
Gibt die globale Timer-Manager-Instanz zurück.
Thread-sicher mit Lazy Loading.
"""
global _timer_manager
if _timer_manager is None:
_timer_manager = TimerManager()
return _timer_manager
def init_timer_manager() -> TimerManager:
"""
Initialisiert den Timer-Manager explizit.
Sollte beim App-Start aufgerufen werden.
"""
return get_timer_manager()
def shutdown_timer_manager():
"""
Beendet den Timer-Manager sauber.
Sollte beim App-Shutdown aufgerufen werden.
"""
global _timer_manager
if _timer_manager:
_timer_manager.shutdown()
_timer_manager = None
# Convenience-Funktionen für häufige Timer-Operationen
def create_kiosk_timer(duration_minutes: int = 30, auto_start: bool = True) -> Optional[SystemTimer]:
"""Erstellt einen Kiosk-Timer"""
return get_timer_manager().create_kiosk_timer(duration_minutes, auto_start)
def create_session_timer(user_id: int, duration_minutes: int = 120) -> Optional[SystemTimer]:
"""Erstellt einen Session-Timer"""
return get_timer_manager().create_session_timer(user_id, duration_minutes)
def start_timer(name: str) -> bool:
"""Startet einen Timer"""
return get_timer_manager().start_timer(name)
def pause_timer(name: str) -> bool:
"""Pausiert einen Timer"""
return get_timer_manager().pause_timer(name)
def stop_timer(name: str) -> bool:
"""Stoppt einen Timer"""
return get_timer_manager().stop_timer(name)
def reset_timer(name: str) -> bool:
"""Setzt einen Timer zurück"""
return get_timer_manager().reset_timer(name)
def extend_timer(name: str, additional_seconds: int) -> bool:
"""Verlängert einen Timer"""
return get_timer_manager().extend_timer(name, additional_seconds)
def get_timer_status(name: str) -> Optional[Dict[str, Any]]:
"""Gibt den Status eines Timers zurück"""
timer = get_timer_manager().get_timer(name)
return timer.to_dict() if timer else None
def update_session_activity(user_id: int) -> bool:
"""Aktualisiert Session-Aktivität"""
return get_timer_manager().update_session_activity(user_id)