github.com/livekit/protocol@v1.39.3/utils/configobserver.go (about) 1 // Copyright 2024 LiveKit, Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package utils 16 17 import ( 18 "fmt" 19 "os" 20 21 "github.com/fsnotify/fsnotify" 22 "go.uber.org/atomic" 23 "gopkg.in/yaml.v3" 24 25 "github.com/livekit/protocol/logger" 26 "github.com/livekit/protocol/utils/events" 27 ) 28 29 type ConfigBuilder[T any] interface { 30 New() (*T, error) 31 } 32 33 type ConfigDefaulter[T any] interface { 34 InitDefaults(*T) error 35 } 36 37 type ConfigObserver[T any] struct { 38 builder ConfigBuilder[T] 39 watcher *fsnotify.Watcher 40 observers *events.ObserverList[*T] 41 conf atomic.Pointer[T] 42 } 43 44 func NewConfigObserver[T any](path string, builder ConfigBuilder[T]) (*ConfigObserver[T], *T, error) { 45 c := &ConfigObserver[T]{ 46 builder: builder, 47 observers: events.NewObserverList[*T](events.WithBlocking()), 48 } 49 50 conf, err := c.load(path) 51 if err != nil { 52 return nil, nil, err 53 } 54 55 if path != "" { 56 c.watcher, err = fsnotify.NewWatcher() 57 if err != nil { 58 return nil, nil, err 59 } 60 if err := c.watcher.Add(path); err != nil { 61 c.watcher.Close() 62 return nil, nil, err 63 } 64 go c.watch() 65 } 66 67 return c, conf, nil 68 } 69 70 func (c *ConfigObserver[T]) Close() { 71 if c != nil && c.watcher != nil { 72 c.watcher.Close() 73 } 74 } 75 76 func (c *ConfigObserver[T]) EmitConfigUpdate(conf *T) { 77 c.observers.Emit(conf) 78 } 79 80 func (c *ConfigObserver[T]) Observe(cb func(*T)) func() { 81 if c == nil { 82 return func() {} 83 } 84 return c.observers.On(cb) 85 } 86 87 func (c *ConfigObserver[T]) Load() *T { 88 return c.conf.Load() 89 } 90 91 func (c *ConfigObserver[T]) watch() { 92 for { 93 select { 94 case event, ok := <-c.watcher.Events: 95 if !ok { 96 return 97 } 98 if event.Has(fsnotify.Remove) { 99 if err := c.watcher.Add(event.Name); err != nil { 100 logger.Errorw("unable to rewatch config file", err, "file", event.Name) 101 } 102 } 103 if event.Has(fsnotify.Write | fsnotify.Remove) { 104 if err := c.reload(event.Name); err != nil { 105 logger.Errorw("unable to update config file", err, "file", event.Name) 106 } else { 107 logger.Infow("config file has been updated", "file", event.Name) 108 } 109 } 110 case err, ok := <-c.watcher.Errors: 111 if !ok { 112 return 113 } 114 logger.Errorw("config file watcher error", err) 115 } 116 } 117 } 118 119 func (c *ConfigObserver[T]) reload(path string) error { 120 conf, err := c.load(path) 121 if err != nil { 122 return err 123 } 124 125 c.EmitConfigUpdate(conf) 126 return nil 127 } 128 129 func (c *ConfigObserver[T]) load(path string) (*T, error) { 130 conf, err := c.builder.New() 131 if err != nil { 132 return nil, err 133 } 134 135 if path != "" { 136 b, err := os.ReadFile(path) 137 if err != nil { 138 return nil, err 139 } 140 141 if len(b) == 0 { 142 return nil, fmt.Errorf("cannot parse config: file empty") 143 } 144 145 if err := yaml.Unmarshal(b, conf); err != nil { 146 return nil, fmt.Errorf("cannot parse config: %v", err) 147 } 148 } 149 150 if d, ok := c.builder.(ConfigDefaulter[T]); ok { 151 d.InitDefaults(conf) 152 } 153 154 c.conf.Store(conf) 155 156 return conf, err 157 }