package data import ( "log" "path/filepath" "sync" "time" "github.com/fsnotify/fsnotify" ) // watchConfig refreshes the in-memory config snapshot on file changes, while // /system/reloadSystem remains responsible // for rebuilding database, Redis, storage, and scheduled tasks. func (d *Data) watchConfig() func() { configPath := d.runtime.ConfigPath() if configPath == "" { return func() {} } absolute, err := filepath.Abs(configPath) if err != nil { log.Printf("resolve config watch path: %v", err) return func() {} } watcher, err := fsnotify.NewWatcher() if err != nil { log.Printf("create config watcher: %v", err) return func() {} } if err = watcher.Add(filepath.Dir(absolute)); err != nil { log.Printf("watch config directory: %v", err) _ = watcher.Close() return func() {} } done := make(chan struct{}) var once sync.Once go func() { var timer *time.Timer for { select { case event, ok := <-watcher.Events: if !ok || filepath.Clean(event.Name) != absolute || event.Op&(fsnotify.Write|fsnotify.Create|fsnotify.Rename) == 0 { continue } if timer != nil { timer.Stop() } timer = time.AfterFunc(100*time.Millisecond, func() { next, loadErr := readBootstrap(absolute) if loadErr != nil { log.Printf("reload changed config: %v", loadErr) return } if next.Data == nil || next.Admin == nil { log.Printf("reload changed config: data and admin configuration are required") return } next.Admin.ConfigPath = absolute d.runtime.Replace(next.Data, next.Admin) log.Printf("config file changed: %s", absolute) }) case watchErr, ok := <-watcher.Errors: if ok { log.Printf("config watcher error: %v", watchErr) } case <-done: if timer != nil { timer.Stop() } return } } }() return func() { once.Do(func() { close(done) _ = watcher.Close() }) } }