calendar.go 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191
  1. package oncalendarfile
  2. import (
  3. "context"
  4. "os"
  5. "sync"
  6. "time"
  7. "github.com/OliveTin/OliveTin/internal/auth"
  8. "github.com/OliveTin/OliveTin/internal/config"
  9. "github.com/OliveTin/OliveTin/internal/executor"
  10. "github.com/OliveTin/OliveTin/internal/filehelper"
  11. log "github.com/sirupsen/logrus"
  12. "gopkg.in/yaml.v3"
  13. )
  14. type timerEntry struct {
  15. timer *time.Timer
  16. cancel context.CancelFunc
  17. }
  18. type existingTimers struct {
  19. timers map[time.Time]timerEntry
  20. }
  21. var (
  22. scheduleMap = make(map[string]existingTimers)
  23. scheduleMapMutex sync.RWMutex
  24. )
  25. func Schedule(cfg *config.Config, ex *executor.Executor) {
  26. for _, action := range cfg.Actions {
  27. captured := action
  28. if action.ExecOnCalendarFile != "" {
  29. x := func(filename string) {
  30. parseCalendarFile(captured, cfg, ex, filename)
  31. }
  32. go filehelper.WatchFileWrite(action.ExecOnCalendarFile, x)
  33. x(action.ExecOnCalendarFile)
  34. }
  35. }
  36. }
  37. func clearExistingTimers(action *config.Action) {
  38. scheduleMapMutex.Lock()
  39. defer scheduleMapMutex.Unlock()
  40. if _, exists := scheduleMap[action.ID]; exists {
  41. for instant, entry := range scheduleMap[action.ID].timers {
  42. log.WithFields(log.Fields{
  43. "instant": instant,
  44. "actionTitle": action.Title,
  45. }).Infof("Clearing existing scheduled action from calendar")
  46. entry.cancel()
  47. entry.timer.Stop()
  48. }
  49. }
  50. scheduleMap[action.ID] = existingTimers{
  51. timers: make(map[time.Time]timerEntry),
  52. }
  53. }
  54. func parseCalendarFile(action *config.Action, cfg *config.Config, ex *executor.Executor, filename string) {
  55. clearExistingTimers(action)
  56. filehelper.Touch(action.ExecOnCalendarFile, "calendar file")
  57. log.WithFields(log.Fields{
  58. "actionTitle": action.Title,
  59. "filename": filename,
  60. }).Infof("Parsing calendar file")
  61. yfile, err := os.ReadFile(action.ExecOnCalendarFile)
  62. if err != nil {
  63. log.Errorf("ReadIn: %v", err)
  64. return
  65. }
  66. data := make([]string, 1)
  67. err = yaml.Unmarshal(yfile, &data)
  68. if err != nil {
  69. log.Errorf("Unmarshal: %v", err)
  70. }
  71. scheduleCalendarActions(data, action, cfg, ex)
  72. }
  73. func scheduleCalendarActions(entries []string, action *config.Action, cfg *config.Config, ex *executor.Executor) {
  74. ctx := context.Background()
  75. for _, instant := range entries {
  76. if instant == "" {
  77. continue
  78. }
  79. until, err := time.Parse(time.RFC3339, instant)
  80. if err != nil {
  81. log.WithFields(log.Fields{
  82. "instant": instant,
  83. "actionTitle": action.Title,
  84. }).Warnf("Invalid calendar entry, skipping: %v", err)
  85. continue
  86. }
  87. go sleepUntil(ctx, until, action, cfg, ex)
  88. }
  89. }
  90. func registerTimer(action *config.Action, instant time.Time, timer *time.Timer, cancel context.CancelFunc) {
  91. scheduleMapMutex.Lock()
  92. defer scheduleMapMutex.Unlock()
  93. if _, exists := scheduleMap[action.ID]; !exists {
  94. scheduleMap[action.ID] = existingTimers{
  95. timers: make(map[time.Time]timerEntry),
  96. }
  97. }
  98. scheduleMap[action.ID].timers[instant] = timerEntry{
  99. timer: timer,
  100. cancel: cancel,
  101. }
  102. }
  103. func unregisterTimer(action *config.Action, instant time.Time) {
  104. scheduleMapMutex.Lock()
  105. v := scheduleMap[action.ID]
  106. if v.timers != nil {
  107. delete(v.timers, instant)
  108. }
  109. scheduleMapMutex.Unlock()
  110. }
  111. func sleepUntil(ctx context.Context, instant time.Time, action *config.Action, cfg *config.Config, ex *executor.Executor) {
  112. if time.Now().After(instant) {
  113. log.WithFields(log.Fields{
  114. "instant": instant,
  115. "actionTitle": action.Title,
  116. }).Warnf("Not scheduling stale calendar action")
  117. return
  118. }
  119. log.WithFields(log.Fields{
  120. "instant": instant,
  121. "actionTitle": action.Title,
  122. }).Infof("Scheduling action on calendar")
  123. childCtx, cancel := context.WithCancel(ctx)
  124. timer := time.NewTimer(time.Until(instant))
  125. registerTimer(action, instant, timer, cancel)
  126. defer timer.Stop()
  127. defer cancel()
  128. select {
  129. case <-timer.C:
  130. unregisterTimer(action, instant)
  131. exec(instant, action, cfg, ex)
  132. return
  133. case <-childCtx.Done():
  134. unregisterTimer(action, instant)
  135. log.Infof("Cancelled scheduled action")
  136. return
  137. }
  138. }
  139. func exec(instant time.Time, action *config.Action, cfg *config.Config, ex *executor.Executor) {
  140. log.WithFields(log.Fields{
  141. "instant": instant,
  142. "actionTitle": action.Title,
  143. }).Infof("Executing action from calendar")
  144. req := &executor.ExecutionRequest{
  145. Binding: ex.FindBindingWithNoEntity(action),
  146. Cfg: cfg,
  147. Tags: []string{},
  148. AuthenticatedUser: auth.UserFromSystem(cfg, "calendar"),
  149. }
  150. ex.ExecRequest(req)
  151. }