package scheduler import ( "RenewCMS/internal/domain/purge" "context" "errors" "log" "time" ) const ( TickInterval = time.Hour StartupDelay = time.Minute ) type Job interface { Tick(now time.Time) (purge.RunResult, bool, error) } type Runner struct { job Job now func() time.Time ticks <-chan time.Time startup <-chan time.Time logger *log.Logger release func() } func NewRunner(job Job, now func() time.Time, ticks <-chan time.Time, startup <-chan time.Time, logger *log.Logger) *Runner { return &Runner{job: job, now: now, ticks: ticks, startup: startup, logger: logger, release: func() {}} } func NewSystemRunner(job Job, logger *log.Logger) *Runner { ticker := time.NewTicker(TickInterval) startup := time.NewTimer(StartupDelay) runner := NewRunner(job, time.Now, ticker.C, startup.C, logger) runner.release = func() { ticker.Stop() startup.Stop() } return runner } func (r *Runner) Run(ctx context.Context) { defer r.release() startup := r.startup for ctx.Err() == nil { select { case <-ctx.Done(): return case <-startup: startup = nil r.tick(ctx) case <-r.ticks: r.tick(ctx) } } } func (r *Runner) tick(ctx context.Context) { if ctx.Err() != nil { return } result, ran, err := r.job.Tick(r.now()) if errors.Is(err, purge.ErrPurgeBusy) { return } if err != nil { r.logger.Printf("scheduler: automatic purge failed: %v", err) return } if ran { r.logger.Printf("scheduler: automatic purge completed: groups_purged=%d merged=%d articles_updated=%d failed_groups=%d", result.GroupsPurged, result.Merged, result.ArticlesUpdated, result.FailedGroups) } }