sing-box/script/script_surge_cron.go
2025-02-02 17:27:29 +08:00

119 lines
2.9 KiB
Go

package script
import (
"context"
"time"
"github.com/sagernet/sing-box/adapter"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/logger"
"github.com/adhocore/gronx"
)
var _ adapter.GenericScript = (*SurgeCronScript)(nil)
type SurgeCronScript struct {
GenericScript
ctx context.Context
expression string
timer *time.Timer
}
func NewSurgeCronScript(ctx context.Context, logger logger.ContextLogger, options option.Script) (*SurgeCronScript, error) {
source, err := NewSource(ctx, logger, options)
if err != nil {
return nil, err
}
if !gronx.IsValid(options.CronOptions.Expression) {
return nil, E.New("invalid cron expression: ", options.CronOptions.Expression)
}
return &SurgeCronScript{
GenericScript: GenericScript{
logger: logger,
tag: options.Tag,
timeout: time.Duration(options.Timeout),
arguments: options.Arguments,
source: source,
},
ctx: ctx,
expression: options.CronOptions.Expression,
}, nil
}
func (s *SurgeCronScript) Type() string {
return C.ScriptTypeSurgeCron
}
func (s *SurgeCronScript) Tag() string {
return s.tag
}
func (s *SurgeCronScript) StartContext(ctx context.Context, startContext *adapter.HTTPStartContext) error {
return s.source.StartContext(ctx, startContext)
}
func (s *SurgeCronScript) PostStart() error {
err := s.source.PostStart()
if err != nil {
return err
}
go s.loop()
return nil
}
func (s *SurgeCronScript) loop() {
s.logger.Debug("starting event")
err := s.Run(s.ctx)
if err != nil {
s.logger.Error(E.Cause(err, "running event"))
}
nextTick, err := gronx.NextTick(s.expression, false)
if err != nil {
s.logger.Error(E.Cause(err, "determine next tick"))
return
}
s.timer = time.NewTimer(nextTick.Sub(time.Now()))
s.logger.Debug("next event at: ", nextTick.Format(log.DefaultTimeFormat))
for {
select {
case <-s.ctx.Done():
return
case <-s.timer.C:
s.logger.Debug("starting event")
err = s.Run(s.ctx)
if err != nil {
s.logger.Error(E.Cause(err, "running event"))
}
nextTick, err = gronx.NextTick(s.expression, false)
if err != nil {
s.logger.Error(E.Cause(err, "determine next tick"))
return
}
s.timer.Reset(nextTick.Sub(time.Now()))
s.logger.Debug("next event at: ", nextTick)
}
}
}
func (s *SurgeCronScript) Close() error {
return s.source.Close()
}
func (s *SurgeCronScript) Run(ctx context.Context) error {
program := s.source.Program()
if program == nil {
return E.New("invalid script")
}
ctx, cancel := context.WithCancelCause(ctx)
defer cancel(nil)
vm := NewRuntime(ctx, s.logger, cancel)
err := SetSurgeModules(vm, ctx, s.logger, cancel, s.Tag(), s.Type(), s.arguments)
if err != nil {
return err
}
return ExecuteSurgeGeneral(vm, program, ctx, s.timeout)
}