From 81711f2f3dc25f1196737e720004f1dda801fd48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jesko=20Ansch=C3=BCtz?= Date: Fri, 27 Mar 2026 20:14:32 +0100 Subject: [PATCH] feat(scheduler): Reconciler iteriert alle Screens + resolveDesiredState Co-Authored-By: Claude Sonnet 4.6 --- .../backend/internal/scheduler/scheduler.go | 48 +++++++++++-------- 1 file changed, 29 insertions(+), 19 deletions(-) diff --git a/server/backend/internal/scheduler/scheduler.go b/server/backend/internal/scheduler/scheduler.go index dcf6efe..0de331f 100644 --- a/server/backend/internal/scheduler/scheduler.go +++ b/server/backend/internal/scheduler/scheduler.go @@ -25,6 +25,11 @@ type DisplayStateGetter interface { GetDisplayState(ctx context.Context, screenID string) (string, error) } +// AllScreensLister lädt alle bekannten Screens. +type AllScreensLister interface { + ListAll(ctx context.Context) ([]*store.Screen, error) +} + // Run startet den Scheduler-Loop. Blockiert bis ctx abgebrochen wird. func Run(ctx context.Context, schedules *store.ScreenScheduleStore, screens ScreenSlugGetter, notifier DisplayCommander) { ticker := time.NewTicker(1 * time.Minute) @@ -41,39 +46,50 @@ func Run(ctx context.Context, schedules *store.ScreenScheduleStore, screens Scre } // Reconcile läuft alle 5 Minuten und gleicht Ist- und Soll-Zustand ab. -func Reconcile(ctx context.Context, schedules *store.ScreenScheduleStore, screens ScreenSlugGetter, states DisplayStateGetter, notifier DisplayCommander) { +func Reconcile(ctx context.Context, schedules *store.ScreenScheduleStore, screens AllScreensLister, states DisplayStateGetter, globalOverrides *store.GlobalOverrideStore, notifier DisplayCommander) { ticker := time.NewTicker(5 * time.Minute) defer ticker.Stop() for { select { case <-ticker.C: - reconcile(ctx, schedules, screens, states, notifier) + reconcile(ctx, schedules, screens, states, globalOverrides, notifier) case <-ctx.Done(): return } } } -func reconcile(ctx context.Context, schedules *store.ScreenScheduleStore, screens ScreenSlugGetter, states DisplayStateGetter, notifier DisplayCommander) { - now := time.Now().Format("15:04") +func reconcile(ctx context.Context, schedules *store.ScreenScheduleStore, allScreens AllScreensLister, states DisplayStateGetter, globalOverrides *store.GlobalOverrideStore, notifier DisplayCommander) { + now := time.Now() - enabled, err := schedules.ListEnabled(ctx) + screenList, err := allScreens.ListAll(ctx) if err != nil { - slog.Error("reconciler: list enabled schedules failed", "err", err) + slog.Error("reconciler: list all screens failed", "err", err) return } - for _, sc := range enabled { - if sc.PowerOnTime == "" || sc.PowerOffTime == "" { + globalOverride, err := globalOverrides.Get(ctx) + if err != nil { + slog.Warn("reconciler: get global override failed", "err", err) + // nicht fatal — ohne Override fortfahren + } + + for _, screen := range screenList { + sc, err := schedules.Get(ctx, screen.ID) + if err != nil { + slog.Warn("reconciler: get schedule failed", "screen_id", screen.ID, "err", err) continue } - want := desiredState(sc.PowerOnTime, sc.PowerOffTime, now) + want, shouldControl := resolveDesiredState(*sc, globalOverride, now) + if !shouldControl { + continue + } - got, err := states.GetDisplayState(ctx, sc.ScreenID) + got, err := states.GetDisplayState(ctx, screen.ID) if err != nil { - slog.Warn("reconciler: get display state failed", "screen_id", sc.ScreenID, "err", err) + slog.Warn("reconciler: get display state failed", "screen_id", screen.ID, "err", err) continue } @@ -81,17 +97,11 @@ func reconcile(ctx context.Context, schedules *store.ScreenScheduleStore, screen continue } - screen, err := screens.GetByID(ctx, sc.ScreenID) - if err != nil { - slog.Warn("reconciler: screen not found", "screen_id", sc.ScreenID, "err", err) - continue - } - action := "display_" + want if err := notifier.SendDisplayCommand(screen.Slug, action); err != nil { - slog.Error("reconciler: send command failed", "screen_id", sc.ScreenID, "action", action, "err", err) + slog.Error("reconciler: send command failed", "screen_id", screen.ID, "action", action, "err", err) } else { - slog.Info("reconciler: corrected display state", "screen_id", sc.ScreenID, "slug", screen.Slug, "was", got, "want", want) + slog.Info("reconciler: corrected display state", "screen_id", screen.ID, "slug", screen.Slug, "was", got, "want", want) } } }