Files
sing-box-extended/provider/local/provider.go

130 lines
3.6 KiB
Go

package provider
import (
"context"
"os"
"path/filepath"
"time"
"github.com/sagernet/fswatch"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/adapter/provider"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-box/parser"
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
F "github.com/sagernet/sing/common/format"
"github.com/sagernet/sing/service"
"github.com/sagernet/sing/service/filemanager"
)
func RegisterProviderLocal(registry *provider.Registry) {
provider.Register[option.ProviderLocalOptions](registry, C.ProviderTypeLocal, NewProviderLocal)
}
func RegisterProviderInline(registry *provider.Registry) {
provider.Register[option.ProviderInlineOptions](registry, C.ProviderTypeInline, NewProviderInline)
}
var _ adapter.Provider = (*ProviderLocal)(nil)
type ProviderLocal struct {
provider.Adapter
ctx context.Context
logger log.ContextLogger
provider adapter.ProviderManager
path string
lastOutOpts []option.Outbound
lastUpdated time.Time
watcher *fswatch.Watcher
}
func NewProviderInline(ctx context.Context, router adapter.Router, logFactory log.Factory, tag string, options option.ProviderInlineOptions) (adapter.Provider, error) {
var (
outbound = service.FromContext[adapter.OutboundManager](ctx)
logger = logFactory.NewLogger(F.ToString("provider/inline", "[", tag, "]"))
)
provider := &ProviderLocal{
Adapter: provider.NewAdapter(ctx, router, outbound, logFactory, logger, tag, C.ProviderTypeInline, options.HealthCheck),
ctx: ctx,
logger: logger,
}
provider.UpdateOutbounds(nil, options.Outbounds)
return provider, nil
}
func NewProviderLocal(ctx context.Context, router adapter.Router, logFactory log.Factory, tag string, options option.ProviderLocalOptions) (adapter.Provider, error) {
if options.Path == "" {
return nil, E.New("provider path is required")
}
var (
outbound = service.FromContext[adapter.OutboundManager](ctx)
logger = logFactory.NewLogger(F.ToString("provider/local", "[", tag, "]"))
)
provider := &ProviderLocal{
Adapter: provider.NewAdapter(ctx, router, outbound, logFactory, logger, tag, C.ProviderTypeLocal, options.HealthCheck),
ctx: ctx,
logger: logger,
provider: service.FromContext[adapter.ProviderManager](ctx),
}
filePath := filemanager.BasePath(ctx, options.Path)
provider.path, _ = filepath.Abs(filePath)
watcher, err := fswatch.NewWatcher(fswatch.Options{
Path: []string{filePath},
Callback: func(path string) {
uErr := provider.reloadFile(path)
if uErr != nil {
logger.Error(E.Cause(uErr, "reload provider ", tag))
}
provider.UpdateGroups()
},
})
if err != nil {
return nil, err
}
provider.watcher = watcher
return provider, nil
}
func (s *ProviderLocal) Start() error {
err := s.reloadFile(s.path)
if err != nil {
return err
}
s.UpdateGroups()
if s.watcher != nil {
err := s.watcher.Start()
if err != nil {
s.logger.Error(E.Cause(err, "watch provider file"))
}
}
return s.Adapter.Start()
}
func (s *ProviderLocal) UpdatedAt() time.Time {
return s.lastUpdated
}
func (s *ProviderLocal) reloadFile(path string) error {
if fileInfo, err := os.Stat(path); err == nil {
s.lastUpdated = fileInfo.ModTime()
}
content, err := os.ReadFile(path)
if err != nil {
return err
}
outboundOpts, err := parser.ParseSubscription(s.ctx, string(content))
if err != nil {
return err
}
s.UpdateOutbounds(s.lastOutOpts, outboundOpts)
s.lastOutOpts = outboundOpts
return nil
}
func (s *ProviderLocal) Close() error {
return common.Close(&s.Adapter, common.PtrOrNil(s.watcher))
}