diff --git a/config/config.go b/config/config.go index e25d073..94f27a3 100644 --- a/config/config.go +++ b/config/config.go @@ -5,45 +5,25 @@ import ( "encoding/json" "fmt" "os" + "sync" ) -// LoadBotNodeConfig reads and parses bot_node_config.json and stores it as the -// global C_botNodeConfig singleton, mirroring LoadGlobalConfig. Unlike the -// other config files this one is auto-generated and optional: a missing or -// empty file yields an empty map, so every node falls back to its default -// enableStatusNotify value (true). -func LoadBotNodeConfig(configPath string) error { - cfg := BotNodeMembers{} - data, err := os.ReadFile(configPath) - if err != nil { - if os.IsNotExist(err) { - C_botNodeConfig = cfg - return nil - } - return fmt.Errorf("failed to read bot node config file: %w", err) - } - if len(bytes.TrimSpace(data)) > 0 { - if err := json.Unmarshal(data, &cfg); err != nil { - return fmt.Errorf("failed to parse bot node config file: %w", err) - } - } - C_botNodeConfig = cfg - return nil -} +// botNodeConfigMu guards botNodeConfig, the in-memory mirror of +// bot_node_config.json. +var ( + botNodeConfigMu sync.RWMutex + botNodeConfig BotNodeMembers +) // NodeStatusNotifyEnabled reports whether the node identified by uuid should -// broadcast status-change notifications, per bot_node_config.json. -// enableStatusNotify defaults to true: a node notifies unless its entry -// explicitly sets the flag to false. +// broadcast status-change notifications, per bot_node_config.json. The flag is +// allow-list only: a node notifies when its entry has enableStatusNotify set to +// true, and stays silent when the entry is absent or not enabled. func NodeStatusNotifyEnabled(uuid string) bool { - if C_botNodeConfig == nil { - return true - } - opts, ok := C_botNodeConfig[uuid] - if !ok || opts.EnableStatusNotify == nil { - return true - } - return *opts.EnableStatusNotify + botNodeConfigMu.RLock() + opts, ok := botNodeConfig[uuid] + botNodeConfigMu.RUnlock() + return ok && opts.EnableStatusNotify } // LoadGlobalConfig reads and parses the configuration file, applies defaults, @@ -155,16 +135,16 @@ func LoadBotUserConfig(configPath string) (*BotUserConfig, error) { return &cfg, nil } -// SaveBotNodeConfig persists the given node UUIDs to the bot node config file, -// which is a plain registry of the nodes Komari reports (read back at startup -// by LoadBotNodeConfig). Entries already present are always preserved: a UUID -// Komari no longer reports on a given fetch is kept rather than deleted, and -// any enableStatusNotify a user set by hand is left untouched. UUIDs seen for -// the first time are added as an empty object, i.e. no parameter is written, so -// they inherit the enableStatusNotify default (true). The resulting JSON has -// its object keys emitted in sorted order by encoding/json, keeping the file -// deterministic across writes. The path is supplied by the caller (typically -// global.ConfigPath.BotNodeConfig). +// SaveBotNodeConfig persists the given node UUIDs to the bot node config file +// and refreshes the in-memory mirror read by NodeStatusNotifyEnabled. Entries +// already present in the file are always preserved: a UUID Komari no longer +// reports on a given fetch is kept rather than deleted, so per-node settings +// for stale nodes survive a node-list refresh that does not include them. UUIDs +// seen for the first time are added disabled (enableStatusNotify defaults to +// false); enable a node's status notifications by setting the flag to true in +// the file. The resulting JSON has its object keys emitted in sorted order by +// encoding/json, keeping the file deterministic across writes. The path is +// supplied by the caller (typically global.ConfigPath.BotNodeConfig). func SaveBotNodeConfig(configPath string, uuids []string) error { // Start from whatever is already on disk so nothing is dropped. An empty or // missing file is treated as an empty map. @@ -192,6 +172,10 @@ func SaveBotNodeConfig(configPath string, uuids []string) error { if err := os.WriteFile(configPath, data, 0o644); err != nil { return fmt.Errorf("failed to write bot node config %s: %w", configPath, err) } + + botNodeConfigMu.Lock() + botNodeConfig = members + botNodeConfigMu.Unlock() return nil } diff --git a/config/variables.go b/config/variables.go index 0e7b3ae..03f7a75 100644 --- a/config/variables.go +++ b/config/variables.go @@ -175,18 +175,14 @@ var C_botUserConfig *BotUserConfig // file is auto-populated by the node tracker for every node Komari reports; // per-node options are edited by hand in the JSON file. type BotNodeOptions struct { - // EnableStatusNotify controls whether this node broadcasts status-change - // notifications. It defaults to true: only an explicit false in the JSON - // file disables a node's notifications. The pointer (rather than a plain - // bool) lets an absent field be told apart from an explicit false, and - // omitempty keeps untouched nodes stored as {}. - EnableStatusNotify *bool `json:"enableStatusNotify,omitempty"` + // EnableStatusNotify is an allow-list switch: a node broadcasts status-change + // notifications only when this is true. Nodes default to false (silent) + // unless explicitly enabled. + EnableStatusNotify bool `json:"enableStatusNotify"` } // BotNodeMembers maps a node UUID (as reported by Komari) to its per-node -// options. -type BotNodeMembers map[string]BotNodeOptions - -// C_botNodeConfig is the global singleton mirroring bot_node_config.json, -// populated by LoadBotNodeConfig. -var C_botNodeConfig BotNodeMembers \ No newline at end of file +// options, e.g. bot_node_config.json: +// +// {"": {"enableStatusNotify": true}, "": {}} +type BotNodeMembers map[string]BotNodeOptions \ No newline at end of file diff --git a/main.go b/main.go index f6102a4..f6d0c5c 100644 --- a/main.go +++ b/main.go @@ -47,13 +47,6 @@ func main() { log.Fatalf("Failed to load bot user config: %v", err) } - // Load the node registry config. Unlike the other files it is optional: - // missing or empty bot_node_config.json simply means every node keeps its - // default enableStatusNotify (true). - if err := config.LoadBotNodeConfig(global.ConfigPath.BotNodeConfig); err != nil { - log.Fatalf("Failed to load bot node config: %v", err) - } - // Initialize logging. postLog.SetDebugMode(cfg.System.DebugMode) postLog.InitLogBroadcaster()