|
|
@@ -19,10 +19,15 @@ var (
|
|
|
EntityChangedSender chan bool
|
|
|
listeners []func()
|
|
|
|
|
|
- watchedMu sync.Mutex
|
|
|
- watchedPaths = map[string]struct{}{}
|
|
|
+ watchedMu sync.Mutex
|
|
|
+ watchedBindings = map[string]entityWatchBinding{}
|
|
|
)
|
|
|
|
|
|
+type entityWatchBinding struct {
|
|
|
+ entityName string
|
|
|
+ sourceFile string
|
|
|
+}
|
|
|
+
|
|
|
type Entity struct {
|
|
|
Data any
|
|
|
UniqueKey string
|
|
|
@@ -38,13 +43,10 @@ func SetupEntityFileWatchers(cfg *config.Config) {
|
|
|
}
|
|
|
|
|
|
// SyncEntityFileWatchers ensures each configured entity file has a watcher and reloads
|
|
|
-// entity data. Safe to call on config reload; already-watched paths are not watched twice.
|
|
|
+// entity data. Safe to call on config reload; obsolete watchers are stopped.
|
|
|
func SyncEntityFileWatchers(cfg *config.Config) {
|
|
|
baseDir := ResolveEntitiesBaseDir(cfg.GetDir())
|
|
|
- for i := range cfg.Entities { // #337 - iterate by key, not by value
|
|
|
- ef := cfg.Entities[i]
|
|
|
- syncEntityFileWatcher(baseDir, ef)
|
|
|
- }
|
|
|
+ reconcileEntityWatchers(desiredEntityWatchers(baseDir, cfg.Entities))
|
|
|
}
|
|
|
|
|
|
// ResolveEntitiesBaseDir returns the directory used to resolve relative entity file paths.
|
|
|
@@ -82,23 +84,71 @@ func resolveEntityFilePath(baseDir string, file string) string {
|
|
|
return p
|
|
|
}
|
|
|
|
|
|
-func syncEntityFileWatcher(baseDir string, ef *config.EntityFile) {
|
|
|
- p := resolveEntityFilePath(baseDir, ef.File)
|
|
|
+func desiredEntityWatchers(baseDir string, entityFiles []*config.EntityFile) map[string]entityWatchBinding {
|
|
|
+ desired := make(map[string]entityWatchBinding, len(entityFiles))
|
|
|
+ for i := range entityFiles { // #337 - iterate by key, not by value
|
|
|
+ ef := entityFiles[i]
|
|
|
+ path := resolveEntityFilePath(baseDir, ef.File)
|
|
|
+ desired[path] = entityWatchBinding{
|
|
|
+ entityName: ef.Name,
|
|
|
+ sourceFile: ef.SourceFile,
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return desired
|
|
|
+}
|
|
|
+
|
|
|
+func reconcileEntityWatchers(desired map[string]entityWatchBinding) {
|
|
|
+ stopObsoleteEntityWatchers(desired)
|
|
|
+ for path, binding := range desired {
|
|
|
+ ensureEntityFileWatcher(path, binding)
|
|
|
+ }
|
|
|
+}
|
|
|
|
|
|
+func stopObsoleteEntityWatchers(desired map[string]entityWatchBinding) {
|
|
|
watchedMu.Lock()
|
|
|
- _, alreadyWatched := watchedPaths[p]
|
|
|
- if !alreadyWatched {
|
|
|
- watchedPaths[p] = struct{}{}
|
|
|
+ defer watchedMu.Unlock()
|
|
|
+
|
|
|
+ for path, binding := range watchedBindings {
|
|
|
+ wanted, ok := desired[path]
|
|
|
+ if ok && wanted == binding {
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ filehelper.StopFileWatch(path)
|
|
|
+ delete(watchedBindings, path)
|
|
|
}
|
|
|
+}
|
|
|
+
|
|
|
+func ensureEntityFileWatcher(path string, binding entityWatchBinding) {
|
|
|
+ watchedMu.Lock()
|
|
|
+ existing, watching := watchedBindings[path]
|
|
|
+ if watching && existing == binding {
|
|
|
+ watchedMu.Unlock()
|
|
|
+ loadEntityFile(path, binding.entityName)
|
|
|
+ return
|
|
|
+ }
|
|
|
+ watchedBindings[path] = binding
|
|
|
watchedMu.Unlock()
|
|
|
|
|
|
- if !alreadyWatched {
|
|
|
- go filehelper.WatchFileWrite(p, func(_ string) { loadEntityFile(p, ef.Name) }, filehelper.WatchMeta{
|
|
|
- ConfigFile: ef.SourceFile,
|
|
|
- })
|
|
|
+ filehelper.WatchFileWrite(path, makeEntityFileWatchCallback(path), filehelper.WatchMeta{
|
|
|
+ ConfigFile: binding.sourceFile,
|
|
|
+ })
|
|
|
+ loadEntityFile(path, binding.entityName)
|
|
|
+}
|
|
|
+
|
|
|
+func makeEntityFileWatchCallback(path string) func(string) {
|
|
|
+ return func(_ string) {
|
|
|
+ entityFileWatchCallback(path)
|
|
|
}
|
|
|
+}
|
|
|
|
|
|
- loadEntityFile(p, ef.Name)
|
|
|
+func entityFileWatchCallback(path string) {
|
|
|
+ watchedMu.Lock()
|
|
|
+ binding, ok := watchedBindings[path]
|
|
|
+ watchedMu.Unlock()
|
|
|
+ if !ok {
|
|
|
+ return
|
|
|
+ }
|
|
|
+ loadEntityFile(path, binding.entityName)
|
|
|
}
|
|
|
|
|
|
func loadEntityFile(filename string, entityname string) {
|