diff --git a/adapter/experimental.go b/adapter/experimental.go index f22ff9b2..648eb418 100644 --- a/adapter/experimental.go +++ b/adapter/experimental.go @@ -39,17 +39,17 @@ type CacheFile interface { StoreSelected(group string, selected string) error LoadGroupExpand(group string) (isExpand bool, loaded bool) StoreGroupExpand(group string, expand bool) error - LoadRuleSet(tag string) *SavedRuleSet - SaveRuleSet(tag string, set *SavedRuleSet) error + LoadRuleSet(tag string) *SavedBinary + SaveRuleSet(tag string, set *SavedBinary) error } -type SavedRuleSet struct { +type SavedBinary struct { Content []byte LastUpdated time.Time LastEtag string } -func (s *SavedRuleSet) MarshalBinary() ([]byte, error) { +func (s *SavedBinary) MarshalBinary() ([]byte, error) { var buffer bytes.Buffer err := binary.Write(&buffer, binary.BigEndian, uint8(1)) if err != nil { @@ -70,7 +70,7 @@ func (s *SavedRuleSet) MarshalBinary() ([]byte, error) { return buffer.Bytes(), nil } -func (s *SavedRuleSet) UnmarshalBinary(data []byte) error { +func (s *SavedBinary) UnmarshalBinary(data []byte) error { reader := bytes.NewReader(data) var version uint8 err := binary.Read(reader, binary.BigEndian, &version) diff --git a/experimental/cachefile/cache.go b/experimental/cachefile/cache.go index 498b9474..88cffdbe 100644 --- a/experimental/cachefile/cache.go +++ b/experimental/cachefile/cache.go @@ -284,8 +284,8 @@ func (c *CacheFile) StoreGroupExpand(group string, isExpand bool) error { }) } -func (c *CacheFile) LoadRuleSet(tag string) *adapter.SavedRuleSet { - var savedSet adapter.SavedRuleSet +func (c *CacheFile) LoadRuleSet(tag string) *adapter.SavedBinary { + var savedSet adapter.SavedBinary err := c.DB.View(func(t *bbolt.Tx) error { bucket := c.bucket(t, bucketRuleSet) if bucket == nil { @@ -303,7 +303,7 @@ func (c *CacheFile) LoadRuleSet(tag string) *adapter.SavedRuleSet { return &savedSet } -func (c *CacheFile) SaveRuleSet(tag string, set *adapter.SavedRuleSet) error { +func (c *CacheFile) SaveRuleSet(tag string, set *adapter.SavedBinary) error { return c.DB.Batch(func(t *bbolt.Tx) error { bucket, err := c.createBucket(t, bucketRuleSet) if err != nil { diff --git a/route/rule/rule_set_local.go b/route/rule/rule_set_local.go index efbc525e..2b44c50b 100644 --- a/route/rule/rule_set_local.go +++ b/route/rule/rule_set_local.go @@ -5,6 +5,7 @@ import ( "os" "path/filepath" "strings" + "sync" "github.com/sagernet/fswatch" "github.com/sagernet/sing-box/adapter" @@ -26,14 +27,16 @@ import ( var _ adapter.RuleSet = (*LocalRuleSet)(nil) type LocalRuleSet struct { - ctx context.Context - logger logger.Logger - tag string - rules []adapter.HeadlessRule - metadata adapter.RuleSetMetadata - fileFormat string - watcher *fswatch.Watcher - refs atomic.Int32 + ctx context.Context + logger logger.Logger + tag string + rules []adapter.HeadlessRule + metadata adapter.RuleSetMetadata + fileFormat string + watcher *fswatch.Watcher + callbackAccess sync.Mutex + callbacks list.List[adapter.RuleSetUpdateCallback] + refs atomic.Int32 } func NewLocalRuleSet(ctx context.Context, logger logger.Logger, options option.RuleSet) (*LocalRuleSet, error) { @@ -52,13 +55,12 @@ func NewLocalRuleSet(ctx context.Context, logger logger.Logger, options option.R return nil, err } } else { - err := ruleSet.reloadFile(filemanager.BasePath(ctx, options.LocalOptions.Path)) + filePath := filemanager.BasePath(ctx, options.LocalOptions.Path) + filePath, _ = filepath.Abs(options.LocalOptions.Path) + err := ruleSet.reloadFile(filePath) if err != nil { return nil, err } - } - if options.Type == C.RuleSetTypeLocal { - filePath, _ := filepath.Abs(options.LocalOptions.Path) watcher, err := fswatch.NewWatcher(fswatch.Options{ Path: []string{filePath}, Callback: func(path string) { @@ -141,6 +143,12 @@ func (s *LocalRuleSet) reloadRules(headlessRules []option.HeadlessRule) error { metadata.ContainsIPCIDRRule = hasHeadlessRule(headlessRules, isIPCIDRHeadlessRule) s.rules = rules s.metadata = metadata + s.callbackAccess.Lock() + callbacks := s.callbacks.Array() + s.callbackAccess.Unlock() + for _, callback := range callbacks { + callback(s) + } return nil } @@ -173,10 +181,15 @@ func (s *LocalRuleSet) Cleanup() { } func (s *LocalRuleSet) RegisterCallback(callback adapter.RuleSetUpdateCallback) *list.Element[adapter.RuleSetUpdateCallback] { - return nil + s.callbackAccess.Lock() + defer s.callbackAccess.Unlock() + return s.callbacks.PushBack(callback) } func (s *LocalRuleSet) UnregisterCallback(element *list.Element[adapter.RuleSetUpdateCallback]) { + s.callbackAccess.Lock() + defer s.callbackAccess.Unlock() + s.callbacks.Remove(element) } func (s *LocalRuleSet) Close() error { diff --git a/route/rule/rule_set_remote.go b/route/rule/rule_set_remote.go index 830e19f7..0c5d9bb3 100644 --- a/route/rule/rule_set_remote.go +++ b/route/rule/rule_set_remote.go @@ -33,23 +33,23 @@ import ( var _ adapter.RuleSet = (*RemoteRuleSet)(nil) type RemoteRuleSet struct { - ctx context.Context - cancel context.CancelFunc - outboundManager adapter.OutboundManager - logger logger.ContextLogger - options option.RuleSet - metadata adapter.RuleSetMetadata - updateInterval time.Duration - dialer N.Dialer - rules []adapter.HeadlessRule - lastUpdated time.Time - lastEtag string - updateTicker *time.Ticker - cacheFile adapter.CacheFile - pauseManager pause.Manager - callbackAccess sync.Mutex - callbacks list.List[adapter.RuleSetUpdateCallback] - refs atomic.Int32 + ctx context.Context + cancel context.CancelFunc + logger logger.ContextLogger + outbound adapter.OutboundManager + options option.RuleSet + metadata adapter.RuleSetMetadata + updateInterval time.Duration + dialer N.Dialer + rules []adapter.HeadlessRule + lastUpdated time.Time + lastEtag string + updateTicker *time.Ticker + cacheFile adapter.CacheFile + pauseManager pause.Manager + callbackAccess sync.Mutex + callbacks list.List[adapter.RuleSetUpdateCallback] + refs atomic.Int32 } func NewRemoteRuleSet(ctx context.Context, logger logger.ContextLogger, options option.RuleSet) *RemoteRuleSet { @@ -61,13 +61,13 @@ func NewRemoteRuleSet(ctx context.Context, logger logger.ContextLogger, options updateInterval = 24 * time.Hour } return &RemoteRuleSet{ - ctx: ctx, - cancel: cancel, - outboundManager: service.FromContext[adapter.OutboundManager](ctx), - logger: logger, - options: options, - updateInterval: updateInterval, - pauseManager: service.FromContext[pause.Manager](ctx), + ctx: ctx, + cancel: cancel, + outbound: service.FromContext[adapter.OutboundManager](ctx), + logger: logger, + options: options, + updateInterval: updateInterval, + pauseManager: service.FromContext[pause.Manager](ctx), } } @@ -83,13 +83,13 @@ func (s *RemoteRuleSet) StartContext(ctx context.Context, startContext *adapter. s.cacheFile = service.FromContext[adapter.CacheFile](s.ctx) var dialer N.Dialer if s.options.RemoteOptions.DownloadDetour != "" { - outbound, loaded := s.outboundManager.Outbound(s.options.RemoteOptions.DownloadDetour) + outbound, loaded := s.outbound.Outbound(s.options.RemoteOptions.DownloadDetour) if !loaded { - return E.New("download_detour not found: ", s.options.RemoteOptions.DownloadDetour) + return E.New("download detour not found: ", s.options.RemoteOptions.DownloadDetour) } dialer = outbound } else { - dialer = s.outboundManager.Default() + dialer = s.outbound.Default() } s.dialer = dialer if s.cacheFile != nil { @@ -286,7 +286,7 @@ func (s *RemoteRuleSet) fetchOnce(ctx context.Context, startContext *adapter.HTT } s.lastUpdated = time.Now() if s.cacheFile != nil { - err = s.cacheFile.SaveRuleSet(s.options.Tag, &adapter.SavedRuleSet{ + err = s.cacheFile.SaveRuleSet(s.options.Tag, &adapter.SavedBinary{ LastUpdated: s.lastUpdated, Content: content, LastEtag: s.lastEtag,