Skip to content

Commit

Permalink
feat: proxy provider 支持V2ray格式订阅链接
Browse files Browse the repository at this point in the history
  • Loading branch information
MetaCubeX committed Jun 5, 2022
1 parent 2188fad commit 5d337b7
Show file tree
Hide file tree
Showing 2 changed files with 69 additions and 61 deletions.
96 changes: 44 additions & 52 deletions adapter/provider/fetcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,42 +16,35 @@ var (
dirMode os.FileMode = 0o755
)

type parser = func([]byte) (any, error)
type parser[V any] func([]byte) (V, error)

type fetcher struct {
type fetcher[V any] struct {
name string
vehicle types.Vehicle
updatedAt *time.Time
ticker *time.Ticker
done chan struct{}
hash [16]byte
parser parser
parser parser[V]
interval time.Duration
onUpdate func(any)
onUpdate func(V)
}

func (f *fetcher) Name() string {
func (f *fetcher[V]) Name() string {
return f.name
}

func (f *fetcher) VehicleType() types.VehicleType {
func (f *fetcher[V]) VehicleType() types.VehicleType {
return f.vehicle.Type()
}

func (f *fetcher) Initial() (any, error) {
func (f *fetcher[V]) Initial() (V, error) {
var (
buf []byte
err error
isLocal bool
)

defer func() {
// pull proxies automatically
if f.ticker != nil {
go f.pullLoop()
}
}()

if stat, fErr := os.Stat(f.vehicle.Path()); fErr == nil {
buf, err = os.ReadFile(f.vehicle.Path())
modTime := stat.ModTime()
Expand All @@ -60,70 +53,75 @@ func (f *fetcher) Initial() (any, error) {
if f.interval != 0 && modTime.Add(f.interval).Before(time.Now()) {
defer func() {
log.Infoln("[Provider] %s's proxies not updated for a long time, force refresh", f.Name())
go f.update()
go f.Update()
}()
}
} else {
buf, err = f.vehicle.Read()
}

if err != nil {
return nil, err
return getZero[V](), err
}

proxies, err := f.parser(buf)
if err != nil {
if !isLocal {
return nil, err
return getZero[V](), err
}

// parse local file error, fallback to remote
buf, err = f.vehicle.Read()
if err != nil {
return nil, err
return getZero[V](), err
}

proxies, err = f.parser(buf)
if err != nil {
return nil, err
return getZero[V](), err
}

isLocal = false
}

if f.vehicle.Type() != types.File && !isLocal {
if err := safeWrite(f.vehicle.Path(), buf); err != nil {
return nil, err
return getZero[V](), err
}
}

f.hash = md5.Sum(buf)

// pull proxies automatically
if f.ticker != nil {
go f.pullLoop()
}

return proxies, nil
}

func (f *fetcher) Update() (any, bool, error) {
func (f *fetcher[V]) Update() (V, bool, error) {
buf, err := f.vehicle.Read()
if err != nil {
return nil, false, err
return getZero[V](), false, err
}

now := time.Now()
hash := md5.Sum(buf)
if bytes.Equal(f.hash[:], hash[:]) {
f.updatedAt = &now
os.Chtimes(f.vehicle.Path(), now, now)
return nil, true, nil
return getZero[V](), true, nil
}

proxies, err := f.parser(buf)
if err != nil {
return nil, false, err
return getZero[V](), false, err
}

if f.vehicle.Type() != types.File {
if err := safeWrite(f.vehicle.Path(), buf); err != nil {
return nil, false, err
return getZero[V](), false, err
}
}

Expand All @@ -133,49 +131,39 @@ func (f *fetcher) Update() (any, bool, error) {
return proxies, false, nil
}

func (f *fetcher) Destroy() error {
func (f *fetcher[V]) Destroy() error {
if f.ticker != nil {
f.done <- struct{}{}
}
return nil
}

func (f *fetcher) pullLoop() {
func (f *fetcher[V]) pullLoop() {
for {
select {
case <-f.ticker.C:
same, err := f.update()
if same || err != nil {
elm, same, err := f.Update()
if err != nil {
log.Warnln("[Provider] %s pull error: %s", f.Name(), err.Error())
continue
}

if same {
log.Debugln("[Provider] %s's proxies doesn't change", f.Name())
continue
}

log.Infoln("[Provider] %s's proxies update", f.Name())
if f.onUpdate != nil {
f.onUpdate(elm)
}
case <-f.done:
f.ticker.Stop()
return
}
}
}

func (f *fetcher) update() (same bool, err error) {
elm, same, err := f.Update()
if err != nil {
log.Warnln("[Provider] %s pull error: %s", f.Name(), err.Error())
return
}

if same {
log.Debugln("[Provider] %s's proxies doesn't change", f.Name())
return
}

if f.onUpdate != nil {
f.onUpdate(elm)
}

log.Infoln("[Provider] %s's proxies update", f.Name())
return
}

func safeWrite(path string, buf []byte) error {
dir := filepath.Dir(path)

Expand All @@ -188,19 +176,23 @@ func safeWrite(path string, buf []byte) error {
return os.WriteFile(path, buf, fileMode)
}

func newFetcher(name string, interval time.Duration, vehicle types.Vehicle, parser parser, onUpdate func(any)) *fetcher {
func newFetcher[V any](name string, interval time.Duration, vehicle types.Vehicle, parser parser[V], onUpdate func(V)) *fetcher[V] {
var ticker *time.Ticker
if interval != 0 {
ticker = time.NewTicker(interval)
}

return &fetcher{
return &fetcher[V]{
name: name,
ticker: ticker,
vehicle: vehicle,
parser: parser,
done: make(chan struct{}, 1),
onUpdate: onUpdate,
interval: interval,
}
}

func getZero[V any]() V {
var result V
return result
}
34 changes: 25 additions & 9 deletions adapter/provider/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@ import (
"encoding/json"
"errors"
"fmt"
"github.com/Dreamacro/clash/common/convert"
"github.com/dlclark/regexp2"
"math"
"runtime"
"time"

"github.com/Dreamacro/clash/adapter"
"github.com/Dreamacro/clash/common/convert"
C "github.com/Dreamacro/clash/constant"
types "github.com/Dreamacro/clash/constant/provider"

Expand All @@ -36,10 +37,6 @@ type proxySetProvider struct {
version uint
}

func (pp *proxySetProvider) Version() uint {
return pp.version
}

func (pp *proxySetProvider) MarshalJSON() ([]byte, error) {
return json.Marshal(map[string]any{
"name": pp.Name(),
Expand All @@ -50,6 +47,10 @@ func (pp *proxySetProvider) MarshalJSON() ([]byte, error) {
})
}

func (pp *proxySetProvider) Version() uint {
return pp.version
}

func (pp *proxySetProvider) Name() string {
return pp.name
}
Expand All @@ -71,7 +72,6 @@ func (pp *proxySetProvider) Initial() error {
if err != nil {
return err
}

pp.onUpdate(elm)
return nil
}
Expand All @@ -93,7 +93,7 @@ func (pp *proxySetProvider) setProxies(proxies []C.Proxy) {
pp.proxies = proxies
pp.healthCheck.setProxy(proxies)
if pp.healthCheck.auto() {
go pp.healthCheck.check()
defer func() { go pp.healthCheck.check() }()
}
}

Expand Down Expand Up @@ -134,6 +134,7 @@ type compatibleProvider struct {
name string
healthCheck *HealthCheck
proxies []C.Proxy
version uint
}

func (cp *compatibleProvider) MarshalJSON() ([]byte, error) {
Expand All @@ -145,6 +146,10 @@ func (cp *compatibleProvider) MarshalJSON() ([]byte, error) {
})
}

func (cp *compatibleProvider) Version() uint {
return cp.version
}

func (cp *compatibleProvider) Name() string {
return cp.name
}
Expand Down Expand Up @@ -205,6 +210,11 @@ func NewCompatibleProvider(name string, proxies []C.Proxy, hc *HealthCheck) (*Co
func proxiesOnUpdate(pd *proxySetProvider) func([]C.Proxy) {
return func(elm []C.Proxy) {
pd.setProxies(elm)
if pd.version == math.MaxUint {
pd.version = 0
} else {
pd.version++
}
}
}

Expand All @@ -213,7 +223,11 @@ func proxiesParseAndFilter(filter string, filterReg *regexp2.Regexp) parser[[]C.
schema := &ProxySchema{}

if err := yaml.Unmarshal(buf, schema); err != nil {
return nil, err
proxies, err1 := convert.ConvertsV2Ray(buf)
if err1 != nil {
return nil, fmt.Errorf("%s, %w", err.Error(), err1)
}
schema.Proxies = proxies
}

if schema.Proxies == nil {
Expand All @@ -222,7 +236,9 @@ func proxiesParseAndFilter(filter string, filterReg *regexp2.Regexp) parser[[]C.

proxies := []C.Proxy{}
for idx, mapping := range schema.Proxies {
if name, ok := mapping["name"]; ok && len(filter) > 0 && !filterReg.MatchString(name.(string)) {
name, ok := mapping["name"]
mat, _ := filterReg.FindStringMatch(name.(string))
if ok && len(filter) > 0 && mat == nil {
continue
}
proxy, err := adapter.ParseProxy(mapping)
Expand Down

0 comments on commit 5d337b7

Please sign in to comment.