package gocron import ( "sort" "time" ) // Scheduler struct, the only data member is the list of jobs. // - implements the sort.Interface{} for sorting jobs, by the time nextRun type Scheduler struct { jobs [MAXJOBNUM]*Job // Array store jobs size int // Size of jobs which jobs holding. loc *time.Location // Location to use when scheduling jobs with specified times } var ( defaultScheduler = NewScheduler() ) // NewScheduler creates a new scheduler func NewScheduler() *Scheduler { return &Scheduler{ jobs: [MAXJOBNUM]*Job{}, size: 0, loc: loc, } } // Jobs returns the list of Jobs from the Scheduler func (s *Scheduler) Jobs() []*Job { return s.jobs[:s.size] } func (s *Scheduler) Len() int { return s.size } func (s *Scheduler) Swap(i, j int) { s.jobs[i], s.jobs[j] = s.jobs[j], s.jobs[i] } func (s *Scheduler) Less(i, j int) bool { return s.jobs[j].nextRun.Unix() >= s.jobs[i].nextRun.Unix() } // ChangeLoc changes the default time location func (s *Scheduler) ChangeLoc(newLocation *time.Location) { s.loc = newLocation } // Get the current runnable jobs, which shouldRun is True func (s *Scheduler) getRunnableJobs() (runningJobs [MAXJOBNUM]*Job, n int) { runnableJobs := [MAXJOBNUM]*Job{} n = 0 sort.Sort(s) for i := 0; i < s.size; i++ { if s.jobs[i].shouldRun() { runnableJobs[n] = s.jobs[i] n++ } else { break } } return runnableJobs, n } // NextRun datetime when the next job should run. func (s *Scheduler) NextRun() (*Job, time.Time) { if s.size <= 0 { return nil, time.Now() } sort.Sort(s) return s.jobs[0], s.jobs[0].nextRun } // Every schedule a new periodic job with interval func (s *Scheduler) Every(interval uint64) *Job { job := NewJob(interval).Loc(s.loc) s.jobs[s.size] = job s.size++ return job } // RunPending runs all the jobs that are scheduled to run. func (s *Scheduler) RunPending() { runnableJobs, n := s.getRunnableJobs() if n != 0 { for i := 0; i < n; i++ { go runnableJobs[i].run() runnableJobs[i].lastRun = time.Now() runnableJobs[i].scheduleNextRun() } } } // RunAll run all jobs regardless if they are scheduled to run or not func (s *Scheduler) RunAll() { s.RunAllwithDelay(0) } // RunAllwithDelay runs all jobs with delay seconds func (s *Scheduler) RunAllwithDelay(d int) { for i := 0; i < s.size; i++ { go s.jobs[i].run() if 0 != d { time.Sleep(time.Duration(d)) } } } // Remove specific job j by function func (s *Scheduler) Remove(j interface{}) { s.removeByCondition(func(someJob *Job) bool { return someJob.jobFunc == getFunctionName(j) }) } // RemoveByRef removes specific job j by reference func (s *Scheduler) RemoveByRef(j *Job) { s.removeByCondition(func(someJob *Job) bool { return someJob == j }) } func (s *Scheduler) removeByCondition(shouldRemove func(*Job) bool) { i := 0 // keep deleting until no more jobs match the criteria for { found := false for ; i < s.size; i++ { if shouldRemove(s.jobs[i]) { found = true break } } if !found { return } for j := (i + 1); j < s.size; j++ { s.jobs[i] = s.jobs[j] i++ } s.size-- s.jobs[s.size] = nil } } // Scheduled checks if specific job j was already added func (s *Scheduler) Scheduled(j interface{}) bool { for _, job := range s.jobs { if job.jobFunc == getFunctionName(j) { return true } } return false } // Clear delete all scheduled jobs func (s *Scheduler) Clear() { for i := 0; i < s.size; i++ { s.jobs[i] = nil } s.size = 0 } // Start all the pending jobs // Add seconds ticker func (s *Scheduler) Start() chan bool { stopped := make(chan bool, 1) ticker := time.NewTicker(1 * time.Second) go func() { for { select { case <-ticker.C: s.RunPending() case <-stopped: ticker.Stop() return } } }() return stopped } // The following methods are shortcuts for not having to // create a Scheduler instance // Every schedules a new periodic job running in specific interval func Every(interval uint64) *Job { return defaultScheduler.Every(interval) } // RunPending run all jobs that are scheduled to run // // Please note that it is *intended behavior that run_pending() // does not run missed jobs*. For example, if you've registered a job // that should run every minute and you only call run_pending() // in one hour increments then your job won't be run 60 times in // between but only once. func RunPending() { defaultScheduler.RunPending() } // RunAll run all jobs regardless if they are scheduled to run or not. func RunAll() { defaultScheduler.RunAll() } // RunAllwithDelay run all the jobs with a delay in seconds // // A delay of `delay` seconds is added between each job. This can help // to distribute the system load generated by the jobs more evenly over // time. func RunAllwithDelay(d int) { defaultScheduler.RunAllwithDelay(d) } // Start run all jobs that are scheduled to run func Start() chan bool { return defaultScheduler.Start() } // Clear all scheduled jobs func Clear() { defaultScheduler.Clear() } // Remove a specific job func Remove(j interface{}) { defaultScheduler.Remove(j) } // Scheduled checks if specific job j was already added func Scheduled(j interface{}) bool { for _, job := range defaultScheduler.jobs { if job.jobFunc == getFunctionName(j) { return true } } return false } // NextRun gets the next running time func NextRun() (job *Job, time time.Time) { return defaultScheduler.NextRun() }