github.com/yaling888/clash@v1.53.0/adapter/provider/fetcher.go (about)

     1  package provider
     2  
     3  import (
     4  	"bytes"
     5  	"crypto/md5"
     6  	"os"
     7  	"path/filepath"
     8  	"time"
     9  
    10  	"github.com/phuslu/log"
    11  	"github.com/samber/lo"
    12  
    13  	types "github.com/yaling888/clash/constant/provider"
    14  )
    15  
    16  var (
    17  	fileMode os.FileMode = 0o666
    18  	dirMode  os.FileMode = 0o755
    19  )
    20  
    21  type parser[V any] func([]byte) (V, error)
    22  
    23  type fetcher[V any] struct {
    24  	name      string
    25  	vehicle   types.Vehicle
    26  	interval  time.Duration
    27  	updatedAt *time.Time
    28  	ticker    *time.Ticker
    29  	tmUpdate  *time.Timer
    30  	done      chan struct{}
    31  	hash      [16]byte
    32  	parser    parser[V]
    33  	onUpdate  func(V)
    34  }
    35  
    36  func (f *fetcher[V]) Name() string {
    37  	return f.name
    38  }
    39  
    40  func (f *fetcher[V]) VehicleType() types.VehicleType {
    41  	return f.vehicle.Type()
    42  }
    43  
    44  func (f *fetcher[V]) Initial() (V, error) {
    45  	var (
    46  		buf               []byte
    47  		err               error
    48  		elm               V
    49  		isLocal           bool
    50  		immediatelyUpdate bool
    51  	)
    52  	if stat, fErr := os.Stat(f.vehicle.Path()); fErr == nil {
    53  		buf, err = os.ReadFile(f.vehicle.Path())
    54  		modTime := stat.ModTime()
    55  		f.updatedAt = &modTime
    56  		isLocal = true
    57  		immediatelyUpdate = f.interval != 0 && time.Since(modTime) > f.interval
    58  	} else {
    59  		// delay fetch for using proxy
    60  		if f.vehicle.Proxy() {
    61  			immediatelyUpdate = true
    62  			goto end
    63  		}
    64  		buf, err = f.vehicle.Read()
    65  	}
    66  
    67  	if err != nil {
    68  		return lo.Empty[V](), err
    69  	}
    70  
    71  	elm, err = f.parser(buf)
    72  	if err != nil {
    73  		if !isLocal {
    74  			return lo.Empty[V](), err
    75  		}
    76  
    77  		// delay fetch for using proxy
    78  		if f.vehicle.Proxy() {
    79  			immediatelyUpdate = true
    80  			goto end
    81  		}
    82  
    83  		// parse local file error, fallback to remote
    84  		buf, err = f.vehicle.Read()
    85  		if err != nil {
    86  			return lo.Empty[V](), err
    87  		}
    88  
    89  		elm, err = f.parser(buf)
    90  		if err != nil {
    91  			return lo.Empty[V](), err
    92  		}
    93  
    94  		isLocal = false
    95  	}
    96  
    97  	if f.vehicle.Type() != types.File && !isLocal {
    98  		if err := safeWrite(f.vehicle.Path(), buf); err != nil {
    99  			return lo.Empty[V](), err
   100  		}
   101  	}
   102  
   103  	f.hash = md5.Sum(buf)
   104  
   105  end:
   106  	// pull element automatically
   107  	if f.vehicle.Type() != types.File {
   108  		go f.pullLoop(immediatelyUpdate)
   109  	}
   110  
   111  	return elm, nil
   112  }
   113  
   114  func (f *fetcher[V]) Update() (V, bool, error) {
   115  	buf, err := f.vehicle.Read()
   116  	if err != nil {
   117  		return lo.Empty[V](), false, err
   118  	}
   119  
   120  	now := time.Now()
   121  	hash := md5.Sum(buf)
   122  	if bytes.Equal(f.hash[:], hash[:]) {
   123  		f.updatedAt = &now
   124  		_ = os.Chtimes(f.vehicle.Path(), now, now)
   125  		return lo.Empty[V](), true, nil
   126  	}
   127  
   128  	proxies, err := f.parser(buf)
   129  	if err != nil {
   130  		return lo.Empty[V](), false, err
   131  	}
   132  
   133  	if f.vehicle.Type() != types.File {
   134  		if err := safeWrite(f.vehicle.Path(), buf); err != nil {
   135  			return lo.Empty[V](), false, err
   136  		}
   137  	}
   138  
   139  	f.updatedAt = &now
   140  	f.hash = hash
   141  
   142  	return proxies, false, nil
   143  }
   144  
   145  func (f *fetcher[V]) Destroy() error {
   146  	if f.tmUpdate != nil {
   147  		f.tmUpdate.Stop()
   148  		f.tmUpdate = nil
   149  	}
   150  	if f.ticker != nil {
   151  		select {
   152  		case f.done <- struct{}{}:
   153  		default:
   154  		}
   155  	}
   156  	return nil
   157  }
   158  
   159  func (f *fetcher[V]) pullLoop(immediately bool) {
   160  	update := func() {
   161  		log.Debug().Str("name", f.Name()).Msg("[Provider] proxies updating...")
   162  		elm, same, err := f.Update()
   163  		if err != nil {
   164  			log.Warn().Err(err).Str("name", f.Name()).Msg("[Provider] pull failed")
   165  			return
   166  		}
   167  
   168  		if same {
   169  			log.Debug().Str("name", f.Name()).Msg("[Provider] proxies doesn't change")
   170  			return
   171  		}
   172  
   173  		log.Info().Str("name", f.Name()).Msg("[Provider] proxies updated")
   174  		if f.onUpdate != nil {
   175  			f.onUpdate(elm)
   176  		}
   177  	}
   178  
   179  	if immediately {
   180  		if f.tmUpdate != nil {
   181  			f.tmUpdate.Stop()
   182  		}
   183  		f.tmUpdate = time.AfterFunc(50*time.Second, func() {
   184  			update()
   185  			f.tmUpdate = nil
   186  		})
   187  	}
   188  
   189  	if f.ticker == nil {
   190  		return
   191  	}
   192  
   193  	for {
   194  		select {
   195  		case <-f.ticker.C:
   196  			update()
   197  		case <-f.done:
   198  			f.ticker.Stop()
   199  			return
   200  		}
   201  	}
   202  }
   203  
   204  func safeWrite(path string, buf []byte) error {
   205  	dir := filepath.Dir(path)
   206  
   207  	if _, err := os.Stat(dir); os.IsNotExist(err) {
   208  		if err := os.MkdirAll(dir, dirMode); err != nil {
   209  			return err
   210  		}
   211  	}
   212  
   213  	return os.WriteFile(path, buf, fileMode)
   214  }
   215  
   216  func newFetcher[V any](name string, interval time.Duration, vehicle types.Vehicle, parser parser[V], onUpdate func(V)) *fetcher[V] {
   217  	if interval < 0 {
   218  		interval = 0
   219  	}
   220  	var ticker *time.Ticker
   221  	if interval != 0 {
   222  		ticker = time.NewTicker(interval)
   223  	}
   224  
   225  	return &fetcher[V]{
   226  		name:     name,
   227  		ticker:   ticker,
   228  		vehicle:  vehicle,
   229  		interval: interval,
   230  		parser:   parser,
   231  		done:     make(chan struct{}, 1),
   232  		onUpdate: onUpdate,
   233  	}
   234  }