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  }