kra-new/internal/integrationruntime/store.go

146 lines
3.6 KiB
Go

// Package integrationruntime keeps the active database-backed integration
// settings and notifies long-lived provider clients when they change.
package integrationruntime
import (
"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 (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()
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()
delete(s.values, configKey(kind, provider))
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, config := range previous {
changed[key] = Config{Kind: config.Kind, Provider: config.Provider}
}
for key, config := range next {
changed[key] = config
}
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
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
}