kra-new/internal/integration/runtimeconfig/store.go

178 lines
4.3 KiB
Go

// Package runtimeconfig keeps the active database-backed integration settings
// and notifies long-lived provider clients when they change.
package runtimeconfig
import (
"bytes"
"encoding/json"
"strings"
"sync"
)
type Config struct {
Kind string
Provider string
Enabled bool
Values json.RawMessage
}
type listener struct {
kind string
provider string
callback func(Config)
}
type Store struct {
mu sync.RWMutex
values map[string]Config
listeners map[uint64]listener
nextID uint64
}
func NewStore() *Store {
return &Store{values: make(map[string]Config), listeners: make(map[uint64]listener)}
}
func configKey(kind, provider string) string {
return strings.ToLower(strings.TrimSpace(kind)) + "/" + strings.ToLower(strings.TrimSpace(provider))
}
func cloneConfig(config Config) Config {
config.Values = append(json.RawMessage(nil), config.Values...)
return config
}
func sameConfig(left, right Config) bool {
return left.Kind == right.Kind &&
left.Provider == right.Provider &&
left.Enabled == right.Enabled &&
bytes.Equal(left.Values, right.Values)
}
func (s *Store) Get(kind, provider string) (Config, bool) {
if s == nil {
return Config{}, false
}
s.mu.RLock()
config, ok := s.values[configKey(kind, provider)]
s.mu.RUnlock()
return cloneConfig(config), ok
}
func (s *Store) Set(config Config) {
if s == nil {
return
}
config.Kind = strings.ToLower(strings.TrimSpace(config.Kind))
config.Provider = strings.ToLower(strings.TrimSpace(config.Provider))
config = cloneConfig(config)
key := configKey(config.Kind, config.Provider)
s.mu.Lock()
if s.values == nil {
s.values = make(map[string]Config)
}
if previous, exists := s.values[key]; exists && sameConfig(previous, config) {
s.mu.Unlock()
return
}
s.values[key] = config
callbacks := s.matchingListenersLocked(config.Kind, config.Provider)
s.mu.Unlock()
for _, callback := range callbacks {
callback(cloneConfig(config))
}
}
func (s *Store) Delete(kind, provider string) {
if s == nil {
return
}
kind = strings.ToLower(strings.TrimSpace(kind))
provider = strings.ToLower(strings.TrimSpace(provider))
s.mu.Lock()
key := configKey(kind, provider)
if _, exists := s.values[key]; !exists {
s.mu.Unlock()
return
}
delete(s.values, key)
callbacks := s.matchingListenersLocked(kind, provider)
s.mu.Unlock()
config := Config{Kind: kind, Provider: provider}
for _, callback := range callbacks {
callback(config)
}
}
func (s *Store) Replace(configs []Config) {
if s == nil {
return
}
next := make(map[string]Config, len(configs))
for _, config := range configs {
config.Kind = strings.ToLower(strings.TrimSpace(config.Kind))
config.Provider = strings.ToLower(strings.TrimSpace(config.Provider))
config = cloneConfig(config)
next[configKey(config.Kind, config.Provider)] = config
}
s.mu.Lock()
previous := s.values
s.values = next
listeners := make([]listener, 0, len(s.listeners))
for _, item := range s.listeners {
listeners = append(listeners, item)
}
s.mu.Unlock()
changed := make(map[string]Config, len(previous)+len(next))
for key, previousConfig := range previous {
nextConfig, exists := next[key]
if !exists {
changed[key] = Config{Kind: previousConfig.Kind, Provider: previousConfig.Provider}
continue
}
if !sameConfig(previousConfig, nextConfig) {
changed[key] = nextConfig
}
}
for key, nextConfig := range next {
if _, exists := previous[key]; !exists {
changed[key] = nextConfig
}
}
for _, item := range listeners {
if config, ok := changed[configKey(item.kind, item.provider)]; ok {
item.callback(cloneConfig(config))
}
}
}
func (s *Store) Subscribe(kind, provider string, callback func(Config)) func() {
if s == nil || callback == nil {
return func() {}
}
s.mu.Lock()
s.nextID++
id := s.nextID
if s.listeners == nil {
s.listeners = make(map[uint64]listener)
}
s.listeners[id] = listener{kind: strings.ToLower(strings.TrimSpace(kind)), provider: strings.ToLower(strings.TrimSpace(provider)), callback: callback}
s.mu.Unlock()
return func() {
s.mu.Lock()
delete(s.listeners, id)
s.mu.Unlock()
}
}
func (s *Store) matchingListenersLocked(kind, provider string) []func(Config) {
callbacks := make([]func(Config), 0)
for _, item := range s.listeners {
if item.kind == kind && item.provider == provider {
callbacks = append(callbacks, item.callback)
}
}
return callbacks
}