add vendoring
This commit is contained in:
342
vendor/github.com/mileusna/crontab/crontab.go
generated
vendored
Normal file
342
vendor/github.com/mileusna/crontab/crontab.go
generated
vendored
Normal file
@@ -0,0 +1,342 @@
|
||||
package crontab
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Crontab struct representing cron table
|
||||
type Crontab struct {
|
||||
ticker *time.Ticker
|
||||
jobs []*job
|
||||
sync.RWMutex
|
||||
}
|
||||
|
||||
// job in cron table
|
||||
type job struct {
|
||||
min map[int]struct{}
|
||||
hour map[int]struct{}
|
||||
day map[int]struct{}
|
||||
month map[int]struct{}
|
||||
dayOfWeek map[int]struct{}
|
||||
|
||||
fn interface{}
|
||||
args []interface{}
|
||||
sync.RWMutex
|
||||
}
|
||||
|
||||
// tick is individual tick that occures each minute
|
||||
type tick struct {
|
||||
min int
|
||||
hour int
|
||||
day int
|
||||
month int
|
||||
dayOfWeek int
|
||||
}
|
||||
|
||||
// New initializes and returns new cron table
|
||||
func New() *Crontab {
|
||||
return new(time.Minute)
|
||||
}
|
||||
|
||||
// new creates new crontab, arg provided for testing purpose
|
||||
func new(t time.Duration) *Crontab {
|
||||
c := &Crontab{
|
||||
ticker: time.NewTicker(t),
|
||||
jobs: []*job{},
|
||||
}
|
||||
|
||||
go func() {
|
||||
for t := range c.ticker.C {
|
||||
c.runScheduled(t)
|
||||
}
|
||||
}()
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
// AddJob to cron table
|
||||
//
|
||||
// Returns error if:
|
||||
//
|
||||
// * Cron syntax can't be parsed or out of bounds
|
||||
//
|
||||
// * fn is not function
|
||||
//
|
||||
// * Provided args don't match the number and/or the type of fn args
|
||||
func (c *Crontab) AddJob(schedule string, fn interface{}, args ...interface{}) error {
|
||||
j, err := parseSchedule(schedule)
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if fn == nil || reflect.ValueOf(fn).Kind() != reflect.Func {
|
||||
return fmt.Errorf("Cron job must be func()")
|
||||
}
|
||||
|
||||
fnType := reflect.TypeOf(fn)
|
||||
if len(args) != fnType.NumIn() {
|
||||
return fmt.Errorf("Number of func() params and number of provided params doesn't match")
|
||||
}
|
||||
|
||||
for i := 0; i < fnType.NumIn(); i++ {
|
||||
a := args[i]
|
||||
t1 := fnType.In(i)
|
||||
t2 := reflect.TypeOf(a)
|
||||
|
||||
if t1 != t2 {
|
||||
if t1.Kind() != reflect.Interface {
|
||||
return fmt.Errorf("Param with index %d shold be `%s` not `%s`", i, t1, t2)
|
||||
}
|
||||
if !t2.Implements(t1) {
|
||||
return fmt.Errorf("Param with index %d of type `%s` doesn't implement interface `%s`", i, t2, t1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// all checked, add job to cron tab
|
||||
j.fn = fn
|
||||
j.args = args
|
||||
c.jobs = append(c.jobs, j)
|
||||
return nil
|
||||
}
|
||||
|
||||
// MustAddJob is like AddJob but panics if there is an problem with job
|
||||
//
|
||||
// It simplifies initialization, since we usually add jobs at the beggining so you won't have to check for errors (it will panic when program starts).
|
||||
// It is a similar aproach as go's std lib package `regexp` and `regexp.Compile()` `regexp.MustCompile()`
|
||||
// MustAddJob will panic if:
|
||||
//
|
||||
// * Cron syntax can't be parsed or out of bounds
|
||||
//
|
||||
// * fn is not function
|
||||
//
|
||||
// * Provided args don't match the number and/or the type of fn args
|
||||
func (c *Crontab) MustAddJob(schedule string, fn interface{}, args ...interface{}) {
|
||||
if err := c.AddJob(schedule, fn, args...); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Shutdown the cron table schedule
|
||||
//
|
||||
// Once stopped, it can't be restarted.
|
||||
// This function is pre-shuttdown helper for your app, there is no Start/Stop functionallity with crontab package.
|
||||
func (c *Crontab) Shutdown() {
|
||||
c.ticker.Stop()
|
||||
}
|
||||
|
||||
// Clear all jobs from cron table
|
||||
func (c *Crontab) Clear() {
|
||||
c.Lock()
|
||||
c.jobs = []*job{}
|
||||
c.Unlock()
|
||||
}
|
||||
|
||||
// RunAll jobs in cron table, shcheduled or not
|
||||
func (c *Crontab) RunAll() {
|
||||
c.RLock()
|
||||
defer c.RUnlock()
|
||||
for _, j := range c.jobs {
|
||||
go j.run()
|
||||
}
|
||||
}
|
||||
|
||||
// RunScheduled jobs
|
||||
func (c *Crontab) runScheduled(t time.Time) {
|
||||
tick := getTick(t)
|
||||
c.RLock()
|
||||
defer c.RUnlock()
|
||||
|
||||
for _, j := range c.jobs {
|
||||
if j.tick(tick) {
|
||||
go j.run()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// run the job using reflection
|
||||
// Recover from panic although all functions and params are checked by AddJob, but you never know.
|
||||
func (j *job) run() {
|
||||
j.RLock()
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
log.Println("Crontab error", r)
|
||||
}
|
||||
}()
|
||||
v := reflect.ValueOf(j.fn)
|
||||
rargs := make([]reflect.Value, len(j.args))
|
||||
for i, a := range j.args {
|
||||
rargs[i] = reflect.ValueOf(a)
|
||||
}
|
||||
j.RUnlock()
|
||||
v.Call(rargs)
|
||||
}
|
||||
|
||||
// tick decides should the job be lauhcned at the tick
|
||||
func (j *job) tick(t tick) bool {
|
||||
j.RLock()
|
||||
defer j.RUnlock()
|
||||
if _, ok := j.min[t.min]; !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
if _, ok := j.hour[t.hour]; !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
// cummulative day and dayOfWeek, as it should be
|
||||
_, day := j.day[t.day]
|
||||
_, dayOfWeek := j.dayOfWeek[t.dayOfWeek]
|
||||
if !day && !dayOfWeek {
|
||||
return false
|
||||
}
|
||||
|
||||
if _, ok := j.month[t.month]; !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// regexps for parsing schedule string
|
||||
var (
|
||||
matchSpaces = regexp.MustCompile(`\s+`)
|
||||
matchN = regexp.MustCompile(`(.*)/(\d+)`)
|
||||
matchRange = regexp.MustCompile(`^(\d+)-(\d+)$`)
|
||||
)
|
||||
|
||||
// parseSchedule string and creates job struct with filled times to launch, or error if synthax is wrong
|
||||
func parseSchedule(s string) (*job, error) {
|
||||
var err error
|
||||
j := &job{}
|
||||
j.Lock()
|
||||
defer j.Unlock()
|
||||
s = matchSpaces.ReplaceAllLiteralString(s, " ")
|
||||
parts := strings.Split(s, " ")
|
||||
if len(parts) != 5 {
|
||||
return j, errors.New("Schedule string must have five components like * * * * *")
|
||||
}
|
||||
|
||||
j.min, err = parsePart(parts[0], 0, 59)
|
||||
if err != nil {
|
||||
return j, err
|
||||
}
|
||||
|
||||
j.hour, err = parsePart(parts[1], 0, 23)
|
||||
if err != nil {
|
||||
return j, err
|
||||
}
|
||||
|
||||
j.day, err = parsePart(parts[2], 1, 31)
|
||||
if err != nil {
|
||||
return j, err
|
||||
}
|
||||
|
||||
j.month, err = parsePart(parts[3], 1, 12)
|
||||
if err != nil {
|
||||
return j, err
|
||||
}
|
||||
|
||||
j.dayOfWeek, err = parsePart(parts[4], 0, 6)
|
||||
if err != nil {
|
||||
return j, err
|
||||
}
|
||||
|
||||
// day/dayOfWeek combination
|
||||
switch {
|
||||
case len(j.day) < 31 && len(j.dayOfWeek) == 7: // day set, but not dayOfWeek, clear dayOfWeek
|
||||
j.dayOfWeek = make(map[int]struct{})
|
||||
case len(j.dayOfWeek) < 7 && len(j.day) == 31: // dayOfWeek set, but not day, clear day
|
||||
j.day = make(map[int]struct{})
|
||||
default:
|
||||
// both day and dayOfWeek are * or both are set, use combined
|
||||
// i.e. don't do anything here
|
||||
}
|
||||
|
||||
return j, nil
|
||||
}
|
||||
|
||||
// parsePart parse individual schedule part from schedule string
|
||||
func parsePart(s string, min, max int) (map[int]struct{}, error) {
|
||||
|
||||
r := make(map[int]struct{})
|
||||
|
||||
// wildcard pattern
|
||||
if s == "*" {
|
||||
for i := min; i <= max; i++ {
|
||||
r[i] = struct{}{}
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
|
||||
// */2 1-59/5 pattern
|
||||
if matches := matchN.FindStringSubmatch(s); matches != nil {
|
||||
localMin := min
|
||||
localMax := max
|
||||
if matches[1] != "" && matches[1] != "*" {
|
||||
if rng := matchRange.FindStringSubmatch(matches[1]); rng != nil {
|
||||
localMin, _ = strconv.Atoi(rng[1])
|
||||
localMax, _ = strconv.Atoi(rng[2])
|
||||
if localMin < min || localMax > max {
|
||||
return nil, fmt.Errorf("Out of range for %s in %s. %s must be in range %d-%d", rng[1], s, rng[1], min, max)
|
||||
}
|
||||
} else {
|
||||
return nil, fmt.Errorf("Unable to parse %s part in %s", matches[1], s)
|
||||
}
|
||||
}
|
||||
n, _ := strconv.Atoi(matches[2])
|
||||
for i := localMin; i <= localMax; i += n {
|
||||
r[i] = struct{}{}
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
|
||||
// 1,2,4 or 1,2,10-15,20,30-45 pattern
|
||||
parts := strings.Split(s, ",")
|
||||
for _, x := range parts {
|
||||
if rng := matchRange.FindStringSubmatch(x); rng != nil {
|
||||
localMin, _ := strconv.Atoi(rng[1])
|
||||
localMax, _ := strconv.Atoi(rng[2])
|
||||
if localMin < min || localMax > max {
|
||||
return nil, fmt.Errorf("Out of range for %s in %s. %s must be in range %d-%d", x, s, x, min, max)
|
||||
}
|
||||
for i := localMin; i <= localMax; i++ {
|
||||
r[i] = struct{}{}
|
||||
}
|
||||
} else if i, err := strconv.Atoi(x); err == nil {
|
||||
if i < min || i > max {
|
||||
return nil, fmt.Errorf("Out of range for %d in %s. %d must be in range %d-%d", i, s, i, min, max)
|
||||
}
|
||||
r[i] = struct{}{}
|
||||
} else {
|
||||
return nil, fmt.Errorf("Unable to parse %s part in %s", x, s)
|
||||
}
|
||||
}
|
||||
|
||||
if len(r) == 0 {
|
||||
return nil, fmt.Errorf("Unable to parse %s", s)
|
||||
}
|
||||
|
||||
return r, nil
|
||||
}
|
||||
|
||||
// getTick returns the tick struct from time
|
||||
func getTick(t time.Time) tick {
|
||||
return tick{
|
||||
min: t.Minute(),
|
||||
hour: t.Hour(),
|
||||
day: t.Day(),
|
||||
month: int(t.Month()),
|
||||
dayOfWeek: int(t.Weekday()),
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user