| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191 |
- package oncalendarfile
- import (
- "context"
- "os"
- "sync"
- "time"
- "github.com/OliveTin/OliveTin/internal/auth"
- "github.com/OliveTin/OliveTin/internal/config"
- "github.com/OliveTin/OliveTin/internal/executor"
- "github.com/OliveTin/OliveTin/internal/filehelper"
- log "github.com/sirupsen/logrus"
- "gopkg.in/yaml.v3"
- )
- type timerEntry struct {
- timer *time.Timer
- cancel context.CancelFunc
- }
- type existingTimers struct {
- timers map[time.Time]timerEntry
- }
- var (
- scheduleMap = make(map[string]existingTimers)
- scheduleMapMutex sync.RWMutex
- )
- func Schedule(cfg *config.Config, ex *executor.Executor) {
- for _, action := range cfg.Actions {
- captured := action
- if action.ExecOnCalendarFile != "" {
- x := func(filename string) {
- parseCalendarFile(captured, cfg, ex, filename)
- }
- go filehelper.WatchFileWrite(action.ExecOnCalendarFile, x)
- x(action.ExecOnCalendarFile)
- }
- }
- }
- func clearExistingTimers(action *config.Action) {
- scheduleMapMutex.Lock()
- defer scheduleMapMutex.Unlock()
- if _, exists := scheduleMap[action.ID]; exists {
- for instant, entry := range scheduleMap[action.ID].timers {
- log.WithFields(log.Fields{
- "instant": instant,
- "actionTitle": action.Title,
- }).Infof("Clearing existing scheduled action from calendar")
- entry.cancel()
- entry.timer.Stop()
- }
- }
- scheduleMap[action.ID] = existingTimers{
- timers: make(map[time.Time]timerEntry),
- }
- }
- func parseCalendarFile(action *config.Action, cfg *config.Config, ex *executor.Executor, filename string) {
- clearExistingTimers(action)
- filehelper.Touch(action.ExecOnCalendarFile, "calendar file")
- log.WithFields(log.Fields{
- "actionTitle": action.Title,
- "filename": filename,
- }).Infof("Parsing calendar file")
- yfile, err := os.ReadFile(action.ExecOnCalendarFile)
- if err != nil {
- log.Errorf("ReadIn: %v", err)
- return
- }
- data := make([]string, 1)
- err = yaml.Unmarshal(yfile, &data)
- if err != nil {
- log.Errorf("Unmarshal: %v", err)
- }
- scheduleCalendarActions(data, action, cfg, ex)
- }
- func scheduleCalendarActions(entries []string, action *config.Action, cfg *config.Config, ex *executor.Executor) {
- ctx := context.Background()
- for _, instant := range entries {
- if instant == "" {
- continue
- }
- until, err := time.Parse(time.RFC3339, instant)
- if err != nil {
- log.WithFields(log.Fields{
- "instant": instant,
- "actionTitle": action.Title,
- }).Warnf("Invalid calendar entry, skipping: %v", err)
- continue
- }
- go sleepUntil(ctx, until, action, cfg, ex)
- }
- }
- func registerTimer(action *config.Action, instant time.Time, timer *time.Timer, cancel context.CancelFunc) {
- scheduleMapMutex.Lock()
- defer scheduleMapMutex.Unlock()
- if _, exists := scheduleMap[action.ID]; !exists {
- scheduleMap[action.ID] = existingTimers{
- timers: make(map[time.Time]timerEntry),
- }
- }
- scheduleMap[action.ID].timers[instant] = timerEntry{
- timer: timer,
- cancel: cancel,
- }
- }
- func unregisterTimer(action *config.Action, instant time.Time) {
- scheduleMapMutex.Lock()
- v := scheduleMap[action.ID]
- if v.timers != nil {
- delete(v.timers, instant)
- }
- scheduleMapMutex.Unlock()
- }
- func sleepUntil(ctx context.Context, instant time.Time, action *config.Action, cfg *config.Config, ex *executor.Executor) {
- if time.Now().After(instant) {
- log.WithFields(log.Fields{
- "instant": instant,
- "actionTitle": action.Title,
- }).Warnf("Not scheduling stale calendar action")
- return
- }
- log.WithFields(log.Fields{
- "instant": instant,
- "actionTitle": action.Title,
- }).Infof("Scheduling action on calendar")
- childCtx, cancel := context.WithCancel(ctx)
- timer := time.NewTimer(time.Until(instant))
- registerTimer(action, instant, timer, cancel)
- defer timer.Stop()
- defer cancel()
- select {
- case <-timer.C:
- unregisterTimer(action, instant)
- exec(instant, action, cfg, ex)
- return
- case <-childCtx.Done():
- unregisterTimer(action, instant)
- log.Infof("Cancelled scheduled action")
- return
- }
- }
- func exec(instant time.Time, action *config.Action, cfg *config.Config, ex *executor.Executor) {
- log.WithFields(log.Fields{
- "instant": instant,
- "actionTitle": action.Title,
- }).Infof("Executing action from calendar")
- req := &executor.ExecutionRequest{
- Binding: ex.FindBindingWithNoEntity(action),
- Cfg: cfg,
- Tags: []string{},
- AuthenticatedUser: auth.UserFromSystem(cfg, "calendar"),
- }
- ex.ExecRequest(req)
- }
|