package scheduler import ( "context" "log/slog" "time" "github.com/cereda-systems/nexus-scanner/internal/module" ) // Scheduler runs registered modules at their configured intervals. type Scheduler struct { registry *module.Registry } // New returns a new Scheduler backed by the given registry. func New(registry *module.Registry) *Scheduler { return &Scheduler{registry: registry} } // Run starts all modules in background goroutines and blocks until ctx is cancelled. func (s *Scheduler) Run(ctx context.Context) { for _, m := range s.registry.All() { go s.runModule(ctx, m) } <-ctx.Done() } func (s *Scheduler) runModule(ctx context.Context, m module.Module) { log := slog.With("module", m.Name()) // Run immediately on startup. s.execute(ctx, m, log) // If interval is zero, run once and stop. if m.Interval() == 0 { return } ticker := time.NewTicker(m.Interval()) defer ticker.Stop() for { select { case <-ctx.Done(): return case <-ticker.C: s.execute(ctx, m, log) } } } func (s *Scheduler) execute(ctx context.Context, m module.Module, log *slog.Logger) { log.Info("run started") start := time.Now() if err := m.Run(ctx); err != nil { log.Error("run failed", "err", err, "elapsed", time.Since(start).Round(time.Millisecond)) return } log.Info("run completed", "elapsed", time.Since(start).Round(time.Millisecond)) }