diff --git a/crontab.go b/crontab.go index 32ad39e..3111c3b 100644 --- a/crontab.go +++ b/crontab.go @@ -9,9 +9,27 @@ import ( "strings" "time" + "github.com/Pallinder/go-randomdata" + "github.com/davecgh/go-spew/spew" ) +type randLR struct { + left, right int +} + +type hInterval struct { + PlanFail []randLR + PlanNormal []randLR + + Count int + ConstCount int +} + +func (interval *hInterval) reset() { + interval.Count = interval.ConstCount +} + type timePointer struct { left, right int leftlimit, rightlimit int @@ -35,6 +53,10 @@ type Crontab struct { SkipPlans []time.Time YearPlan *trieYear + + interval *CircularLinked + lastStatus bool + nextTime time.Time } // NewCrontab create 一个crontab @@ -44,6 +66,31 @@ func NewCrontab(crontab string) *Crontab { return cron } +// SetStatus 设置状态 接口定义 +func (cron *Crontab) SetStatus(status interface{}) { + if cron.interval != nil { + cron.lastStatus = status.(bool) + } +} + +// GetStatus 设置状态 接口定义 +func (cron *Crontab) GetStatus() (status interface{}) { + if cron.interval != nil { + return cron.lastStatus + } + return nil +} + +// TimeUp 是否时间快到 +func (cron *Crontab) TimeUp() bool { + + if cron.interval != nil { + return cron.intervalTimeUp() + } + + return cron.linuxTimeUp() +} + func (cron *Crontab) String() string { return fmt.Sprintf("min:%s\nhour:%s\nday:%s\nmonth:%s\nweek:%s\n", spew.Sdump(cron.min), spew.Sdump(cron.hour), spew.Sdump(cron.day), spew.Sdump(cron.month), spew.Sdump(cron.week)) } @@ -56,6 +103,13 @@ func (cron *Crontab) FromString(crontab string) error { mlen := len(matches) switch mlen { case 1: + // "f1-2|5-10x5,f1|10m,10-15,f1" + cron.nextTime = time.Now() + cron.lastStatus = true + cron.interval = NewCircularLinked() + var intervalList []interface{} + intervalList = parseIntervalString(matches[0]) + cron.interval.Append(intervalList...) case 5: cron.min = createTimePointer(matches[0], 0, 59, true) @@ -64,7 +118,7 @@ func (cron *Crontab) FromString(crontab string) error { cron.month = createTimePointer(matches[3], 1, 12, true) cron.week = createTimePointer(matches[4], 0, 6, true) - cron.CreateYearPlan() + cron.createYearPlan() default: return errors.New("mathches len != want, check crontab string") } @@ -72,15 +126,13 @@ func (cron *Crontab) FromString(crontab string) error { return nil } -// CreateYearPlan 创建年度计划 -func (cron *Crontab) CreateYearPlan() { +// createYearPlan 创建年度计划 +func (cron *Crontab) createYearPlan() { cron.YearPlan = newTrieYear() cron.YearPlan.FromCrontab(cron) } -// TimeUp 是否时间快到 -func (cron *Crontab) TimeUp() bool { - +func (cron *Crontab) linuxTimeUp() bool { now := time.Now() maxlen := 1000 createlen := 500 @@ -94,7 +146,7 @@ func (cron *Crontab) TimeUp() bool { lastplan = cron.WillPlans[plen-1].Add(time.Minute) } if !cron.YearPlan.CheckYear() { - cron.CreateYearPlan() + cron.createYearPlan() } timeplans := cron.YearPlan.GetPlanTime(cron, lastplan, uint(maxlen-plen)) @@ -130,6 +182,36 @@ func (cron *Crontab) TimeUp() bool { return false } +func (cron *Crontab) intervalTimeUp() bool { + + now := time.Now() + if now.Unix() >= cron.nextTime.Unix() { + + iv := cron.interval.Cursor().GetValue().(hInterval) + isecond := 0 + if cron.lastStatus == false && len(iv.PlanFail) > 0 { + idx := randomdata.Number(len(iv.PlanFail)) + lr := iv.PlanFail[idx] + isecond = randomdata.Number(lr.left, lr.right+1) + } else { + idx := randomdata.Number(len(iv.PlanNormal)) + lr := iv.PlanNormal[idx] + isecond = randomdata.Number(lr.left, lr.right+1) + } + + iv.Count-- + if iv.Count <= 0 { + iv.reset() + cron.interval.MoveNext() + } + + cron.nextTime = now.Add(time.Duration(isecond) * time.Second) + return true + } + + return false +} + func createTimePointer(min string, llimit, rlimit int, fixedLeftRight bool) []timePointer { var result []timePointer @@ -217,3 +299,98 @@ func createTimePointer(min string, llimit, rlimit int, fixedLeftRight bool) []ti return result } + +func parseIntervalString(crontab string) []interface{} { + var result []interface{} + + values := strings.Split(crontab, ",") + for _, value := range values { + interval := hInterval{} + // 次数 + valuesCounts := strings.Split(value, "x") + switch len(valuesCounts) { + case 1: + interval.ConstCount = 1 + + case 2: + count, err := strconv.Atoi(valuesCounts[1]) + if err != nil { + panic(err) + } + interval.ConstCount = count + default: + panic("valuesCounts error, the len is not in range") + } + + // 统计失败与普通间隔值的数组 + failAndNormal := valuesCounts[0] + + valuesFN := strings.Split(failAndNormal, "|") + + for _, FN := range valuesFN { + if FN == "" { + continue + } + + switch FN[0] { + case 'f', 'F': + fvalue := FN[1:] + interval.PlanFail = append(interval.PlanFail, parseRandLR(fvalue)) + case 'n': + value := FN[1:] + interval.PlanNormal = append(interval.PlanNormal, parseRandLR(value)) + default: + interval.PlanNormal = append(interval.PlanNormal, parseRandLR(FN)) + } + } + + interval.reset() + result = append(result, interval) + } + + return result +} + +func parseRandLR(value string) randLR { + vlen := len(value) + lastchar := value[vlen-1] + + lr := strings.Split(value, "-") + switch len(lr) { + case 1: + lr := randLR{parseTimeValue(lr[0], lastchar), parseTimeValue(lr[0], lastchar)} + return lr + case 2: + + lr := randLR{parseTimeValue(lr[0], lastchar), parseTimeValue(lr[1], lastchar)} + return lr + default: + panic("lr is error") + } +} + +func getInt(v string) int { + vint, err := strconv.Atoi(v) + if err != nil { + panic(err) + } + return vint +} + +func parseTimeValue(v string, lastchar byte) int { + + vlen := len(v) + + switch lastchar { + case 's': + return getInt(v[:vlen-1]) + case 'm': + return getInt(v[:vlen-1]) * 60 + case 'h': + return getInt(v[:vlen-1]) * 3600 + case 'd': + return getInt(v[:vlen-1]) * 3600 * 24 + default: + return getInt(v) + } +} diff --git a/crontab_test.go b/crontab_test.go index 59ca0da..a9368c6 100644 --- a/crontab_test.go +++ b/crontab_test.go @@ -25,7 +25,7 @@ func TestParseCrontab(t *testing.T) { t.Error("GetPlanTime error len != 10") } - cron.CreateYearPlan() + cron.createYearPlan() if !cron.TimeUp() { t.Error("timeup error") } @@ -34,6 +34,26 @@ func TestParseCrontab(t *testing.T) { } +func TestParseInterval(t *testing.T) { + // crontab := "0-5/2,7-30/3,30,35,40-^1 * * * *" //(秒) 分 时 号(每月的多少号, 要注意月可可能性) 星期几(每个星期的) /每 ,列表 -范围 + // t.Error("") + // crontab := "f1-2|1-3|5-8x5,f1|10m,10-15,f1" + + // PrintMemUsage() + // cron := NewCrontab(crontab) + + // now := time.Now() + // for i := 0; i <= 15; i++ { + // if cron.TimeUp() { + // log.Println(time.Since(now)) + // now = time.Now() + // } + // time.Sleep(time.Second) + // } + + // PrintMemUsage() +} + func TestCrontabPlus(t *testing.T) { // crontab := "0-5/2,7-30/3,30,35,40-^1 * * * *" //(秒) 分 时 号(每月的多少号, 要注意月可可能性) 星期几(每个星期的) /每 ,列表 -范围 // crondata := NewCrontab("*22 * * * *") diff --git a/structure.go b/structure.go index 180bcc1..8415f0a 100644 --- a/structure.go +++ b/structure.go @@ -1,6 +1,12 @@ package curl2info -import "container/heap" +import ( + "container/heap" + "log" + "strings" + + "github.com/davecgh/go-spew/spew" +) // trieWord Trie 需要的Word接口 type trieWord interface { @@ -242,3 +248,234 @@ func (pqe *pQueueExecute) Len() int { // } // return content // } + +// CNode 循环链表 三色标记 不确定是否会清除循环引用, 网上说会 +type CNode struct { + value interface{} + prev *CNode + next *CNode +} + +// GetValue 获取到Node的值 +func (node *CNode) GetValue() interface{} { + return node.value +} + +// SetValue 获取到Node的值 +func (node *CNode) SetValue(value interface{}) { + node.value = value +} + +// CircularLinked 循环链表 +type CircularLinked struct { + cursor *CNode + head *CNode + tail *CNode + size uint64 +} + +// NewCircularLinked create a CircularLinked +func NewCircularLinked(values ...interface{}) *CircularLinked { + list := &CircularLinked{} + if len(values) > 0 { + list.Append(values...) + } + return list +} + +// Cursor get current Cursor +func (list *CircularLinked) Cursor() *CNode { + if list.cursor == nil { + list.cursor = list.head + } + return list.cursor +} + +// MoveNext get next Cursor +func (list *CircularLinked) MoveNext() *CNode { + if list.cursor == nil { + list.cursor = list.head + } + list.cursor = list.cursor.next + return list.cursor +} + +// MovePrev get prev Cursor +func (list *CircularLinked) MovePrev() *CNode { + if list.cursor == nil { + list.cursor = list.head + } + list.cursor = list.cursor.prev + return list.cursor +} + +// CursorToHead cursor move to head +func (list *CircularLinked) CursorToHead() *CNode { + list.cursor = list.head + return list.cursor +} + +// CursorToTail cursor move to tail +func (list *CircularLinked) CursorToTail() *CNode { + list.cursor = list.tail + return list.cursor +} + +// GetLoopValues 获取从头到尾的值 +func (list *CircularLinked) GetLoopValues() []*CNode { + var result []*CNode + + if list.head != nil { + result = append(result, list.head) + for cur := list.head.next; cur != list.head; cur = cur.next { + result = append(result, cur) + } + } + return result +} + +// Append a value (one or more) at the end of the list (same as Append()) +func (list *CircularLinked) Append(values ...interface{}) { + for _, value := range values { + node := &CNode{value: value} + if list.size == 0 { + list.head = node + list.tail = node + node.next = node + node.prev = node + } else { + list.tail.next = node + node.next = list.head + node.prev = list.tail + list.tail = node + } + list.size++ + } +} + +// Remove 移除一些节点 +func (list *CircularLinked) Remove(node *CNode) { + + switch list.size { + case 0: + list.errorNotInList(node) + case 1: + if list.head == node { + list.head = nil + list.tail = nil + node.next = nil + node.prev = nil + list.cursor = nil + list.size-- + } else { + list.errorNotInList(node) + } + case 2: + + node.prev = nil + node.next = nil + + switch node { + case list.head: + list.head = list.tail + list.tail.prev = list.head + list.head.next = list.tail + list.cursor = list.head + list.size-- + case list.tail: + list.tail = list.head + list.tail.prev = list.head + list.head.next = list.tail + list.cursor = list.head + list.size-- + default: + list.errorNotInList(node) + } + + default: + switch node { + case list.head: + _, next := list.cutAndSplice(node) + list.size-- + list.head = next + if list.cursor == node { + list.cursor = next + } + case list.tail: + prev, _ := list.cutAndSplice(node) + list.size-- + list.tail = prev + if list.cursor == node { + list.cursor = prev + } + default: + _, next := list.cutAndSplice(node) + list.size-- + if list.cursor == node { + list.cursor = next + } + } + } + +} + +// LookCursor for list show +func (list *CircularLinked) LookCursor() string { + cursor := list.Cursor() + + content := "->[" + cur := list.head + if list.size != 0 { + for size := uint64(0); size < list.size; size++ { + if cursor == cur { + content += "(" + spew.Sprint(cur.value) + ")" + ", " + } else { + content += spew.Sprint(cur.value) + ", " + } + + cur = cur.next + } + } + content = strings.TrimRight(content, ", ") + showlen := len(content) + if showlen >= 64 { + showlen = 64 + } + content += "]" + content[0:showlen] + " ..." + return content +} + +// Clear for list show +func (list *CircularLinked) Clear() { + if list.size != 0 { + list.head.prev = nil + list.tail.next = nil + list.head = nil + list.tail = nil + list.cursor = nil + list.size = 0 + } +} + +// Size for list show +func (list *CircularLinked) Size() uint64 { + return list.size +} + +func (list *CircularLinked) errorNotInList(node *CNode) { + log.Println("the node value ", spew.Sprint(node), " is not in list") +} + +// cutAndSplice 不考虑边界情况 上层使用的是否判断 +func (list *CircularLinked) cutAndSplice(node *CNode) (prev, next *CNode) { + prev = node.prev + next = node.next + + prev.next = next + next.prev = prev + + node.prev = nil + node.next = nil + + return prev, next +}