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{}