github.com/chwjbn/xclash@v0.2.0/adapter/provider/provider.go (about) 1 package provider 2 3 import ( 4 "encoding/json" 5 "errors" 6 "fmt" 7 "regexp" 8 "runtime" 9 "time" 10 11 "github.com/chwjbn/xclash/adapter" 12 C "github.com/chwjbn/xclash/constant" 13 types "github.com/chwjbn/xclash/constant/provider" 14 15 "gopkg.in/yaml.v2" 16 ) 17 18 const ( 19 ReservedName = "default" 20 ) 21 22 type ProxySchema struct { 23 Proxies []map[string]interface{} `yaml:"proxies"` 24 } 25 26 // for auto gc 27 type ProxySetProvider struct { 28 *proxySetProvider 29 } 30 31 type proxySetProvider struct { 32 *fetcher 33 proxies []C.Proxy 34 healthCheck *HealthCheck 35 } 36 37 func (pp *proxySetProvider) MarshalJSON() ([]byte, error) { 38 return json.Marshal(map[string]interface{}{ 39 "name": pp.Name(), 40 "type": pp.Type().String(), 41 "vehicleType": pp.VehicleType().String(), 42 "proxies": pp.Proxies(), 43 "updatedAt": pp.updatedAt, 44 }) 45 } 46 47 func (pp *proxySetProvider) Name() string { 48 return pp.name 49 } 50 51 func (pp *proxySetProvider) HealthCheck() { 52 pp.healthCheck.check() 53 } 54 55 func (pp *proxySetProvider) Update() error { 56 elm, same, err := pp.fetcher.Update() 57 if err == nil && !same { 58 pp.onUpdate(elm) 59 } 60 return err 61 } 62 63 func (pp *proxySetProvider) Initial() error { 64 elm, err := pp.fetcher.Initial() 65 if err != nil { 66 return err 67 } 68 69 pp.onUpdate(elm) 70 return nil 71 } 72 73 func (pp *proxySetProvider) Type() types.ProviderType { 74 return types.Proxy 75 } 76 77 func (pp *proxySetProvider) Proxies() []C.Proxy { 78 return pp.proxies 79 } 80 81 func (pp *proxySetProvider) ProxiesWithTouch() []C.Proxy { 82 pp.healthCheck.touch() 83 return pp.Proxies() 84 } 85 86 func (pp *proxySetProvider) setProxies(proxies []C.Proxy) { 87 pp.proxies = proxies 88 pp.healthCheck.setProxy(proxies) 89 if pp.healthCheck.auto() { 90 go pp.healthCheck.check() 91 } 92 } 93 94 func stopProxyProvider(pd *ProxySetProvider) { 95 pd.healthCheck.close() 96 pd.fetcher.Destroy() 97 } 98 99 func NewProxySetProvider(name string, interval time.Duration, filter string, vehicle types.Vehicle, hc *HealthCheck) (*ProxySetProvider, error) { 100 filterReg, err := regexp.Compile(filter) 101 if err != nil { 102 return nil, fmt.Errorf("invalid filter regex: %w", err) 103 } 104 105 if hc.auto() { 106 go hc.process() 107 } 108 109 pd := &proxySetProvider{ 110 proxies: []C.Proxy{}, 111 healthCheck: hc, 112 } 113 114 onUpdate := func(elm interface{}) { 115 ret := elm.([]C.Proxy) 116 pd.setProxies(ret) 117 } 118 119 proxiesParseAndFilter := func(buf []byte) (interface{}, error) { 120 schema := &ProxySchema{} 121 122 if err := yaml.Unmarshal(buf, schema); err != nil { 123 return nil, err 124 } 125 126 if schema.Proxies == nil { 127 return nil, errors.New("file must have a `proxies` field") 128 } 129 130 proxies := []C.Proxy{} 131 for idx, mapping := range schema.Proxies { 132 if name, ok := mapping["name"]; ok && len(filter) > 0 && !filterReg.MatchString(name.(string)) { 133 continue 134 } 135 proxy, err := adapter.ParseProxy(mapping) 136 if err != nil { 137 return nil, fmt.Errorf("proxy %d error: %w", idx, err) 138 } 139 proxies = append(proxies, proxy) 140 } 141 142 if len(proxies) == 0 { 143 if len(filter) > 0 { 144 return nil, errors.New("doesn't match any proxy, please check your filter") 145 } 146 return nil, errors.New("file doesn't have any proxy") 147 } 148 149 return proxies, nil 150 } 151 152 fetcher := newFetcher(name, interval, vehicle, proxiesParseAndFilter, onUpdate) 153 pd.fetcher = fetcher 154 155 wrapper := &ProxySetProvider{pd} 156 runtime.SetFinalizer(wrapper, stopProxyProvider) 157 return wrapper, nil 158 } 159 160 // for auto gc 161 type CompatibleProvider struct { 162 *compatibleProvider 163 } 164 165 type compatibleProvider struct { 166 name string 167 healthCheck *HealthCheck 168 proxies []C.Proxy 169 } 170 171 func (cp *compatibleProvider) MarshalJSON() ([]byte, error) { 172 return json.Marshal(map[string]interface{}{ 173 "name": cp.Name(), 174 "type": cp.Type().String(), 175 "vehicleType": cp.VehicleType().String(), 176 "proxies": cp.Proxies(), 177 }) 178 } 179 180 func (cp *compatibleProvider) Name() string { 181 return cp.name 182 } 183 184 func (cp *compatibleProvider) HealthCheck() { 185 cp.healthCheck.check() 186 } 187 188 func (cp *compatibleProvider) Update() error { 189 return nil 190 } 191 192 func (cp *compatibleProvider) Initial() error { 193 return nil 194 } 195 196 func (cp *compatibleProvider) VehicleType() types.VehicleType { 197 return types.Compatible 198 } 199 200 func (cp *compatibleProvider) Type() types.ProviderType { 201 return types.Proxy 202 } 203 204 func (cp *compatibleProvider) Proxies() []C.Proxy { 205 return cp.proxies 206 } 207 208 func (cp *compatibleProvider) ProxiesWithTouch() []C.Proxy { 209 cp.healthCheck.touch() 210 return cp.Proxies() 211 } 212 213 func stopCompatibleProvider(pd *CompatibleProvider) { 214 pd.healthCheck.close() 215 } 216 217 func NewCompatibleProvider(name string, proxies []C.Proxy, hc *HealthCheck) (*CompatibleProvider, error) { 218 if len(proxies) == 0 { 219 return nil, errors.New("provider need one proxy at least") 220 } 221 222 if hc.auto() { 223 go hc.process() 224 } 225 226 pd := &compatibleProvider{ 227 name: name, 228 proxies: proxies, 229 healthCheck: hc, 230 } 231 232 wrapper := &CompatibleProvider{pd} 233 runtime.SetFinalizer(wrapper, stopCompatibleProvider) 234 return wrapper, nil 235 }