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