github.com/cozy/cozy-stack@v0.0.0-20240603063001-31110fa4cae1/model/job/trigger_cron.go (about) 1 package job 2 3 import ( 4 "fmt" 5 "time" 6 7 "github.com/robfig/cron/v3" 8 ) 9 10 // CronTrigger implements the @cron trigger type. It schedules recurring jobs with 11 // the weird but very used Cron syntax. 12 type CronTrigger struct { 13 *TriggerInfos 14 sched cron.Schedule 15 done chan struct{} 16 } 17 18 var ( 19 cronParser = cron.NewParser(cron.SecondOptional | cron.Minute | cron.Hour | cron.Dom | cron.Month | cron.Dow | cron.Descriptor) 20 periodicParser = NewPeriodicParser() 21 ) 22 23 // NewCronTrigger returns a new instance of CronTrigger given the specified options. 24 func NewCronTrigger(infos *TriggerInfos) (*CronTrigger, error) { 25 schedule, err := cronParser.Parse(infos.Arguments) 26 if err != nil { 27 return nil, ErrMalformedTrigger 28 } 29 return &CronTrigger{ 30 TriggerInfos: infos, 31 sched: schedule, 32 done: make(chan struct{}), 33 }, nil 34 } 35 36 // NewEveryTrigger returns a new instance of CronTrigger given the specified 37 // options as @every. 38 func NewEveryTrigger(infos *TriggerInfos) (*CronTrigger, error) { 39 schedule, err := cronParser.Parse("@every " + infos.Arguments) 40 if err != nil { 41 return nil, ErrMalformedTrigger 42 } 43 return &CronTrigger{ 44 TriggerInfos: infos, 45 sched: schedule, 46 done: make(chan struct{}), 47 }, nil 48 } 49 50 // NewMonthlyTrigger returns a new instance of CronTrigger given the specified 51 // options as @monthly. It will take a random day/hour in the possible range to 52 // spread the triggers from the same app manifest. 53 func NewMonthlyTrigger(infos *TriggerInfos) (*CronTrigger, error) { 54 return newPeriodicTrigger(infos, MonthlyKind) 55 } 56 57 // NewWeeklyTrigger returns a new instance of CronTrigger given the specified 58 // options as @weekly. It will take a random day/hour in the possible range to 59 // spread the triggers from the same app manifest. 60 func NewWeeklyTrigger(infos *TriggerInfos) (*CronTrigger, error) { 61 return newPeriodicTrigger(infos, WeeklyKind) 62 } 63 64 // NewDailyTrigger returns a new instance of CronTrigger given the specified 65 // options as @daily. It will take a random hour in the possible range to 66 // spread the triggers from the same app manifest. 67 func NewDailyTrigger(infos *TriggerInfos) (*CronTrigger, error) { 68 return newPeriodicTrigger(infos, DailyKind) 69 } 70 71 // NewHourlyTrigger returns a new instance of CronTrigger given the specified 72 // options as @hourly. It will take a random minute in the possible range to 73 // spread the triggers from the same app manifest. 74 func NewHourlyTrigger(infos *TriggerInfos) (*CronTrigger, error) { 75 return newPeriodicTrigger(infos, HourlyKind) 76 } 77 78 func newPeriodicTrigger(infos *TriggerInfos, frequency FrequencyKind) (*CronTrigger, error) { 79 spec, err := periodicParser.Parse(frequency, infos.Arguments) 80 if err != nil { 81 return nil, ErrMalformedTrigger 82 } 83 seed := fmt.Sprintf("%s/%s/%v", infos.Domain, infos.WorkerType, infos.Message) 84 crontab := spec.ToRandomCrontab(seed) 85 schedule, err := cronParser.Parse(crontab) 86 if err != nil { 87 return nil, ErrMalformedTrigger 88 } 89 return &CronTrigger{ 90 TriggerInfos: infos, 91 sched: schedule, 92 done: make(chan struct{}), 93 }, nil 94 } 95 96 // Type implements the Type method of the Trigger interface. 97 func (c *CronTrigger) Type() string { 98 return c.TriggerInfos.Type 99 } 100 101 // NextExecution returns the next time when a job should be fired for this trigger 102 func (c *CronTrigger) NextExecution(last time.Time) time.Time { 103 return c.sched.Next(last) 104 } 105 106 // Schedule implements the Schedule method of the Trigger interface. 107 func (c *CronTrigger) Schedule() <-chan *JobRequest { 108 ch := make(chan *JobRequest) 109 go func() { 110 next := time.Now() 111 for { 112 next = c.NextExecution(next) 113 select { 114 case <-time.After(-time.Since(next)): 115 ch <- c.TriggerInfos.JobRequest() 116 case <-c.done: 117 close(ch) 118 return 119 } 120 } 121 }() 122 return ch 123 } 124 125 // Unschedule implements the Unschedule method of the Trigger interface. 126 func (c *CronTrigger) Unschedule() { 127 close(c.done) 128 } 129 130 // Infos implements the Infos method of the Trigger interface. 131 func (c *CronTrigger) Infos() *TriggerInfos { 132 return c.TriggerInfos 133 } 134 135 // CombineRequest implements the CombineRequest method of the Trigger interface. 136 func (c *CronTrigger) CombineRequest() string { 137 return keepOriginalRequest 138 } 139 140 var _ Trigger = &CronTrigger{}