sing-box/script/manager.go
2025-03-21 21:52:44 +08:00

118 lines
3.3 KiB
Go

//go:build with_script
package script
import (
"context"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/taskmonitor"
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"
F "github.com/sagernet/sing/common/format"
"github.com/sagernet/sing/common/logger"
"github.com/sagernet/sing/common/task"
)
var _ adapter.ScriptManager = (*Manager)(nil)
type Manager struct {
ctx context.Context
logger logger.ContextLogger
scripts []adapter.Script
scriptByName map[string]adapter.Script
surgeCache *adapter.SurgeInMemoryCache
}
func NewManager(ctx context.Context, logFactory log.Factory, scripts []option.Script) (*Manager, error) {
manager := &Manager{
ctx: ctx,
logger: logFactory.NewLogger("script"),
scriptByName: make(map[string]adapter.Script),
}
for _, scriptOptions := range scripts {
script, err := NewScript(ctx, logFactory.NewLogger(F.ToString("script/", scriptOptions.Type, "[", scriptOptions.Tag, "]")), scriptOptions)
if err != nil {
return nil, E.Cause(err, "initialize script: ", scriptOptions.Tag)
}
manager.scripts = append(manager.scripts, script)
manager.scriptByName[scriptOptions.Tag] = script
}
return manager, nil
}
func (m *Manager) Start(stage adapter.StartStage) error {
monitor := taskmonitor.New(m.logger, C.StartTimeout)
switch stage {
case adapter.StartStateStart:
var cacheContext *adapter.HTTPStartContext
if len(m.scripts) > 0 {
monitor.Start("initialize rule-set")
cacheContext = adapter.NewHTTPStartContext(m.ctx)
var scriptStartGroup task.Group
for _, script := range m.scripts {
scriptInPlace := script
scriptStartGroup.Append0(func(ctx context.Context) error {
err := scriptInPlace.StartContext(ctx, cacheContext)
if err != nil {
return E.Cause(err, "initialize script/", scriptInPlace.Type(), "[", scriptInPlace.Tag(), "]")
}
return nil
})
}
scriptStartGroup.Concurrency(5)
scriptStartGroup.FastFail()
err := scriptStartGroup.Run(m.ctx)
monitor.Finish()
if err != nil {
return err
}
}
if cacheContext != nil {
cacheContext.Close()
}
case adapter.StartStatePostStart:
for _, script := range m.scripts {
monitor.Start(F.ToString("post start script/", script.Type(), "[", script.Tag(), "]"))
err := script.PostStart()
monitor.Finish()
if err != nil {
return E.Cause(err, "post start script/", script.Type(), "[", script.Tag(), "]")
}
}
}
return nil
}
func (m *Manager) Close() error {
monitor := taskmonitor.New(m.logger, C.StopTimeout)
var err error
for _, script := range m.scripts {
monitor.Start(F.ToString("close start script/", script.Type(), "[", script.Tag(), "]"))
err = E.Append(err, script.Close(), func(err error) error {
return E.Cause(err, "close script/", script.Type(), "[", script.Tag(), "]")
})
monitor.Finish()
}
return err
}
func (m *Manager) Scripts() []adapter.Script {
return m.scripts
}
func (m *Manager) Script(name string) (adapter.Script, bool) {
script, loaded := m.scriptByName[name]
return script, loaded
}
func (m *Manager) SurgeCache() *adapter.SurgeInMemoryCache {
if m.surgeCache == nil {
m.surgeCache = &adapter.SurgeInMemoryCache{
Data: make(map[string]string),
}
}
return m.surgeCache
}