added Cron.Jobs() method
This commit is contained in:
		
							parent
							
								
									f27d9f1dc9
								
							
						
					
					
						commit
						bae5421d62
					
				| 
						 | 
					@ -11,21 +11,17 @@ package cron
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"errors"
 | 
						"errors"
 | 
				
			||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
 | 
						"slices"
 | 
				
			||||||
	"sync"
 | 
						"sync"
 | 
				
			||||||
	"time"
 | 
						"time"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type job struct {
 | 
					 | 
				
			||||||
	schedule *Schedule
 | 
					 | 
				
			||||||
	run      func()
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Cron is a crontab-like struct for tasks/jobs scheduling.
 | 
					// Cron is a crontab-like struct for tasks/jobs scheduling.
 | 
				
			||||||
type Cron struct {
 | 
					type Cron struct {
 | 
				
			||||||
	timezone   *time.Location
 | 
						timezone   *time.Location
 | 
				
			||||||
	ticker     *time.Ticker
 | 
						ticker     *time.Ticker
 | 
				
			||||||
	startTimer *time.Timer
 | 
						startTimer *time.Timer
 | 
				
			||||||
	jobs       map[string]*job
 | 
						jobs       []*Job
 | 
				
			||||||
	tickerDone chan bool
 | 
						tickerDone chan bool
 | 
				
			||||||
	interval   time.Duration
 | 
						interval   time.Duration
 | 
				
			||||||
	mux        sync.RWMutex
 | 
						mux        sync.RWMutex
 | 
				
			||||||
| 
						 | 
					@ -40,7 +36,7 @@ func New() *Cron {
 | 
				
			||||||
	return &Cron{
 | 
						return &Cron{
 | 
				
			||||||
		interval:   1 * time.Minute,
 | 
							interval:   1 * time.Minute,
 | 
				
			||||||
		timezone:   time.UTC,
 | 
							timezone:   time.UTC,
 | 
				
			||||||
		jobs:       map[string]*job{},
 | 
							jobs:       []*Job{},
 | 
				
			||||||
		tickerDone: make(chan bool),
 | 
							tickerDone: make(chan bool),
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -82,23 +78,30 @@ func (c *Cron) MustAdd(jobId string, cronExpr string, run func()) {
 | 
				
			||||||
//
 | 
					//
 | 
				
			||||||
// cronExpr is a regular cron expression, eg. "0 */3 * * *" (aka. at minute 0 past every 3rd hour).
 | 
					// cronExpr is a regular cron expression, eg. "0 */3 * * *" (aka. at minute 0 past every 3rd hour).
 | 
				
			||||||
// Check cron.NewSchedule() for the supported tokens.
 | 
					// Check cron.NewSchedule() for the supported tokens.
 | 
				
			||||||
func (c *Cron) Add(jobId string, cronExpr string, run func()) error {
 | 
					func (c *Cron) Add(jobId string, cronExpr string, fn func()) error {
 | 
				
			||||||
	if run == nil {
 | 
						if fn == nil {
 | 
				
			||||||
		return errors.New("failed to add new cron job: run must be non-nil function")
 | 
							return errors.New("failed to add new cron job: fn must be non-nil function")
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	c.mux.Lock()
 | 
					 | 
				
			||||||
	defer c.mux.Unlock()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	schedule, err := NewSchedule(cronExpr)
 | 
						schedule, err := NewSchedule(cronExpr)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return fmt.Errorf("failed to add new cron job: %w", err)
 | 
							return fmt.Errorf("failed to add new cron job: %w", err)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	c.jobs[jobId] = &job{
 | 
						c.mux.Lock()
 | 
				
			||||||
 | 
						defer c.mux.Unlock()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// remove previous (if any)
 | 
				
			||||||
 | 
						c.jobs = slices.DeleteFunc(c.jobs, func(j *Job) bool {
 | 
				
			||||||
 | 
							return j.Id() == jobId
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// add new
 | 
				
			||||||
 | 
						c.jobs = append(c.jobs, &Job{
 | 
				
			||||||
 | 
							id:       jobId,
 | 
				
			||||||
 | 
							fn:       fn,
 | 
				
			||||||
		schedule: schedule,
 | 
							schedule: schedule,
 | 
				
			||||||
		run:      run,
 | 
						})
 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return nil
 | 
						return nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -108,7 +111,13 @@ func (c *Cron) Remove(jobId string) {
 | 
				
			||||||
	c.mux.Lock()
 | 
						c.mux.Lock()
 | 
				
			||||||
	defer c.mux.Unlock()
 | 
						defer c.mux.Unlock()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	delete(c.jobs, jobId)
 | 
						if c.jobs == nil {
 | 
				
			||||||
 | 
							return // nothing to remove
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						c.jobs = slices.DeleteFunc(c.jobs, func(j *Job) bool {
 | 
				
			||||||
 | 
							return j.Id() == jobId
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// RemoveAll removes all registered cron jobs.
 | 
					// RemoveAll removes all registered cron jobs.
 | 
				
			||||||
| 
						 | 
					@ -116,7 +125,7 @@ func (c *Cron) RemoveAll() {
 | 
				
			||||||
	c.mux.Lock()
 | 
						c.mux.Lock()
 | 
				
			||||||
	defer c.mux.Unlock()
 | 
						defer c.mux.Unlock()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	c.jobs = map[string]*job{}
 | 
						c.jobs = []*Job{}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Total returns the current total number of registered cron jobs.
 | 
					// Total returns the current total number of registered cron jobs.
 | 
				
			||||||
| 
						 | 
					@ -127,6 +136,19 @@ func (c *Cron) Total() int {
 | 
				
			||||||
	return len(c.jobs)
 | 
						return len(c.jobs)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Jobs returns a shallow copy of the currently registered cron jobs.
 | 
				
			||||||
 | 
					func (c *Cron) Jobs() []*Job {
 | 
				
			||||||
 | 
						c.mux.RLock()
 | 
				
			||||||
 | 
						defer c.mux.RUnlock()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						copy := make([]*Job, len(c.jobs))
 | 
				
			||||||
 | 
						for i, j := range c.jobs {
 | 
				
			||||||
 | 
							copy[i] = j
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return copy
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Stop stops the current cron ticker (if not already).
 | 
					// Stop stops the current cron ticker (if not already).
 | 
				
			||||||
//
 | 
					//
 | 
				
			||||||
// You can resume the ticker by calling Start().
 | 
					// You can resume the ticker by calling Start().
 | 
				
			||||||
| 
						 | 
					@ -200,7 +222,7 @@ func (c *Cron) runDue(t time.Time) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	for _, j := range c.jobs {
 | 
						for _, j := range c.jobs {
 | 
				
			||||||
		if j.schedule.IsDue(moment) {
 | 
							if j.schedule.IsDue(moment) {
 | 
				
			||||||
			go j.run()
 | 
								go j.Run()
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2,6 +2,7 @@ package cron
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"encoding/json"
 | 
						"encoding/json"
 | 
				
			||||||
 | 
						"slices"
 | 
				
			||||||
	"testing"
 | 
						"testing"
 | 
				
			||||||
	"time"
 | 
						"time"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
| 
						 | 
					@ -98,6 +99,11 @@ func TestCronAddAndRemove(t *testing.T) {
 | 
				
			||||||
	// try to remove non-existing (should be no-op)
 | 
						// try to remove non-existing (should be no-op)
 | 
				
			||||||
	c.Remove("missing")
 | 
						c.Remove("missing")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						indexedJobs := make(map[string]*Job, len(c.jobs))
 | 
				
			||||||
 | 
						for _, j := range c.jobs {
 | 
				
			||||||
 | 
							indexedJobs[j.Id()] = j
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// check job keys
 | 
						// check job keys
 | 
				
			||||||
	{
 | 
						{
 | 
				
			||||||
		expectedKeys := []string{"test3", "test2", "test5"}
 | 
							expectedKeys := []string{"test3", "test2", "test5"}
 | 
				
			||||||
| 
						 | 
					@ -107,7 +113,7 @@ func TestCronAddAndRemove(t *testing.T) {
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		for _, k := range expectedKeys {
 | 
							for _, k := range expectedKeys {
 | 
				
			||||||
			if c.jobs[k] == nil {
 | 
								if indexedJobs[k] == nil {
 | 
				
			||||||
				t.Fatalf("Expected job with key %s, got nil", k)
 | 
									t.Fatalf("Expected job with key %s, got nil", k)
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
| 
						 | 
					@ -121,7 +127,7 @@ func TestCronAddAndRemove(t *testing.T) {
 | 
				
			||||||
			"test5": `{"minutes":{"1":{}},"hours":{"2":{}},"days":{"3":{}},"months":{"4":{}},"daysOfWeek":{"5":{}}}`,
 | 
								"test5": `{"minutes":{"1":{}},"hours":{"2":{}},"days":{"3":{}},"months":{"4":{}},"daysOfWeek":{"5":{}}}`,
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		for k, v := range expectedSchedules {
 | 
							for k, v := range expectedSchedules {
 | 
				
			||||||
			raw, err := json.Marshal(c.jobs[k].schedule)
 | 
								raw, err := json.Marshal(indexedJobs[k].schedule)
 | 
				
			||||||
			if err != nil {
 | 
								if err != nil {
 | 
				
			||||||
				t.Fatal(err)
 | 
									t.Fatal(err)
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
| 
						 | 
					@ -148,7 +154,7 @@ func TestCronMustAdd(t *testing.T) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	c.MustAdd("test2", "* * * * *", func() {})
 | 
						c.MustAdd("test2", "* * * * *", func() {})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if _, ok := c.jobs["test2"]; !ok {
 | 
						if !slices.ContainsFunc(c.jobs, func(j *Job) bool { return j.Id() == "test2" }) {
 | 
				
			||||||
		t.Fatal("Couldn't find job test2")
 | 
							t.Fatal("Couldn't find job test2")
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -208,6 +214,42 @@ func TestCronTotal(t *testing.T) {
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestCronJobs(t *testing.T) {
 | 
				
			||||||
 | 
						t.Parallel()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						c := New()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						calls := ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err := c.Add("a", "1 * * * *", func() { calls += "a" }); err != nil {
 | 
				
			||||||
 | 
							t.Fatal(err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err := c.Add("b", "2 * * * *", func() { calls += "b" }); err != nil {
 | 
				
			||||||
 | 
							t.Fatal(err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// overwrite
 | 
				
			||||||
 | 
						if err := c.Add("b", "3 * * * *", func() { calls += "b" }); err != nil {
 | 
				
			||||||
 | 
							t.Fatal(err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						jobs := c.Jobs()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if len(jobs) != 2 {
 | 
				
			||||||
 | 
							t.Fatalf("Expected 2 jobs, got %v", len(jobs))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, j := range jobs {
 | 
				
			||||||
 | 
							j.Run()
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						expectedCalls := "ab"
 | 
				
			||||||
 | 
						if calls != expectedCalls {
 | 
				
			||||||
 | 
							t.Fatalf("Expected %q calls, got %q", expectedCalls, calls)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func TestCronStartStop(t *testing.T) {
 | 
					func TestCronStartStop(t *testing.T) {
 | 
				
			||||||
	t.Parallel()
 | 
						t.Parallel()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,25 @@
 | 
				
			||||||
 | 
					package cron
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Job defines a single registered cron job.
 | 
				
			||||||
 | 
					type Job struct {
 | 
				
			||||||
 | 
						fn       func()
 | 
				
			||||||
 | 
						schedule *Schedule
 | 
				
			||||||
 | 
						id       string
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Id returns the cron job id.
 | 
				
			||||||
 | 
					func (j *Job) Id() string {
 | 
				
			||||||
 | 
						return j.id
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Expr returns the plain cron job schedule expression.
 | 
				
			||||||
 | 
					func (j *Job) Expr() string {
 | 
				
			||||||
 | 
						return j.schedule.rawExpr
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Run runs the cron job function.
 | 
				
			||||||
 | 
					func (j *Job) Run() {
 | 
				
			||||||
 | 
						if j.fn != nil {
 | 
				
			||||||
 | 
							j.fn()
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,49 @@
 | 
				
			||||||
 | 
					package cron
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import "testing"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestJobId(t *testing.T) {
 | 
				
			||||||
 | 
						expected := "test"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						j := Job{id: expected}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if j.Id() != expected {
 | 
				
			||||||
 | 
							t.Fatalf("Expected job with id %q, got %q", expected, j.Id())
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestJobExpr(t *testing.T) {
 | 
				
			||||||
 | 
						expected := "1 2 3 4 5"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						s, err := NewSchedule(expected)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							t.Fatal(err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						j := Job{schedule: s}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if j.Expr() != expected {
 | 
				
			||||||
 | 
							t.Fatalf("Expected job with cron expression %q, got %q", expected, j.Expr())
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestJobRun(t *testing.T) {
 | 
				
			||||||
 | 
						defer func() {
 | 
				
			||||||
 | 
							if r := recover(); r != nil {
 | 
				
			||||||
 | 
								t.Errorf("Shouldn't panic: %v", r)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						calls := ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						j1 := Job{}
 | 
				
			||||||
 | 
						j2 := Job{fn: func() { calls += "2" }}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						j1.Run()
 | 
				
			||||||
 | 
						j2.Run()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						expected := "2"
 | 
				
			||||||
 | 
						if calls != expected {
 | 
				
			||||||
 | 
							t.Fatalf("Expected calls %q, got %q", expected, calls)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -35,6 +35,8 @@ type Schedule struct {
 | 
				
			||||||
	Days       map[int]struct{} `json:"days"`
 | 
						Days       map[int]struct{} `json:"days"`
 | 
				
			||||||
	Months     map[int]struct{} `json:"months"`
 | 
						Months     map[int]struct{} `json:"months"`
 | 
				
			||||||
	DaysOfWeek map[int]struct{} `json:"daysOfWeek"`
 | 
						DaysOfWeek map[int]struct{} `json:"daysOfWeek"`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						rawExpr string
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// IsDue checks whether the provided Moment satisfies the current Schedule.
 | 
					// IsDue checks whether the provided Moment satisfies the current Schedule.
 | 
				
			||||||
| 
						 | 
					@ -130,6 +132,7 @@ func NewSchedule(cronExpr string) (*Schedule, error) {
 | 
				
			||||||
		Days:       days,
 | 
							Days:       days,
 | 
				
			||||||
		Months:     months,
 | 
							Months:     months,
 | 
				
			||||||
		DaysOfWeek: daysOfWeek,
 | 
							DaysOfWeek: daysOfWeek,
 | 
				
			||||||
 | 
							rawExpr:    cronExpr,
 | 
				
			||||||
	}, nil
 | 
						}, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue