X-Git-Url: https://git.giacomolli.uk/onechat/blobdiff_plain/d008b46cbfc6348255e9b9a1fc009f0e4bd6aa2a..3b7a3676dc3614ee3099e8798b5d0c07cb116b93:/schedule.go?ds=inline diff --git a/schedule.go b/schedule.go index d27274b..d2f53e0 100644 --- a/schedule.go +++ b/schedule.go @@ -6,6 +6,7 @@ import ( "encoding/json" "errors" "fmt" + "log" "os" "slices" "strconv" @@ -48,11 +49,13 @@ const ( ) // openSchedules reads the schedules of the file at path. A missing file holds -// none. The clock gives the time and the time zone of the schedules. -func openSchedules(path string, clock func() time.Time) (*Schedules, error) { +// none. The clock gives the time and the time zone of the schedules, and logger +// gets one line for each run. +func openSchedules(path string, clock func() time.Time, logger *log.Logger) (*Schedules, error) { s := &Schedules{ path: path, clock: clock, + logger: logger, check: scheduleCheck, changed: make(chan struct{}, 1), } @@ -93,6 +96,7 @@ func openSchedules(path string, clock func() time.Time) (*Schedules, error) { type Schedules struct { path string clock func() time.Time + logger *log.Logger check time.Duration changed chan struct{} @@ -111,8 +115,11 @@ func (s *Schedules) Run(ctx context.Context, l *TurnLoop) error { return err } - var timer *time.Timer - var timeout <-chan time.Time + var ( + timer *time.Timer + timeout <-chan time.Time + ) + wait, found := s.Wait(s.clock()) if found { timer = time.NewTimer(min(wait, s.check)) @@ -209,6 +216,14 @@ func (s *Schedules) List() string { return strings.Join(lines, "\n") } +// Len returns the number of schedules. +func (s *Schedules) Len() int { + s.mutex.Lock() + defer s.mutex.Unlock() + + return len(s.file.Schedules) +} + // Remove removes the schedule id and returns the answer of the tool. func (s *Schedules) Remove(id int) (string, error) { s.mutex.Lock() @@ -236,10 +251,14 @@ func (s *Schedules) Remove(id int) (string, error) { // deliver logs the message of each due schedule in l, in due order, as a work // message of scheduleSource, and wakes the loop once with all of them. A // schedule ends its run with Done after its message is durable in the log. A -// crash between the two makes the run repeat once. +// crash between the two makes the run repeat once. Each run logs one line with +// the ID of the schedule and, for a late run, how late it is. The line holds no +// text of the schedule. func (s *Schedules) deliver(ctx context.Context, l *TurnLoop) error { now := s.clock() + var turns []Message + for _, schedule := range s.Due(now) { message, err := l.Work(scheduleSource, schedule.Message(now), "") if err != nil { @@ -262,6 +281,12 @@ func (s *Schedules) deliver(ctx context.Context, l *TurnLoop) error { return err } + if schedule.late(now) { + s.logger.Printf("schedule %d ran %s late", schedule.ID, durationText(now.Sub(schedule.Due))) + } else { + s.logger.Printf("schedule %d ran", schedule.ID) + } + turns = append(turns, message) } @@ -273,6 +298,7 @@ func (s *Schedules) deliver(ctx context.Context, l *TurnLoop) error { // Due returns the schedules whose time is not after now, by due time. func (s *Schedules) Due(now time.Time) []Schedule { var due []Schedule + for _, schedule := range s.sorted() { if !schedule.Due.After(now) { due = append(due, schedule) @@ -343,6 +369,7 @@ func (s *Schedules) change() { // sorted returns a copy of the schedules by due time, then by ID. func (s *Schedules) sorted() []Schedule { s.mutex.Lock() + schedules := slices.Clone(s.file.Schedules) s.mutex.Unlock() @@ -394,13 +421,19 @@ func (s Schedule) Message(now time.Time) string { s.Text, } - if now.Sub(s.Due) > scheduleLate { + if s.late(now) { lines = append(lines, scheduleLateLine) } return strings.Join(append(lines, scheduleEmptyReply), "\n") } +// late tells if a run at now is late: more than scheduleLate after the due +// time. +func (s Schedule) late(now time.Time) bool { + return now.Sub(s.Due) > scheduleLate +} + // repeats returns once for a one-time schedule, or else repeats and the cron // expression. func (s Schedule) repeats() string {