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 }