126 lines
2.4 KiB
Go
126 lines
2.4 KiB
Go
package plugins
|
|
|
|
import (
|
|
"fmt"
|
|
"log"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
// Task represents a single background task instance.
|
|
type Task struct {
|
|
ID string
|
|
Interval time.Duration
|
|
Script string // relative path to .lua file (or "hook:name" for a Lua function)
|
|
IsHook bool // if true, Script is a function name to call via CallHook
|
|
stopCh chan struct{}
|
|
stopped bool
|
|
}
|
|
|
|
// Scheduler manages background tasks for a plugin.
|
|
type Scheduler struct {
|
|
plugin *Plugin
|
|
vm *LuaVM
|
|
tasks []*Task
|
|
mu sync.Mutex
|
|
wg sync.WaitGroup
|
|
}
|
|
|
|
// NewScheduler creates a scheduler for a plugin.
|
|
func NewScheduler(p *Plugin, vm *LuaVM) *Scheduler {
|
|
return &Scheduler{
|
|
plugin: p,
|
|
vm: vm,
|
|
}
|
|
}
|
|
|
|
// AddTask adds a task from a BackgroundTask definition.
|
|
func (s *Scheduler) AddTask(bg BackgroundTask) error {
|
|
dur, err := parseDuration(bg.Interval)
|
|
if err != nil {
|
|
return fmt.Errorf("task %s: %w", bg.ID, err)
|
|
}
|
|
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
|
|
task := &Task{
|
|
ID: bg.ID,
|
|
Interval: dur,
|
|
Script: bg.Script,
|
|
stopCh: make(chan struct{}),
|
|
}
|
|
s.tasks = append(s.tasks, task)
|
|
return nil
|
|
}
|
|
|
|
// Start begins all registered tasks.
|
|
func (s *Scheduler) Start() {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
|
|
for _, t := range s.tasks {
|
|
if t.stopped {
|
|
continue
|
|
}
|
|
s.wg.Add(1)
|
|
go s.runTask(t)
|
|
}
|
|
}
|
|
|
|
// Stop cancels all running tasks and waits for them to finish.
|
|
func (s *Scheduler) Stop() {
|
|
s.mu.Lock()
|
|
for _, t := range s.tasks {
|
|
if !t.stopped {
|
|
close(t.stopCh)
|
|
t.stopped = true
|
|
}
|
|
}
|
|
s.mu.Unlock()
|
|
s.wg.Wait()
|
|
}
|
|
|
|
func (s *Scheduler) runTask(t *Task) {
|
|
defer s.wg.Done()
|
|
|
|
ticker := time.NewTicker(t.Interval)
|
|
defer ticker.Stop()
|
|
|
|
for {
|
|
select {
|
|
case <-ticker.C:
|
|
s.executeTask(t)
|
|
case <-t.stopCh:
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
func (s *Scheduler) executeTask(t *Task) {
|
|
if s.vm == nil {
|
|
return
|
|
}
|
|
|
|
if t.IsHook {
|
|
if err := s.vm.CallHook(t.Script); err != nil {
|
|
log.Printf("[plugins] task %s/%s hook error: %v", s.plugin.Meta.Name, t.ID, err)
|
|
}
|
|
return
|
|
}
|
|
|
|
if err := s.vm.LoadScript(t.Script); err != nil {
|
|
log.Printf("[plugins] task %s/%s script error: %v", s.plugin.Meta.Name, t.ID, err)
|
|
}
|
|
}
|
|
|
|
// parseDuration parses a human-readable interval like "5m", "1h", "30s".
|
|
func parseDuration(s string) (time.Duration, error) {
|
|
d, err := time.ParseDuration(s)
|
|
if err == nil {
|
|
return d, nil
|
|
}
|
|
// Try cron-like or other formats later
|
|
return 0, fmt.Errorf("invalid interval %q: use Go duration format (e.g. 5m, 1h, 30s)", s)
|
|
}
|