package watcher import ( "crypto/md5" "fmt" "os" "time" "firewall_containers/network-go/logger" ) // FileWatcher periodically checks a file for changes using MD5 hash type FileWatcher struct { path string period time.Duration lastHash string onChange func() stopCh chan struct{} } // NewFileWatcher creates a new file watcher that polls the file at the given period func NewFileWatcher(path string, period time.Duration, onChange func()) *FileWatcher { return &FileWatcher{ path: path, period: period, lastHash: "", onChange: onChange, stopCh: make(chan struct{}), } } // hashFile computes an MD5 hash of the file contents func (fw *FileWatcher) hashFile() (string, error) { data, err := os.ReadFile(fw.path) if err != nil { return "", fmt.Errorf("failed to read file %s: %w", fw.path, err) } return fmt.Sprintf("%x", md5.Sum(data)), nil } // Start begins polling the file for changes in a goroutine func (fw *FileWatcher) Start() { // Compute initial hash hash, err := fw.hashFile() if err != nil { logger.Warn("WATCHER: initial hash computation failed for %s: %v", fw.path, err) } else { fw.lastHash = hash } go func() { ticker := time.NewTicker(fw.period) defer ticker.Stop() logger.Info("WATCHER: started watching %s every %s", fw.path, fw.period) for { select { case <-fw.stopCh: logger.Info("WATCHER: stopped watching %s", fw.path) return case <-ticker.C: hash, err := fw.hashFile() if err != nil { logger.Warn("WATCHER: failed to hash %s: %v", fw.path, err) continue } if hash != fw.lastHash { logger.Info("WATCHER: detected change in %s", fw.path) fw.lastHash = hash if fw.onChange != nil { fw.onChange() } } } } }() } // Stop signals the watcher goroutine to stop func (fw *FileWatcher) Stop() { close(fw.stopCh) }