196 lines
4.5 KiB
Go
196 lines
4.5 KiB
Go
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, filehelper.WatchMeta{
|
|
ActionID: action.ID,
|
|
ActionTitle: action.Title,
|
|
ConfigFile: action.SourceFile,
|
|
})
|
|
|
|
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)
|
|
}
|