Initial commit: kantine2ical CLI, Flask-Server, Docker
This commit is contained in:
83
app.py
Normal file
83
app.py
Normal file
@@ -0,0 +1,83 @@
|
||||
#!/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()
|
||||
Reference in New Issue
Block a user