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  }