#!/usr/bin/env python3 """ Flask-Server: Kantinen-Speiseplan als abonnierbare iCal-URL. Täglicher Hintergrund-Refresh lädt neue PDFs und aktualisiert den Kalender. """ import logging import os import threading import time from flask import Flask, Response from kantine2ical import BASE_URL, empty_ical_bytes, refresh_speiseplan # Konfiguration (Umgebungsvariablen mit Fallback) KANTINE_BASE_URL = os.environ.get("KANTINE_BASE_URL", BASE_URL) REFRESH_INTERVAL_SECONDS = int(os.environ.get("REFRESH_INTERVAL_SECONDS", "86400")) # 24h app = Flask(__name__) _log = logging.getLogger(__name__) # Cache: zuletzt gültige iCal-Bytes; Lock für Zugriff _ical_cache: bytes = empty_ical_bytes() _cache_lock = threading.Lock() def _do_refresh() -> None: """Refresh ausführen und bei Erfolg Cache aktualisieren.""" global _ical_cache result = refresh_speiseplan(KANTINE_BASE_URL) if result is not None: _, ical_bytes = result with _cache_lock: _ical_cache = ical_bytes _log.info("Speiseplan-Refresh erfolgreich, Cache aktualisiert.") else: _log.warning("Speiseplan-Refresh fehlgeschlagen oder keine Daten; Cache unverändert.") def _refresh_loop() -> None: """Hintergrund-Thread: alle REFRESH_INTERVAL_SECONDS einen Refresh ausführen.""" while True: time.sleep(REFRESH_INTERVAL_SECONDS) try: _do_refresh() except Exception as e: _log.exception("Refresh-Thread: %s", e) # Beim Import: einmal Refresh, Hintergrund-Thread starten (gilt auch für Gunicorn) logging.basicConfig(level=logging.INFO) _log.info("Starte Speiseplan-Refresh beim Start ...") _do_refresh() _refresh_thread = threading.Thread(target=_refresh_loop, daemon=True) _refresh_thread.start() _log.info("Hintergrund-Refresh alle %s Sekunden.", REFRESH_INTERVAL_SECONDS) @app.route("/calendar.ics") def calendar_ics() -> Response: """iCal-Kalender ausliefern (für Abo-URL z. B. in Google Kalender).""" with _cache_lock: data = _ical_cache return Response( data, mimetype="text/calendar; charset=utf-8", headers={"Content-Disposition": 'attachment; filename="kantine_speiseplan.ics"'}, ) @app.route("/") def index() -> Response: """Redirect auf calendar.ics oder gleiche Antwort wie /calendar.ics.""" return calendar_ics() def main() -> None: """Flask-Entwicklungsserver starten (Refresh und Thread laufen bereits beim Import).""" app.run(host="0.0.0.0", port=5000) if __name__ == "__main__": main()