github.com/Aoi-hosizora/ahlib-more@v1.5.1-0.20230404072844-256112befaf6/xtask/xtask.go (about) 1 package xtask 2 3 import ( 4 "fmt" 5 "github.com/Aoi-hosizora/ahlib/xcolor" 6 "github.com/Aoi-hosizora/ahlib/xreflect" 7 "github.com/Aoi-hosizora/ahlib/xslice" 8 "github.com/robfig/cron/v3" 9 "log" 10 "reflect" 11 "runtime" 12 "sync" 13 ) 14 15 // CronTask represents a job collection, or called a task, which is implemented by wrapping cron.Cron. 16 type CronTask struct { 17 cron *cron.Cron 18 jobs []*FuncJob 19 muJob sync.RWMutex 20 21 addedCallback func(job *FuncJob) 22 removedCallback func(job *FuncJob) 23 scheduledCallback func(job *FuncJob) 24 panicHandler func(job *FuncJob, v interface{}) 25 } 26 27 // FuncJob represents a cron.Job with some information such as title, cron.Schedule and cron.Entry, stored in CronTask. 28 type FuncJob struct { 29 title string 30 cronSpec string // can be empty 31 schedule cron.Schedule // can be nil 32 function func() 33 entry *cron.Entry 34 entryID cron.EntryID 35 36 parent *CronTask 37 } 38 39 var _ cron.Job = (*FuncJob)(nil) 40 41 // ======== 42 // CronTask 43 // ======== 44 45 // NewCronTask creates a default CronTask with given cron.Cron and default callbacks and handlers. 46 func NewCronTask(c *cron.Cron) *CronTask { 47 return &CronTask{ 48 cron: c, 49 jobs: make([]*FuncJob, 0), 50 51 addedCallback: DefaultAddedCallback, 52 removedCallback: defaultJobRemovedCallback, 53 scheduledCallback: nil, // defaults to do nothing 54 panicHandler: func(job *FuncJob, v interface{}) { 55 log.Printf("xtask warning: Job \"%s\" panicked with `%v`", job.title, v) 56 }, 57 } 58 } 59 60 // Cron returns cron.Cron from CronTask. 61 func (c *CronTask) Cron() *cron.Cron { 62 return c.cron 63 } 64 65 // ScheduleParser returns cron.ScheduleParser from cron.Cron in CronTask. 66 func (c *CronTask) ScheduleParser() cron.ScheduleParser { 67 return xreflect.GetUnexportedField(xreflect.FieldValueOf(c.cron, "parser")).Interface().(cron.ScheduleParser) 68 } 69 70 // Jobs returns FuncJob slice from CronTask. 71 func (c *CronTask) Jobs() []*FuncJob { 72 c.muJob.RLock() 73 out := c.jobs 74 c.muJob.RUnlock() 75 return out 76 } 77 78 const ( 79 panicNilFunction = "xtask: nil job function" 80 panicNilSchedule = "xtask: nil cron schedule" 81 ) 82 83 // AddJobByCronSpec adds a FuncJob to cron.Cron and CronTask by given repeatable title, cron spec and function. 84 func (c *CronTask) AddJobByCronSpec(title string, spec string, f func()) (cron.EntryID, error) { 85 if f == nil { 86 panic(panicNilFunction) 87 } 88 schedule, err := c.ScheduleParser().Parse(spec) // use cron.Schedule() rather than cron.AddJob() 89 if err != nil { 90 return 0, err 91 } 92 return c.addJob(title, spec, schedule, f), nil // by spec when spec is not empty 93 } 94 95 // AddJobBySchedule adds a FuncJob to cron.Cron and CronTask by given repeatable title, cron.Schedule and function. 96 func (c *CronTask) AddJobBySchedule(title string, schedule cron.Schedule, f func()) cron.EntryID { 97 if schedule == nil { 98 panic(panicNilSchedule) 99 } 100 if f == nil { 101 panic(panicNilFunction) 102 } 103 return c.addJob(title, "", schedule, f) // by schedule when spec is empty 104 } 105 106 // addJob adds given FuncJob's fields to cron.Cron using given parsed cron.Schedule and returns the added cron.EntryID. 107 func (c *CronTask) addJob(title string, spec string, schedule cron.Schedule, f func()) cron.EntryID { 108 job := &FuncJob{parent: c, title: title, cronSpec: spec /* maybe empty */, schedule: schedule, function: f} 109 110 c.muJob.Lock() 111 id := c.cron.Schedule(schedule, job) // always use schedule 112 entry := c.cron.Entry(id) 113 job.entry = &entry 114 job.entryID = entry.ID 115 c.jobs = append(c.jobs, job) 116 c.muJob.Unlock() 117 118 if c.addedCallback != nil { 119 c.addedCallback(job) 120 } 121 return id 122 } 123 124 // RemoveJob removes a cron.Entry by given cron.EntryID from cron.Cron and CronTask. 125 func (c *CronTask) RemoveJob(id cron.EntryID) { 126 c.muJob.Lock() 127 c.cron.Remove(id) 128 c.jobs = xslice.DeleteAllWithG(c.jobs, &FuncJob{entryID: id}, func(i, j interface{}) bool { 129 if i.(*FuncJob).entryID == j.(*FuncJob).entryID { 130 if c.removedCallback != nil { 131 c.removedCallback(i.(*FuncJob)) 132 } 133 return true 134 } 135 return false 136 }).([]*FuncJob) 137 c.muJob.Unlock() 138 } 139 140 // DefaultAddedCallback is the default CronTask's addedCallback, can be modified by CronTask.SetAddedCallback. 141 // 142 // The default callback logs like (just like gin.DebugPrintRouteFunc): 143 // [Task] job1, 0/1 * * * * * --> ... (EntryID: 1) 144 // [Task] job3, every 3s --> ... (EntryID: 3) 145 // [Task] job4, <parsed SpecSchedule> --> ... (EntryID: 4) 146 // |-------------------------------| |----------------| 147 // 31 ... 148 func DefaultAddedCallback(j *FuncJob) { 149 fmt.Printf("[Task] %-31s --> %s (EntryID: %d)\n", fmt.Sprintf("%s, %s", j.Title(), j.ScheduleExpr()), j.Funcname(), j.EntryID()) 150 } 151 152 // DefaultColorizedAddedCallback is the DefaultAddedCallback (CronTask's addedCallback) in color. 153 // 154 // The default callback logs like (just like gin.DebugPrintRouteFunc): 155 // [Task] job1, 0/1 * * * * * --> ... (EntryID: 1) 156 // [Task] job3, every 3s --> ... (EntryID: 3) 157 // [Task] job4, <parsed SpecSchedule> --> ... (EntryID: 4) 158 // |-------------------------------| |----------------| 159 // 31 (blue) ... 160 func DefaultColorizedAddedCallback(j *FuncJob) { 161 fmt.Printf("[Task] %s --> %s (EntryID: %d)\n", xcolor.Blue.ASprintf(-31, "%s, %s", j.Title(), j.ScheduleExpr()), j.Funcname(), j.EntryID()) 162 } 163 164 // defaultJobRemovedCallback is the default removedCallback, can be modified by CronTask.SetRemovedCallback 165 // 166 // The default callback logs like: 167 // [Task] Remove job: job1, EntryID: 1 168 func defaultJobRemovedCallback(j *FuncJob) { 169 fmt.Printf("[Task] Remove job: %s, EntryID: %d\n", j.Title(), j.EntryID()) 170 } 171 172 // SetAddedCallback sets job added callback, this will be invoked after FuncJob added, defaults to DefaultAddedCallback. 173 func (c *CronTask) SetAddedCallback(cb func(job *FuncJob)) { 174 c.addedCallback = cb 175 } 176 177 // SetRemovedCallback sets job removed callback, this will be invoked after FuncJob removed, defaults to defaultJobRemovedCallback. 178 func (c *CronTask) SetRemovedCallback(cb func(job *FuncJob)) { 179 c.removedCallback = cb 180 } 181 182 // SetScheduledCallback sets job scheduled callback, this will be invoked when after FuncJob scheduled, defaults to do nothing. 183 func (c *CronTask) SetScheduledCallback(cb func(job *FuncJob)) { 184 c.scheduledCallback = cb 185 } 186 187 // SetPanicHandler sets panic handler for jobs executing, defaults to print warning message. 188 func (c *CronTask) SetPanicHandler(handler func(job *FuncJob, v interface{})) { 189 c.panicHandler = handler 190 } 191 192 // ======= 193 // FuncJob 194 // ======= 195 196 // Title returns title from FuncJob. 197 func (f *FuncJob) Title() string { 198 return f.title 199 } 200 201 // CronSpec returns cron spec string from FuncJob. 202 func (f *FuncJob) CronSpec() string { 203 return f.cronSpec 204 } 205 206 // Schedule returns cron.Schedule from FuncJob. 207 func (f *FuncJob) Schedule() cron.Schedule { 208 return f.schedule 209 } 210 211 // ScheduleExpr returns schedule expr from FuncJob, is generated by cron spec string and cron.Schedule. 212 func (f *FuncJob) ScheduleExpr() string { 213 if f.cronSpec != "" { // by spec 214 return f.cronSpec 215 } 216 if _, ok := f.schedule.(*cron.SpecSchedule); ok { 217 return "<parsed SpecSchedule>" // hide origin spec 218 } 219 if s, ok := f.schedule.(cron.ConstantDelaySchedule); ok { 220 return fmt.Sprintf("every %s", s.Delay.String()) 221 } 222 return "<unknown Schedule>" 223 } 224 225 // Funcname returns job function name from FuncJob. 226 func (f *FuncJob) Funcname() string { 227 return runtime.FuncForPC(reflect.ValueOf(f.function).Pointer()).Name() 228 } 229 230 // Entry returns cron.Entry from FuncJob. 231 func (f *FuncJob) Entry() *cron.Entry { 232 return f.entry 233 } 234 235 // EntryID returns cron.EntryID from FuncJob. 236 func (f *FuncJob) EntryID() cron.EntryID { 237 return f.entryID 238 } 239 240 // Run runs the FuncJob with panic handler, this implements cron.Job interface. 241 func (f *FuncJob) Run() { 242 defer func() { 243 v := recover() 244 if v != nil && f.parent.panicHandler != nil { 245 f.parent.panicHandler(f, v) // defaults to print warning message 246 } 247 }() 248 249 if f.parent.scheduledCallback != nil { 250 f.parent.scheduledCallback(f) // defaults to do nothing 251 } 252 f.function() 253 }