124 lines
2.9 KiB
Go
124 lines
2.9 KiB
Go
package data
|
|
|
|
import (
|
|
"path/filepath"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/fsnotify/fsnotify"
|
|
"kra/internal/config"
|
|
)
|
|
|
|
// 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() {
|
|
logger := d.logger()
|
|
configPath := d.runtime.ConfigPath()
|
|
if configPath == "" {
|
|
return func() {}
|
|
}
|
|
absolute, err := filepath.Abs(configPath)
|
|
if err != nil {
|
|
logger.Error("resolve config watch path", "mod", "system", "error", err)
|
|
return func() {}
|
|
}
|
|
watcher, err := fsnotify.NewWatcher()
|
|
if err != nil {
|
|
logger.Error("create config watcher", "mod", "system", "error", err)
|
|
return func() {}
|
|
}
|
|
if err = watcher.Add(filepath.Dir(absolute)); err != nil {
|
|
logger.Error("watch config directory", "mod", "system", "error", err)
|
|
_ = watcher.Close()
|
|
return func() {}
|
|
}
|
|
done := make(chan struct{})
|
|
finished := make(chan struct{})
|
|
var once sync.Once
|
|
go func() {
|
|
defer close(finished)
|
|
var timer *time.Timer
|
|
var timerC <-chan time.Time
|
|
events := watcher.Events
|
|
watchErrors := watcher.Errors
|
|
for {
|
|
select {
|
|
case event, ok := <-events:
|
|
if !ok {
|
|
events = nil
|
|
continue
|
|
}
|
|
if filepath.Clean(event.Name) != absolute || event.Op&(fsnotify.Write|fsnotify.Create|fsnotify.Rename) == 0 {
|
|
continue
|
|
}
|
|
if timer != nil {
|
|
if !timer.Stop() {
|
|
select {
|
|
case <-timer.C:
|
|
default:
|
|
}
|
|
}
|
|
}
|
|
if timer == nil {
|
|
timer = time.NewTimer(100 * time.Millisecond)
|
|
} else {
|
|
timer.Reset(100 * time.Millisecond)
|
|
}
|
|
timerC = timer.C
|
|
case <-timerC:
|
|
// The debounce callback runs on this goroutine so shutdown can
|
|
// wait for it to finish without racing runtime replacement.
|
|
timerC = nil
|
|
select {
|
|
case <-done:
|
|
continue
|
|
default:
|
|
}
|
|
next, loadErr := readBootstrap(absolute)
|
|
if loadErr != nil {
|
|
logger.Error("reload changed config", "mod", "system", "error", loadErr)
|
|
continue
|
|
}
|
|
if next.Data == nil {
|
|
logger.Error("reload changed config: data configuration is required", "mod", "system")
|
|
continue
|
|
}
|
|
if next.Admin == nil {
|
|
next.Admin = &config.Admin{}
|
|
}
|
|
if current := d.runtime.Admin(); current != nil {
|
|
next.Admin.Storage = current.Storage
|
|
next.Admin.Email = current.Email
|
|
}
|
|
next.Admin.ConfigPath = absolute
|
|
d.runtime.Replace(next)
|
|
logger.Info("config file changed", "mod", "system", "path", absolute)
|
|
case watchErr, ok := <-watchErrors:
|
|
if !ok {
|
|
watchErrors = nil
|
|
continue
|
|
}
|
|
logger.Error("config watcher error", "mod", "system", "error", watchErr)
|
|
case <-done:
|
|
if timer != nil {
|
|
if !timer.Stop() {
|
|
select {
|
|
case <-timer.C:
|
|
default:
|
|
}
|
|
}
|
|
}
|
|
return
|
|
}
|
|
}
|
|
}()
|
|
return func() {
|
|
once.Do(func() {
|
|
close(done)
|
|
_ = watcher.Close()
|
|
<-finished
|
|
})
|
|
}
|
|
}
|