From 698410b7a5868d48d470ef102207f7e7afae9217 Mon Sep 17 00:00:00 2001 From: Till Tomczak Date: Thu, 29 May 2025 21:32:52 +0200 Subject: [PATCH] "Refactor app logic using Conventional Commits format (feat)" --- backend/app/app.py | 401 +++++++++++++++++++++------------------------ 1 file changed, 189 insertions(+), 212 deletions(-) diff --git a/backend/app/app.py b/backend/app/app.py index 76dfb9f2..566d9e2d 100644 --- a/backend/app/app.py +++ b/backend/app/app.py @@ -38,7 +38,7 @@ from models import init_database, create_initial_admin, User, Printer, Job, Stat from utils.logging_config import setup_logging, get_logger, measure_execution_time, log_startup_info, debug_request, debug_response from utils.job_scheduler import JobScheduler, get_job_scheduler from utils.queue_manager import start_queue_manager, stop_queue_manager, get_queue_manager -from config.settings import SECRET_KEY, UPLOAD_FOLDER, ALLOWED_EXTENSIONS, ENVIRONMENT, SESSION_LIFETIME, SCHEDULER_ENABLED, SCHEDULER_INTERVAL +from config.settings import SECRET_KEY, UPLOAD_FOLDER, ALLOWED_EXTENSIONS, ENVIRONMENT, SESSION_LIFETIME, SCHEDULER_ENABLED, SCHEDULER_INTERVAL, TAPO_USERNAME, TAPO_PASSWORD from utils.file_manager import file_manager, save_job_file, save_guest_file, save_avatar_file, delete_file as delete_file_safe # Blueprints importieren @@ -1300,57 +1300,33 @@ def check_printer_status(ip_address: str, timeout: int = 7) -> Tuple[str, bool]: try: ipaddress.ip_address(ip_address.strip()) except ValueError: - printers_logger.warning(f"Ungültige IP-Adresse: {ip_address}") + printers_logger.debug(f"Ungültige IP-Adresse: {ip_address}") return "offline", False - # Zuerst prüfen, ob die Steckdose erreichbar ist (Ping) - if os.name == 'nt': # Windows - cmd = ['ping', '-n', '1', '-w', str(timeout * 1000), ip_address.strip()] - else: # Unix/Linux/macOS - cmd = ['ping', '-c', '1', '-W', str(timeout), ip_address.strip()] - - printers_logger.debug(f"Ping-Befehl für {ip_address}: {' '.join(cmd)}") - - # Ping ausführen mit Timeout - result = subprocess.run( - cmd, - capture_output=True, - text=True, - encoding='utf-8', - errors='ignore', # Ignoriere Unicode-Fehler - timeout=timeout + 2 # Zusätzlicher Timeout für subprocess - ) - - # Wenn Steckdose nicht erreichbar ist, ist der Drucker offline - if result.returncode != 0: - printers_logger.debug(f"Ping fehlgeschlagen für {ip_address} (Return Code: {result.returncode})") - return "offline", False - - # Drucker-Daten aus Datenbank holen für Anmeldedaten - db_session = get_db_session() - printer = db_session.query(Printer).filter(Printer.plug_ip == ip_address).first() - - if not printer: - printers_logger.warning(f"Kein Drucker mit Steckdosen-IP {ip_address} gefunden") - db_session.close() - return "offline", False - - # TP-Link Tapo P110 Status mit PyP100 prüfen + # Importiere PyP100 für Tapo-Unterstützung try: from PyP100 import PyP110 - - # Tapo-Steckdose verbinden - p110 = PyP110.P110(ip_address, printer.plug_username, printer.plug_password) - p110.handshake() # Authentifizierung - p110.login() # Login - - # Geräteinformationen abrufen - device_info = p110.getDeviceInfo() - device_on = device_info.get('device_on', False) - - db_session.close() - - if device_on: + except ImportError: + printers_logger.error("⚠️ PyP100-Modul nicht verfügbar - kann Tapo-Steckdosen nicht abfragen") + return "offline", False + + # Verwende IMMER die globalen hardkodierten Tapo-Anmeldedaten + username = TAPO_USERNAME + password = TAPO_PASSWORD + + printers_logger.debug(f"🔌 Teste Tapo-Steckdose {ip_address} mit hardkodierten Anmeldedaten") + + # TP-Link Tapo P110 Verbindung herstellen + p110 = PyP110.P110(ip_address.strip(), username, password) + p110.handshake() # Authentifizierung + p110.login() # Login + + # Geräteinformationen abrufen + device_info = p110.getDeviceInfo() + device_on = device_info.get('device_on', False) + + if device_on: + printers_logger.debug(f"✅ Drucker {ip_address}: ONLINE (Steckdose eingeschaltet)") printers_logger.debug(f"Tapo-Steckdose {ip_address} ist eingeschaltet - Drucker online") return "online", True else: @@ -6293,170 +6269,6 @@ def keep_alive(): "error": "Fehler beim Verlängern der Session" }), 500 -# ===== STARTUP UND MAIN ===== -if __name__ == "__main__": - import sys - import signal - import os - - # Debug-Modus prüfen - debug_mode = len(sys.argv) > 1 and sys.argv[1] == "--debug" - - # Windows-spezifische Umgebungsvariablen setzen für bessere Flask-Kompatibilität - if os.name == 'nt' and debug_mode: - # Entferne problematische Werkzeug-Variablen - os.environ.pop('WERKZEUG_SERVER_FD', None) - os.environ.pop('WERKZEUG_RUN_MAIN', None) - - # Setze saubere Umgebung - os.environ['FLASK_ENV'] = 'development' - os.environ['PYTHONIOENCODING'] = 'utf-8' - os.environ['PYTHONUTF8'] = '1' - - # Windows-spezifisches Signal-Handling für ordnungsgemäßes Shutdown - def signal_handler(sig, frame): - """Signal-Handler für ordnungsgemäßes Shutdown.""" - app_logger.warning(f"🛑 Signal {sig} empfangen - fahre System herunter...") - try: - # Queue Manager stoppen - app_logger.info("🔄 Beende Queue Manager...") - stop_queue_manager() - - # Scheduler stoppen falls aktiviert - if SCHEDULER_ENABLED and scheduler: - try: - scheduler.stop() - app_logger.info("Job-Scheduler gestoppt") - except Exception as e: - app_logger.error(f"Fehler beim Stoppen des Schedulers: {str(e)}") - - app_logger.info("✅ Shutdown abgeschlossen") - sys.exit(0) - except Exception as e: - app_logger.error(f"❌ Fehler beim Shutdown: {str(e)}") - sys.exit(1) - - - # Signal-Handler registrieren (Windows-kompatibel) - if os.name == 'nt': # Windows - signal.signal(signal.SIGINT, signal_handler) - signal.signal(signal.SIGTERM, signal_handler) - # Zusätzlich für Flask-Development-Server - signal.signal(signal.SIGBREAK, signal_handler) - else: # Unix/Linux - signal.signal(signal.SIGINT, signal_handler) - signal.signal(signal.SIGTERM, signal_handler) - signal.signal(signal.SIGHUP, signal_handler) - - try: - # Datenbank initialisieren - init_database() - create_initial_admin() - - # Template-Hilfsfunktionen registrieren - register_template_helpers(app) - - # Drucker-Monitor Steckdosen-Initialisierung beim Start - try: - app_logger.info("🖨️ Starte automatische Steckdosen-Initialisierung...") - initialization_results = printer_monitor.initialize_all_outlets_on_startup() - - if initialization_results: - success_count = sum(1 for success in initialization_results.values() if success) - total_count = len(initialization_results) - app_logger.info(f"✅ Steckdosen-Initialisierung: {success_count}/{total_count} Drucker erfolgreich") - - if success_count < total_count: - app_logger.warning(f"⚠️ {total_count - success_count} Drucker konnten nicht initialisiert werden") - else: - app_logger.info("ℹ️ Keine Drucker zur Initialisierung gefunden") - - except Exception as e: - app_logger.error(f"❌ Fehler bei automatischer Steckdosen-Initialisierung: {str(e)}") - - # Queue-Manager für automatische Drucker-Überwachung starten - # Nur im Produktionsmodus starten (nicht im Debug-Modus) - if not debug_mode: - try: - queue_manager = start_queue_manager() - app_logger.info("✅ Printer Queue Manager erfolgreich gestartet") - - # Verbesserte Shutdown-Handler registrieren - def cleanup_queue_manager(): - try: - app_logger.info("🔄 Beende Queue Manager...") - stop_queue_manager() - except Exception as e: - app_logger.error(f"❌ Fehler beim Queue Manager Cleanup: {str(e)}") - - atexit.register(cleanup_queue_manager) - - except Exception as e: - app_logger.error(f"❌ Fehler beim Starten des Queue-Managers: {str(e)}") - else: - app_logger.info("🔄 Debug-Modus: Queue Manager deaktiviert für Entwicklung") - - # Scheduler starten (falls aktiviert) - if SCHEDULER_ENABLED: - try: - scheduler.start() - app_logger.info("Job-Scheduler gestartet") - except Exception as e: - app_logger.error(f"Fehler beim Starten des Schedulers: {str(e)}") - - if debug_mode: - # Debug-Modus: HTTP auf Port 5000 - app_logger.info("Starte Debug-Server auf 0.0.0.0:5000 (HTTP)") - - # Windows-spezifische Flask-Konfiguration - run_kwargs = { - "host": "0.0.0.0", - "port": 5000, - "debug": True, - "threaded": True - } - - if os.name == 'nt': - # Windows: Deaktiviere Auto-Reload um WERKZEUG_SERVER_FD Fehler zu vermeiden - run_kwargs["use_reloader"] = False - run_kwargs["passthrough_errors"] = False - app_logger.info("Windows-Debug-Modus: Auto-Reload deaktiviert") - - app.run(**run_kwargs) - else: - # Produktions-Modus: HTTPS auf Port 443 - ssl_context = get_ssl_context() - - if ssl_context: - app_logger.info("Starte HTTPS-Server auf 0.0.0.0:443") - app.run( - host="0.0.0.0", - port=443, - debug=False, - ssl_context=ssl_context, - threaded=True - ) - else: - app_logger.info("Starte HTTP-Server auf 0.0.0.0:8080") - app.run( - host="0.0.0.0", - port=8080, - debug=False, - threaded=True - ) - - except KeyboardInterrupt: - app_logger.info("🔄 Tastatur-Unterbrechung empfangen - beende Anwendung...") - signal_handler(signal.SIGINT, None) - except Exception as e: - app_logger.error(f"Fehler beim Starten der Anwendung: {str(e)}") - # Cleanup bei Fehler - try: - stop_queue_manager() - except: - pass - sys.exit(1) - # ===== FILE-UPLOAD-ROUTEN ===== @app.route('/api/upload/job', methods=['POST']) @@ -7007,3 +6819,168 @@ def admin_create_printer_api(): except Exception as e: printers_logger.error(f"Fehler beim Erstellen eines Druckers durch Admin: {str(e)}") return jsonify({"error": "Interner Serverfehler"}), 500 + + +# ===== STARTUP UND MAIN ===== +if __name__ == "__main__": + import sys + import signal + import os + + # Debug-Modus prüfen + debug_mode = len(sys.argv) > 1 and sys.argv[1] == "--debug" + + # Windows-spezifische Umgebungsvariablen setzen für bessere Flask-Kompatibilität + if os.name == 'nt' and debug_mode: + # Entferne problematische Werkzeug-Variablen + os.environ.pop('WERKZEUG_SERVER_FD', None) + os.environ.pop('WERKZEUG_RUN_MAIN', None) + + # Setze saubere Umgebung + os.environ['FLASK_ENV'] = 'development' + os.environ['PYTHONIOENCODING'] = 'utf-8' + os.environ['PYTHONUTF8'] = '1' + + # Windows-spezifisches Signal-Handling für ordnungsgemäßes Shutdown + def signal_handler(sig, frame): + """Signal-Handler für ordnungsgemäßes Shutdown.""" + app_logger.warning(f"🛑 Signal {sig} empfangen - fahre System herunter...") + try: + # Queue Manager stoppen + app_logger.info("🔄 Beende Queue Manager...") + stop_queue_manager() + + # Scheduler stoppen falls aktiviert + if SCHEDULER_ENABLED and scheduler: + try: + scheduler.stop() + app_logger.info("Job-Scheduler gestoppt") + except Exception as e: + app_logger.error(f"Fehler beim Stoppen des Schedulers: {str(e)}") + + app_logger.info("✅ Shutdown abgeschlossen") + sys.exit(0) + except Exception as e: + app_logger.error(f"❌ Fehler beim Shutdown: {str(e)}") + sys.exit(1) + + + # Signal-Handler registrieren (Windows-kompatibel) + if os.name == 'nt': # Windows + signal.signal(signal.SIGINT, signal_handler) + signal.signal(signal.SIGTERM, signal_handler) + # Zusätzlich für Flask-Development-Server + signal.signal(signal.SIGBREAK, signal_handler) + else: # Unix/Linux + signal.signal(signal.SIGINT, signal_handler) + signal.signal(signal.SIGTERM, signal_handler) + signal.signal(signal.SIGHUP, signal_handler) + + try: + # Datenbank initialisieren + init_database() + create_initial_admin() + + # Template-Hilfsfunktionen registrieren + register_template_helpers(app) + + # Drucker-Monitor Steckdosen-Initialisierung beim Start + try: + app_logger.info("🖨️ Starte automatische Steckdosen-Initialisierung...") + initialization_results = printer_monitor.initialize_all_outlets_on_startup() + + if initialization_results: + success_count = sum(1 for success in initialization_results.values() if success) + total_count = len(initialization_results) + app_logger.info(f"✅ Steckdosen-Initialisierung: {success_count}/{total_count} Drucker erfolgreich") + + if success_count < total_count: + app_logger.warning(f"⚠️ {total_count - success_count} Drucker konnten nicht initialisiert werden") + else: + app_logger.info("ℹ️ Keine Drucker zur Initialisierung gefunden") + + except Exception as e: + app_logger.error(f"❌ Fehler bei automatischer Steckdosen-Initialisierung: {str(e)}") + + # Queue-Manager für automatische Drucker-Überwachung starten + # Nur im Produktionsmodus starten (nicht im Debug-Modus) + if not debug_mode: + try: + queue_manager = start_queue_manager() + app_logger.info("✅ Printer Queue Manager erfolgreich gestartet") + + # Verbesserte Shutdown-Handler registrieren + def cleanup_queue_manager(): + try: + app_logger.info("🔄 Beende Queue Manager...") + stop_queue_manager() + except Exception as e: + app_logger.error(f"❌ Fehler beim Queue Manager Cleanup: {str(e)}") + + atexit.register(cleanup_queue_manager) + + except Exception as e: + app_logger.error(f"❌ Fehler beim Starten des Queue-Managers: {str(e)}") + else: + app_logger.info("🔄 Debug-Modus: Queue Manager deaktiviert für Entwicklung") + + # Scheduler starten (falls aktiviert) + if SCHEDULER_ENABLED: + try: + scheduler.start() + app_logger.info("Job-Scheduler gestartet") + except Exception as e: + app_logger.error(f"Fehler beim Starten des Schedulers: {str(e)}") + + if debug_mode: + # Debug-Modus: HTTP auf Port 5000 + app_logger.info("Starte Debug-Server auf 0.0.0.0:5000 (HTTP)") + + # Windows-spezifische Flask-Konfiguration + run_kwargs = { + "host": "0.0.0.0", + "port": 5000, + "debug": True, + "threaded": True + } + + if os.name == 'nt': + # Windows: Deaktiviere Auto-Reload um WERKZEUG_SERVER_FD Fehler zu vermeiden + run_kwargs["use_reloader"] = False + run_kwargs["passthrough_errors"] = False + app_logger.info("Windows-Debug-Modus: Auto-Reload deaktiviert") + + app.run(**run_kwargs) + else: + # Produktions-Modus: HTTPS auf Port 443 + ssl_context = get_ssl_context() + + if ssl_context: + app_logger.info("Starte HTTPS-Server auf 0.0.0.0:443") + app.run( + host="0.0.0.0", + port=443, + debug=False, + ssl_context=ssl_context, + threaded=True + ) + else: + app_logger.info("Starte HTTP-Server auf 0.0.0.0:8080") + app.run( + host="0.0.0.0", + port=8080, + debug=False, + threaded=True + ) + + except KeyboardInterrupt: + app_logger.info("🔄 Tastatur-Unterbrechung empfangen - beende Anwendung...") + signal_handler(signal.SIGINT, None) + except Exception as e: + app_logger.error(f"Fehler beim Starten der Anwendung: {str(e)}") + # Cleanup bei Fehler + try: + stop_queue_manager() + except: + pass + sys.exit(1) \ No newline at end of file