136 lines
3.3 KiB
Go
136 lines
3.3 KiB
Go
package config
|
|
|
|
import (
|
|
"fmt"
|
|
"path/filepath"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/fsnotify/fsnotify"
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
// ConfigWatcher surveille les fichiers de configuration pour changements (T0040)
|
|
type ConfigWatcher struct {
|
|
watcher *fsnotify.Watcher
|
|
reloader *ConfigReloader
|
|
logger *zap.Logger
|
|
stopChan chan struct{}
|
|
stopOnce sync.Once // Ensures stopChan is closed only once
|
|
wg sync.WaitGroup
|
|
debounce time.Duration
|
|
}
|
|
|
|
// NewConfigWatcher crée un nouveau watcher de configuration (T0040)
|
|
func NewConfigWatcher(reloader *ConfigReloader, logger *zap.Logger) (*ConfigWatcher, error) {
|
|
watcher, err := fsnotify.NewWatcher()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to create watcher: %w", err)
|
|
}
|
|
|
|
return &ConfigWatcher{
|
|
watcher: watcher,
|
|
reloader: reloader,
|
|
logger: logger,
|
|
stopChan: make(chan struct{}),
|
|
stopOnce: sync.Once{}, // Initialize sync.Once
|
|
debounce: 500 * time.Millisecond,
|
|
}, nil
|
|
}
|
|
|
|
// Watch surveille les fichiers .env pour changements (T0040)
|
|
func (w *ConfigWatcher) Watch(envFiles []string) error {
|
|
// Ajouter les fichiers à surveiller
|
|
for _, file := range envFiles {
|
|
// Résoudre le chemin absolu pour éviter les problèmes de chemins relatifs
|
|
absPath, err := filepath.Abs(file)
|
|
if err != nil {
|
|
w.logger.Warn("Failed to resolve absolute path", zap.String("file", file), zap.Error(err))
|
|
absPath = file
|
|
}
|
|
|
|
if err := w.watcher.Add(absPath); err != nil {
|
|
w.logger.Warn("Failed to watch file", zap.String("file", absPath), zap.Error(err))
|
|
continue
|
|
}
|
|
w.logger.Info("Watching config file", zap.String("file", absPath))
|
|
}
|
|
|
|
w.wg.Add(1)
|
|
go w.watchLoop()
|
|
|
|
return nil
|
|
}
|
|
|
|
// watchLoop boucle principale de surveillance avec debouncing (T0040)
|
|
func (w *ConfigWatcher) watchLoop() {
|
|
defer w.wg.Done()
|
|
|
|
var debounceTimer *time.Timer
|
|
|
|
for {
|
|
select {
|
|
case event, ok := <-w.watcher.Events:
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
// Ignorer les opérations autres que Write et Create
|
|
if event.Op&fsnotify.Write == 0 && event.Op&fsnotify.Create == 0 {
|
|
continue
|
|
}
|
|
|
|
w.logger.Debug("Config file changed", zap.String("file", event.Name), zap.String("op", event.Op.String()))
|
|
|
|
// Arrêter le timer précédent si existant
|
|
if debounceTimer != nil {
|
|
debounceTimer.Stop()
|
|
}
|
|
|
|
// Démarrer un nouveau timer de debounce
|
|
debounceTimer = time.NewTimer(w.debounce)
|
|
|
|
// Goroutine pour attendre le debounce et relancer
|
|
go func(fileName string) {
|
|
<-debounceTimer.C
|
|
w.logger.Info("Config file changed, reloading", zap.String("file", fileName))
|
|
if err := w.reloader.ReloadAll(); err != nil {
|
|
w.logger.Error("Failed to reload config", zap.Error(err))
|
|
} else {
|
|
w.logger.Info("Config reloaded successfully")
|
|
}
|
|
}(event.Name)
|
|
|
|
case err, ok := <-w.watcher.Errors:
|
|
if !ok {
|
|
return
|
|
}
|
|
w.logger.Error("Watcher error", zap.Error(err))
|
|
|
|
case <-w.stopChan:
|
|
// Arrêter le timer si actif
|
|
if debounceTimer != nil {
|
|
debounceTimer.Stop()
|
|
}
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
// Stop arrête la surveillance proprement (T0040)
|
|
func (w *ConfigWatcher) Stop() error {
|
|
w.stopOnce.Do(func() {
|
|
close(w.stopChan)
|
|
})
|
|
err := w.watcher.Close()
|
|
w.wg.Wait()
|
|
return err
|
|
}
|
|
|
|
// GetWatchedFiles retourne la liste des fichiers surveillés (T0040)
|
|
func (w *ConfigWatcher) GetWatchedFiles() []string {
|
|
if w.watcher == nil {
|
|
return []string{}
|
|
}
|
|
return w.watcher.WatchList()
|
|
}
|