github.com/livekit/protocol@v1.16.1-0.20240517185851-47e4c6bba773/utils/configobserver.go (about) 1 package utils 2 3 import ( 4 "container/list" 5 "fmt" 6 "os" 7 "sync" 8 9 "github.com/fsnotify/fsnotify" 10 "gopkg.in/yaml.v3" 11 12 "github.com/livekit/protocol/logger" 13 ) 14 15 type ConfigBuilder[T any] interface { 16 New() (*T, error) 17 } 18 19 type ConfigDefaulter[T any] interface { 20 InitDefaults(*T) error 21 } 22 23 type ConfigObserver[T any] struct { 24 builder ConfigBuilder[T] 25 watcher *fsnotify.Watcher 26 mu sync.Mutex 27 cbs list.List 28 } 29 30 func NewConfigObserver[T any](path string, builder ConfigBuilder[T]) (*ConfigObserver[T], *T, error) { 31 c := &ConfigObserver[T]{ 32 builder: builder, 33 } 34 35 config, err := c.load(path) 36 if err != nil { 37 return nil, nil, err 38 } 39 40 if path != "" { 41 c.watcher, err = fsnotify.NewWatcher() 42 if err != nil { 43 return nil, nil, err 44 } 45 if err := c.watcher.Add(path); err != nil { 46 c.watcher.Close() 47 return nil, nil, err 48 } 49 go c.watch() 50 } 51 52 return c, config, nil 53 } 54 55 func (c *ConfigObserver[T]) Close() { 56 if c != nil && c.watcher != nil { 57 c.watcher.Close() 58 } 59 } 60 61 func (c *ConfigObserver[T]) EmitConfigUpdate(conf *T) { 62 c.mu.Lock() 63 defer c.mu.Unlock() 64 for e := c.cbs.Front(); e != nil; e = e.Next() { 65 e.Value.(func(*T))(conf) 66 } 67 } 68 69 func (c *ConfigObserver[T]) Observe(cb func(*T)) func() { 70 if c == nil { 71 return func() {} 72 } 73 c.mu.Lock() 74 e := c.cbs.PushBack(cb) 75 c.mu.Unlock() 76 77 return func() { 78 c.mu.Lock() 79 c.cbs.Remove(e) 80 c.mu.Unlock() 81 } 82 } 83 84 func (c *ConfigObserver[T]) watch() { 85 for { 86 select { 87 case event, ok := <-c.watcher.Events: 88 if !ok { 89 return 90 } 91 if event.Has(fsnotify.Remove) { 92 if err := c.watcher.Add(event.Name); err != nil { 93 logger.Errorw("unable to rewatch config file", err, "file", event.Name) 94 } 95 } 96 if event.Has(fsnotify.Write | fsnotify.Remove) { 97 if err := c.reload(event.Name); err != nil { 98 logger.Errorw("unable to update config file", err, "file", event.Name) 99 } else { 100 logger.Infow("config file has been updated", "file", event.Name) 101 } 102 } 103 case err, ok := <-c.watcher.Errors: 104 if !ok { 105 return 106 } 107 logger.Errorw("config file watcher error", err) 108 } 109 } 110 } 111 112 func (c *ConfigObserver[T]) reload(path string) error { 113 conf, err := c.load(path) 114 if err != nil { 115 return err 116 } 117 118 c.EmitConfigUpdate(conf) 119 return nil 120 } 121 122 func (c *ConfigObserver[T]) load(path string) (*T, error) { 123 conf, err := c.builder.New() 124 if err != nil { 125 return nil, err 126 } 127 128 if path != "" { 129 b, err := os.ReadFile(path) 130 if err != nil { 131 return nil, err 132 } 133 134 if len(b) == 0 { 135 return nil, fmt.Errorf("cannot parse config: file empty") 136 } 137 138 if err := yaml.Unmarshal(b, conf); err != nil { 139 return nil, fmt.Errorf("cannot parse config: %v", err) 140 } 141 } 142 143 if d, ok := c.builder.(ConfigDefaulter[T]); ok { 144 d.InitDefaults(conf) 145 } 146 147 return conf, err 148 }