diff --git a/adapter/outboundgroup/fallback.go b/adapter/outboundgroup/fallback.go index 1a2f88fe..37c38e7e 100644 --- a/adapter/outboundgroup/fallback.go +++ b/adapter/outboundgroup/fallback.go @@ -8,18 +8,14 @@ import ( "time" "github.com/Dreamacro/clash/adapter/outbound" - "github.com/Dreamacro/clash/common/singledo" "github.com/Dreamacro/clash/component/dialer" C "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/clash/constant/provider" ) type Fallback struct { - *outbound.Base + *GroupBase disableUDP bool - filter string - single *singledo.Single[[]C.Proxy] - providers []provider.ProxyProvider failedTimes *atomic.Int32 failedTime *atomic.Int64 } @@ -98,7 +94,7 @@ func (f *Fallback) SupportUDP() bool { // MarshalJSON implements C.ProxyAdapter func (f *Fallback) MarshalJSON() ([]byte, error) { all := []string{} - for _, proxy := range f.proxies(false) { + for _, proxy := range f.GetProxies(false) { all = append(all, proxy.Name()) } return json.Marshal(map[string]any{ @@ -114,16 +110,8 @@ func (f *Fallback) Unwrap(metadata *C.Metadata) C.Proxy { return proxy } -func (f *Fallback) proxies(touch bool) []C.Proxy { - elm, _, _ := f.single.Do(func() ([]C.Proxy, error) { - return getProvidersProxies(f.providers, touch, f.filter), nil - }) - - return elm -} - func (f *Fallback) findAliveProxy(touch bool) C.Proxy { - proxies := f.proxies(touch) + proxies := f.GetProxies(touch) for _, proxy := range proxies { if proxy.Alive() { return proxy @@ -135,16 +123,17 @@ func (f *Fallback) findAliveProxy(touch bool) C.Proxy { func NewFallback(option *GroupCommonOption, providers []provider.ProxyProvider) *Fallback { return &Fallback{ - Base: outbound.NewBase(outbound.BaseOption{ - Name: option.Name, - Type: C.Fallback, - Interface: option.Interface, - RoutingMark: option.RoutingMark, + GroupBase: NewGroupBase(GroupBaseOption{ + outbound.BaseOption{ + Name: option.Name, + Type: C.Fallback, + Interface: option.Interface, + RoutingMark: option.RoutingMark, + }, + option.Filter, + providers, }), - single: singledo.NewSingle[[]C.Proxy](defaultGetProxiesDuration), - providers: providers, disableUDP: option.DisableUDP, - filter: option.Filter, failedTimes: atomic.NewInt32(-1), failedTime: atomic.NewInt64(-1), } diff --git a/adapter/outboundgroup/groupbase.go b/adapter/outboundgroup/groupbase.go new file mode 100644 index 00000000..8fd4c775 --- /dev/null +++ b/adapter/outboundgroup/groupbase.go @@ -0,0 +1,99 @@ +package outboundgroup + +import ( + "github.com/Dreamacro/clash/adapter/outbound" + C "github.com/Dreamacro/clash/constant" + "github.com/Dreamacro/clash/constant/provider" + types "github.com/Dreamacro/clash/constant/provider" + "github.com/Dreamacro/clash/tunnel" + "github.com/dlclark/regexp2" +) + +type GroupBase struct { + *outbound.Base + filter *regexp2.Regexp + providers []provider.ProxyProvider + flags map[string]uint + proxies map[string][]C.Proxy +} + +type GroupBaseOption struct { + outbound.BaseOption + filter string + providers []provider.ProxyProvider +} + +func NewGroupBase(opt GroupBaseOption) *GroupBase { + var filter *regexp2.Regexp = nil + if opt.filter != "" { + filter = regexp2.MustCompile(opt.filter, 0) + } + return &GroupBase{ + Base: outbound.NewBase(opt.BaseOption), + filter: filter, + providers: opt.providers, + flags: map[string]uint{}, + proxies: map[string][]C.Proxy{}, + } +} + +func (gb *GroupBase) GetProxies(touch bool) []C.Proxy { + if gb.filter == nil { + var proxies []C.Proxy + for _, pd := range gb.providers { + if touch { + proxies = append(proxies, pd.ProxiesWithTouch()...) + } else { + proxies = append(proxies, pd.Proxies()...) + } + } + if len(proxies) == 0 { + return append(proxies, tunnel.Proxies()["COMPATIBLE"]) + } + return proxies + } + //TODO("Touch Flag 没变的") + for _, pd := range gb.providers { + vt := pd.VehicleType() + if vt == types.Compatible { + if touch { + gb.proxies[pd.Name()] = pd.ProxiesWithTouch() + } else { + gb.proxies[pd.Name()] = pd.Proxies() + } + + gb.flags[pd.Name()] = pd.Flag() + continue + } + + if flag, ok := gb.flags[pd.Name()]; !ok || flag != pd.Flag() { + var ( + proxies []C.Proxy + newProxies []C.Proxy + ) + + if touch { + proxies = pd.ProxiesWithTouch() + } else { + proxies = pd.Proxies() + } + + for _, p := range proxies { + if mat, _ := gb.filter.FindStringMatch(p.Name()); mat != nil { + newProxies = append(newProxies, p) + } + } + + gb.proxies[pd.Name()] = newProxies + gb.flags[pd.Name()] = pd.Flag() + } + } + var proxies []C.Proxy + for _, v := range gb.proxies { + proxies = append(proxies, v...) + } + if len(proxies) == 0 { + return append(proxies, tunnel.Proxies()["COMPATIBLE"]) + } + return proxies +} diff --git a/adapter/outboundgroup/loadbalance.go b/adapter/outboundgroup/loadbalance.go index aa22240f..5e952390 100644 --- a/adapter/outboundgroup/loadbalance.go +++ b/adapter/outboundgroup/loadbalance.go @@ -9,7 +9,6 @@ import ( "github.com/Dreamacro/clash/adapter/outbound" "github.com/Dreamacro/clash/common/murmur3" - "github.com/Dreamacro/clash/common/singledo" "github.com/Dreamacro/clash/component/dialer" C "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/clash/constant/provider" @@ -20,11 +19,8 @@ import ( type strategyFn = func(proxies []C.Proxy, metadata *C.Metadata) C.Proxy type LoadBalance struct { - *outbound.Base + *GroupBase disableUDP bool - filter string - single *singledo.Single[[]C.Proxy] - providers []provider.ProxyProvider strategyFn strategyFn } @@ -136,22 +132,14 @@ func strategyConsistentHashing() strategyFn { // Unwrap implements C.ProxyAdapter func (lb *LoadBalance) Unwrap(metadata *C.Metadata) C.Proxy { - proxies := lb.proxies(true) + proxies := lb.GetProxies(true) return lb.strategyFn(proxies, metadata) } -func (lb *LoadBalance) proxies(touch bool) []C.Proxy { - elm, _, _ := lb.single.Do(func() ([]C.Proxy, error) { - return getProvidersProxies(lb.providers, touch, lb.filter), nil - }) - - return elm -} - // MarshalJSON implements C.ProxyAdapter func (lb *LoadBalance) MarshalJSON() ([]byte, error) { all := []string{} - for _, proxy := range lb.proxies(false) { + for _, proxy := range lb.GetProxies(false) { all = append(all, proxy.Name()) } return json.Marshal(map[string]any{ @@ -171,16 +159,17 @@ func NewLoadBalance(option *GroupCommonOption, providers []provider.ProxyProvide return nil, fmt.Errorf("%w: %s", errStrategy, strategy) } return &LoadBalance{ - Base: outbound.NewBase(outbound.BaseOption{ - Name: option.Name, - Type: C.LoadBalance, - Interface: option.Interface, - RoutingMark: option.RoutingMark, + GroupBase: NewGroupBase(GroupBaseOption{ + outbound.BaseOption{ + Name: option.Name, + Type: C.LoadBalance, + Interface: option.Interface, + RoutingMark: option.RoutingMark, + }, + option.Filter, + providers, }), - single: singledo.NewSingle[[]C.Proxy](defaultGetProxiesDuration), - providers: providers, strategyFn: strategyFn, disableUDP: option.DisableUDP, - filter: option.Filter, }, nil } diff --git a/adapter/outboundgroup/relay.go b/adapter/outboundgroup/relay.go index ffccd9a3..14f5a546 100644 --- a/adapter/outboundgroup/relay.go +++ b/adapter/outboundgroup/relay.go @@ -6,17 +6,13 @@ import ( "fmt" "github.com/Dreamacro/clash/adapter/outbound" - "github.com/Dreamacro/clash/common/singledo" "github.com/Dreamacro/clash/component/dialer" C "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/clash/constant/provider" ) type Relay struct { - *outbound.Base - single *singledo.Single[[]C.Proxy] - providers []provider.ProxyProvider - filter string + *GroupBase } // DialContext implements C.ProxyAdapter @@ -70,7 +66,7 @@ func (r *Relay) DialContext(ctx context.Context, metadata *C.Metadata, opts ...d // MarshalJSON implements C.ProxyAdapter func (r *Relay) MarshalJSON() ([]byte, error) { all := []string{} - for _, proxy := range r.rawProxies(false) { + for _, proxy := range r.GetProxies(false) { all = append(all, proxy.Name()) } return json.Marshal(map[string]any{ @@ -79,16 +75,8 @@ func (r *Relay) MarshalJSON() ([]byte, error) { }) } -func (r *Relay) rawProxies(touch bool) []C.Proxy { - elm, _, _ := r.single.Do(func() ([]C.Proxy, error) { - return getProvidersProxies(r.providers, touch, r.filter), nil - }) - - return elm -} - func (r *Relay) proxies(metadata *C.Metadata, touch bool) []C.Proxy { - proxies := r.rawProxies(touch) + proxies := r.GetProxies(touch) for n, proxy := range proxies { subproxy := proxy.Unwrap(metadata) @@ -103,14 +91,15 @@ func (r *Relay) proxies(metadata *C.Metadata, touch bool) []C.Proxy { func NewRelay(option *GroupCommonOption, providers []provider.ProxyProvider) *Relay { return &Relay{ - Base: outbound.NewBase(outbound.BaseOption{ - Name: option.Name, - Type: C.Relay, - Interface: option.Interface, - RoutingMark: option.RoutingMark, + GroupBase: NewGroupBase(GroupBaseOption{ + outbound.BaseOption{ + Name: option.Name, + Type: C.Relay, + Interface: option.Interface, + RoutingMark: option.RoutingMark, + }, + "", + providers, }), - single: singledo.NewSingle[[]C.Proxy](defaultGetProxiesDuration), - providers: providers, - filter: option.Filter, } } diff --git a/adapter/outboundgroup/selector.go b/adapter/outboundgroup/selector.go index 97447f4d..dd36eeb7 100644 --- a/adapter/outboundgroup/selector.go +++ b/adapter/outboundgroup/selector.go @@ -6,19 +6,15 @@ import ( "errors" "github.com/Dreamacro/clash/adapter/outbound" - "github.com/Dreamacro/clash/common/singledo" "github.com/Dreamacro/clash/component/dialer" C "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/clash/constant/provider" ) type Selector struct { - *outbound.Base + *GroupBase disableUDP bool - single *singledo.Single[C.Proxy] selected string - filter string - providers []provider.ProxyProvider } // DialContext implements C.ProxyAdapter @@ -51,7 +47,7 @@ func (s *Selector) SupportUDP() bool { // MarshalJSON implements C.ProxyAdapter func (s *Selector) MarshalJSON() ([]byte, error) { all := []string{} - for _, proxy := range getProvidersProxies(s.providers, false, s.filter) { + for _, proxy := range s.GetProxies(false) { all = append(all, proxy.Name()) } @@ -67,10 +63,9 @@ func (s *Selector) Now() string { } func (s *Selector) Set(name string) error { - for _, proxy := range getProvidersProxies(s.providers, false, s.filter) { + for _, proxy := range s.GetProxies(false) { if proxy.Name() == name { s.selected = name - s.single.Reset() return nil } } @@ -84,32 +79,29 @@ func (s *Selector) Unwrap(*C.Metadata) C.Proxy { } func (s *Selector) selectedProxy(touch bool) C.Proxy { - elm, _, _ := s.single.Do(func() (C.Proxy, error) { - proxies := getProvidersProxies(s.providers, touch, s.filter) - for _, proxy := range proxies { - if proxy.Name() == s.selected { - return proxy, nil - } + proxies := s.GetProxies(touch) + for _, proxy := range proxies { + if proxy.Name() == s.selected { + return proxy } + } - return proxies[0], nil - }) - - return elm + return proxies[0] } func NewSelector(option *GroupCommonOption, providers []provider.ProxyProvider) *Selector { return &Selector{ - Base: outbound.NewBase(outbound.BaseOption{ - Name: option.Name, - Type: C.Selector, - Interface: option.Interface, - RoutingMark: option.RoutingMark, + GroupBase: NewGroupBase(GroupBaseOption{ + outbound.BaseOption{ + Name: option.Name, + Type: C.Selector, + Interface: option.Interface, + RoutingMark: option.RoutingMark, + }, + option.Filter, + providers, }), - single: singledo.NewSingle[C.Proxy](defaultGetProxiesDuration), - providers: providers, selected: "COMPATIBLE", disableUDP: option.DisableUDP, - filter: option.Filter, } } diff --git a/adapter/outboundgroup/urltest.go b/adapter/outboundgroup/urltest.go index 11b4a5ce..f0d181b8 100644 --- a/adapter/outboundgroup/urltest.go +++ b/adapter/outboundgroup/urltest.go @@ -23,14 +23,11 @@ func urlTestWithTolerance(tolerance uint16) urlTestOption { } type URLTest struct { - *outbound.Base + *GroupBase tolerance uint16 disableUDP bool fastNode C.Proxy - filter string - single *singledo.Single[[]C.Proxy] fastSingle *singledo.Single[C.Proxy] - providers []provider.ProxyProvider failedTimes *atomic.Int32 failedTime *atomic.Int64 } @@ -70,17 +67,9 @@ func (u *URLTest) Unwrap(*C.Metadata) C.Proxy { return u.fast(true) } -func (u *URLTest) proxies(touch bool) []C.Proxy { - elm, _, _ := u.single.Do(func() ([]C.Proxy, error) { - return getProvidersProxies(u.providers, touch, u.filter), nil - }) - - return elm -} - func (u *URLTest) fast(touch bool) C.Proxy { elm, _, _ := u.fastSingle.Do(func() (C.Proxy, error) { - proxies := u.proxies(touch) + proxies := u.GetProxies(touch) fast := proxies[0] min := fast.LastDelay() fastNotExist := true @@ -124,7 +113,7 @@ func (u *URLTest) SupportUDP() bool { // MarshalJSON implements C.ProxyAdapter func (u *URLTest) MarshalJSON() ([]byte, error) { all := []string{} - for _, proxy := range u.proxies(false) { + for _, proxy := range u.GetProxies(false) { all = append(all, proxy.Name()) } return json.Marshal(map[string]any{ @@ -175,17 +164,18 @@ func parseURLTestOption(config map[string]any) []urlTestOption { func NewURLTest(option *GroupCommonOption, providers []provider.ProxyProvider, options ...urlTestOption) *URLTest { urlTest := &URLTest{ - Base: outbound.NewBase(outbound.BaseOption{ - Name: option.Name, - Type: C.URLTest, - Interface: option.Interface, - RoutingMark: option.RoutingMark, + GroupBase: NewGroupBase(GroupBaseOption{ + outbound.BaseOption{ + Name: option.Name, + Type: C.URLTest, + Interface: option.Interface, + RoutingMark: option.RoutingMark, + }, + option.Filter, + providers, }), - single: singledo.NewSingle[[]C.Proxy](defaultGetProxiesDuration), fastSingle: singledo.NewSingle[C.Proxy](time.Second * 10), - providers: providers, disableUDP: option.DisableUDP, - filter: option.Filter, failedTimes: atomic.NewInt32(-1), failedTime: atomic.NewInt64(-1), } diff --git a/adapter/provider/provider.go b/adapter/provider/provider.go index e8dbe9fb..be0ce4b1 100644 --- a/adapter/provider/provider.go +++ b/adapter/provider/provider.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "github.com/dlclark/regexp2" + "math" "runtime" "time" @@ -32,6 +33,11 @@ type proxySetProvider struct { *fetcher proxies []C.Proxy healthCheck *HealthCheck + flag uint +} + +func (pp *proxySetProvider) Flag() uint { + return pp.flag } func (pp *proxySetProvider) MarshalJSON() ([]byte, error) { @@ -117,6 +123,11 @@ func NewProxySetProvider(name string, interval time.Duration, filter string, veh onUpdate := func(elm any) { ret := elm.([]C.Proxy) pd.setProxies(ret) + if pd.flag == math.MaxUint { + pd.flag = 0 + } else { + pd.flag++ + } } proxiesParseAndFilter := func(buf []byte) (any, error) { @@ -171,6 +182,11 @@ type compatibleProvider struct { name string healthCheck *HealthCheck proxies []C.Proxy + flag uint +} + +func (cp *compatibleProvider) Flag() uint { + return cp.flag } func (cp *compatibleProvider) MarshalJSON() ([]byte, error) { diff --git a/constant/provider/interface.go b/constant/provider/interface.go index 53bda7ea..97c8318a 100644 --- a/constant/provider/interface.go +++ b/constant/provider/interface.go @@ -70,6 +70,7 @@ type ProxyProvider interface { // Commonly used in DialContext and DialPacketConn ProxiesWithTouch() []constant.Proxy HealthCheck() + Flag() uint } // Rule Type