Skip to content

Commit a8b2b27

Browse files
author
Steven Seitz
committed
fix: decouple scheduler from streamlit UI to guarantee autostart after container restarts
1 parent 3ff2bfb commit a8b2b27

3 files changed

Lines changed: 48 additions & 49 deletions

File tree

Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,4 @@ COPY . /app/
2222
EXPOSE 8501
2323

2424
# Befehl, der beim Starten ausgeführt wird
25-
CMD ["streamlit", "run", "ui.py", "--server.port=8501", "--server.address=0.0.0.0"]
25+
CMD ["python", "main.py"]

main.py

Lines changed: 41 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -365,33 +365,56 @@ def sync_collections():
365365
logging.info("🏁 Sync-Durchlauf beendet.")
366366

367367
def main():
368+
import subprocess
369+
368370
config = load_config()
369371
if not config:
370372
sys.exit(1)
371373

372374
settings = config.get("settings", {})
373-
run_schedules = settings.get("run_schedules", ["NOW"])
374375

375-
if isinstance(run_schedules, str):
376-
run_schedules = [run_schedules]
377-
378-
# NEU: Beim Start des Skripts (ausserhalb des Syncs) noch nicht rotieren
376+
# Log beim Start einmalig ohne Rotation initialisieren
379377
setup_logger(settings.get("log_level", "INFO"), rotate=False)
380378

381-
if any(s.upper() == "NOW" for s in run_schedules):
382-
logging.info("⚡ Modus 'NOW' erkannt: Führe Sync sofort aus und beende danach.")
383-
sync_collections()
384-
logging.info("👋 Container/Skript wird beendet. Ciao!")
385-
sys.exit(0)
386-
else:
387-
logging.info(f"🕒 Standby-Modus aktiviert. Geplante Syncs täglich um: {', '.join(run_schedules)} Uhr.")
388-
389-
for time_str in run_schedules:
390-
schedule.every().day.at(time_str).do(sync_collections)
379+
logging.info("🌐 Starte Streamlit Dashboard im Hintergrund...")
380+
# Streamlit als separaten Prozess abfeuern
381+
subprocess.Popen(["streamlit", "run", "ui.py", "--server.port=8501", "--server.address=0.0.0.0"])
382+
383+
logging.info("🕒 Starte intelligenten Hintergrund-Scheduler...")
384+
last_schedules = []
385+
386+
while True:
387+
current_config = load_config()
388+
if current_config:
389+
current_settings = current_config.get("settings", {})
390+
# Wir mergen hier die Keys, um den Bug zwischen ui.py und config.yml zu fixen
391+
current_schedules = current_settings.get("sync_times", current_settings.get("run_schedules", ["04:30"]))
391392

392-
while True:
393-
schedule.run_pending()
394-
time.sleep(60)
393+
if isinstance(current_schedules, str):
394+
current_schedules = [current_schedules]
395+
396+
# Wenn NOW getriggert wird
397+
if any(s.upper() == "NOW" for s in current_schedules):
398+
logging.info("⚡ Modus 'NOW' erkannt: Führe Sync sofort aus.")
399+
sync_collections()
400+
# NOW aus der Config löschen, damit er nicht im Loop festhängt
401+
current_schedules = [s for s in current_schedules if s.upper() != "NOW"]
402+
current_settings["sync_times"] = current_schedules
403+
current_config["settings"] = current_settings
404+
with open("config.yml", "w", encoding="utf-8") as f:
405+
yaml.dump(current_config, f, default_flow_style=False, allow_unicode=True)
406+
407+
# Bei Änderungen am Zeitplan direkt neu aufbauen
408+
if current_schedules != last_schedules:
409+
schedule.clear()
410+
for time_str in current_schedules:
411+
if time_str.upper() != "NOW":
412+
schedule.every().day.at(time_str).do(sync_collections)
413+
logging.info(f"🔄 Zeitplan aktiv: Syncs täglich um {', '.join(current_schedules)} Uhr")
414+
last_schedules = current_schedules
415+
416+
schedule.run_pending()
417+
time.sleep(10)
395418

396419
if __name__ == "__main__":
397420
main()

ui.py

Lines changed: 6 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -87,27 +87,6 @@ def get_recent_logs(num_lines=50):
8787
return "".join(recent)
8888
except Exception as e:
8989
return f"Fehler beim Lesen der Logs: {e}"
90-
91-
# --- DYNAMISCHE TIMER LOGIK ---
92-
def rebuild_schedule():
93-
schedule.clear()
94-
sync_times = settings.get("sync_times", ["04:30"])
95-
for t in sync_times:
96-
try:
97-
schedule.every().day.at(t).do(sync_collections)
98-
except Exception as e:
99-
st.error(f"Ungültige Zeit im Zeitplan: {t}")
100-
101-
def run_timer_loop():
102-
while True:
103-
schedule.run_pending()
104-
time.sleep(10) # Häufiger prüfen für bessere UI-Reaktion
105-
106-
# Thread initial starten
107-
if 'timer_thread_started' not in st.session_state:
108-
rebuild_schedule() # Einmalig beim Start aufbauen
109-
threading.Thread(target=run_timer_loop, daemon=True).start()
110-
st.session_state['timer_thread_started'] = True
11190

11291
# === UI HEADER ===
11392
header_col1, header_col2 = st.columns([1, 6])
@@ -251,20 +230,17 @@ def run_timer_loop():
251230
save_config(config)
252231

253232
# WICHTIG: Den Timer-Thread sofort mit den neuen Zeiten füttern!
254-
rebuild_schedule()
255233
st.rerun() # UI neu laden, damit die "Nächster Run" Info oben rechts stimmt
256234

257235
st.divider()
258236
st.subheader("🕒 Automatisierung")
259237

260-
next_run = schedule.next_run()
261-
if next_run:
262-
st.success(f"Nächster Sync: **{next_run.strftime('%H:%M Uhr')}** ({next_run.strftime('%d.%m.')})")
263-
264-
all_jobs = schedule.get_jobs()
265-
with st.expander("Alle geplanten Syncs"):
266-
for job in all_jobs:
267-
st.write(f"• Täglich um {job.at_time}")
238+
sync_times = settings.get("sync_times", settings.get("run_schedules", []))
239+
if sync_times:
240+
st.success("Hintergrund-Scheduler läuft unabhängig und ist aktiv.")
241+
with st.expander("Geplante Syncs (Täglich)"):
242+
for t in sync_times:
243+
st.write(f"• {t} Uhr")
268244
else:
269245
st.warning("Kein Zeitplan aktiv.")
270246

0 commit comments

Comments
 (0)