trpc.group/trpc-go/trpc-go@v1.0.3/config/provider.go (about) 1 // 2 // 3 // Tencent is pleased to support the open source community by making tRPC available. 4 // 5 // Copyright (C) 2023 THL A29 Limited, a Tencent company. 6 // All rights reserved. 7 // 8 // If you have downloaded a copy of the tRPC source code from Tencent, 9 // please note that tRPC source code is licensed under the Apache 2.0 License, 10 // A copy of the Apache 2.0 License is included in this file. 11 // 12 // 13 14 package config 15 16 import ( 17 "os" 18 "path/filepath" 19 "sync" 20 21 "github.com/fsnotify/fsnotify" 22 23 "trpc.group/trpc-go/trpc-go/log" 24 ) 25 26 func init() { 27 RegisterProvider(newFileProvider()) 28 } 29 30 func newFileProvider() *FileProvider { 31 fp := &FileProvider{ 32 cb: make(chan ProviderCallback), 33 disabledWatcher: true, 34 cache: make(map[string]string), 35 modTime: make(map[string]int64), 36 } 37 watcher, err := fsnotify.NewWatcher() 38 if err == nil { 39 fp.disabledWatcher = false 40 fp.watcher = watcher 41 go fp.run() 42 return fp 43 } 44 log.Debugf("fsnotify.NewWatcher err: %+v", err) 45 return fp 46 } 47 48 // FileProvider is a config provider which gets config from file system. 49 type FileProvider struct { 50 disabledWatcher bool 51 watcher *fsnotify.Watcher 52 cb chan ProviderCallback 53 cache map[string]string 54 modTime map[string]int64 55 mu sync.RWMutex 56 } 57 58 // Name returns file provider's name. 59 func (*FileProvider) Name() string { 60 return "file" 61 } 62 63 // Read reads the specific path file, returns 64 // it content as bytes. 65 func (fp *FileProvider) Read(path string) ([]byte, error) { 66 if !fp.disabledWatcher { 67 if err := fp.watcher.Add(filepath.Dir(path)); err != nil { 68 return nil, err 69 } 70 fp.mu.Lock() 71 fp.cache[filepath.Clean(path)] = path 72 fp.mu.Unlock() 73 } 74 data, err := os.ReadFile(path) 75 if err != nil { 76 log.Tracef("Failed to read file %v", err) 77 return nil, err 78 } 79 return data, nil 80 } 81 82 // Watch watches config changing. The change will 83 // be handled by callback function. 84 func (fp *FileProvider) Watch(cb ProviderCallback) { 85 if !fp.disabledWatcher { 86 fp.cb <- cb 87 } 88 } 89 90 func (fp *FileProvider) run() { 91 fn := make([]ProviderCallback, 0) 92 for { 93 select { 94 case i := <-fp.cb: 95 fn = append(fn, i) 96 case e := <-fp.watcher.Events: 97 if t, ok := fp.isModified(e); ok { 98 fp.trigger(e, t, fn) 99 } 100 } 101 } 102 } 103 104 func (fp *FileProvider) isModified(e fsnotify.Event) (int64, bool) { 105 if e.Op&fsnotify.Write != fsnotify.Write { 106 return 0, false 107 } 108 fp.mu.RLock() 109 defer fp.mu.RUnlock() 110 if _, ok := fp.cache[filepath.Clean(e.Name)]; !ok { 111 return 0, false 112 } 113 fi, err := os.Stat(e.Name) 114 if err != nil { 115 return 0, false 116 } 117 if fi.ModTime().Unix() > fp.modTime[e.Name] { 118 return fi.ModTime().Unix(), true 119 } 120 return 0, false 121 } 122 123 func (fp *FileProvider) trigger(e fsnotify.Event, t int64, fn []ProviderCallback) { 124 data, err := os.ReadFile(e.Name) 125 if err != nil { 126 return 127 } 128 fp.mu.Lock() 129 path := fp.cache[filepath.Clean(e.Name)] 130 fp.modTime[e.Name] = t 131 fp.mu.Unlock() 132 for _, f := range fn { 133 go f(path, data) 134 } 135 }