package appcontext import ( "fmt" "log/slog" "os" "time" "github.com/AFASystems/presence/internal/pkg/kafkaclient" "github.com/mitchellh/mapstructure" ) // AppState provides centralized access to application state type AppState struct { beacons BeaconsList settings Settings beaconEvents BeaconEventList beaconsLookup BeaconsLookup health Health startTime time.Time } func getEnv(key, def string) string { if v := os.Getenv(key); v != "" { return v } return def } // NewAppState creates a new application context AppState with default values func NewAppState() *AppState { return &AppState{ startTime: time.Now(), beacons: BeaconsList{ Beacons: make(map[string]Beacon), }, settings: Settings{ ID: 1, CurrentAlgorithm: getEnv("ALGORITHM", "filter"), LocationConfidence: 4, LastSeenThreshold: 15, BeaconMetricSize: 30, HASendInterval: 5, HASendChangesOnly: false, RSSIEnforceThreshold: false, RSSIMinThreshold: 100, }, beaconEvents: BeaconEventList{ Beacons: make(map[string]BeaconEvent), }, beaconsLookup: BeaconsLookup{ Lookup: make(map[string]string), }, health: Health{ ID: "1", Location: LocationHealth{ BaseHealth: BaseHealth{ Uptime: 0, ActiveReaders: []string{}, ActiveWriters: []string{}, ActiveBeacons: []string{}, }, }, Decoder: DecoderHealth{ BaseHealth: BaseHealth{ Uptime: 0, ActiveReaders: []string{}, ActiveWriters: []string{}, ActiveBeacons: []string{}, }, }, Bridge: BridgeHealth{ BaseHealth: BaseHealth{ Uptime: 0, ActiveReaders: []string{}, ActiveWriters: []string{}, ActiveBeacons: []string{}, }, }, }, } } func (m *AppState) GetLocationHealth(k *kafkaclient.KafkaManager) ([]byte, error) { m.health.Location.GetUptime(m.startTime) m.health.Location.GetActiveReaders(k) m.health.Location.GetActiveWriters(k) m.health.Location.GetActiveBeacons(m) return m.health.Location.Marshal() } func (m *AppState) GetDecoderHealth(k *kafkaclient.KafkaManager) ([]byte, error) { m.health.Decoder.GetUptime(m.startTime) m.health.Decoder.GetActiveReaders(k) m.health.Decoder.GetActiveWriters(k) m.health.Decoder.GetActiveBeacons(m) return m.health.Decoder.Marshal() } func (m *AppState) GetBridgeHealth(k *kafkaclient.KafkaManager) ([]byte, error) { m.health.Bridge.GetUptime(m.startTime) m.health.Bridge.GetActiveReaders(k) m.health.Bridge.GetActiveWriters(k) m.health.Bridge.GetActiveBeacons(m) return m.health.Bridge.Marshal() } // GetBeacons returns thread-safe access to beacons list func (m *AppState) GetBeacons() *BeaconsList { m.beacons.Lock.RLock() defer m.beacons.Lock.RUnlock() return &m.beacons } // GetSettings returns thread-safe access to settings func (m *AppState) GetSettings() *Settings { return &m.settings } // GetBeaconEvents returns thread-safe access to beacon events func (m *AppState) GetBeaconEvents() *BeaconEventList { m.beaconEvents.Lock.RLock() defer m.beaconEvents.Lock.RUnlock() return &m.beaconEvents } // AddBeaconToLookup adds a beacon ID to the lookup map func (m *AppState) AddBeaconToLookup(id, value string) { m.beaconsLookup.Lock.Lock() m.beaconsLookup.Lookup[id] = value m.beaconsLookup.Lock.Unlock() } // RemoveBeaconFromLookup removes a beacon ID from the lookup map func (m *AppState) RemoveBeaconFromLookup(id string) { m.beaconsLookup.Lock.Lock() delete(m.beaconsLookup.Lookup, id) m.beaconsLookup.Lock.Unlock() } func (m *AppState) CleanLookup() { m.beaconsLookup.Lock.Lock() clear(m.beaconsLookup.Lookup) m.beaconsLookup.Lock.Unlock() } // BeaconExists checks if a beacon exists in the lookup func (m *AppState) BeaconExists(id string) (string, bool) { m.beaconsLookup.Lock.RLock() defer m.beaconsLookup.Lock.RUnlock() val, exists := m.beaconsLookup.Lookup[id] return val, exists } // GetBeacon returns a beacon by ID (thread-safe) func (m *AppState) GetBeacon(id string) (Beacon, bool) { m.beacons.Lock.RLock() defer m.beacons.Lock.RUnlock() beacon, exists := m.beacons.Beacons[id] return beacon, exists } // UpdateBeacon updates a beacon in the list (thread-safe) func (m *AppState) UpdateBeacon(id string, beacon Beacon) { m.beacons.Lock.Lock() defer m.beacons.Lock.Unlock() m.beacons.Beacons[id] = beacon } // GetBeaconEvent returns a beacon event by ID (thread-safe) func (m *AppState) GetBeaconEvent(id string) (BeaconEvent, bool) { m.beaconEvents.Lock.RLock() defer m.beaconEvents.Lock.RUnlock() event, exists := m.beaconEvents.Beacons[id] return event, exists } // UpdateBeaconEvent updates a beacon event in the list (thread-safe) func (m *AppState) UpdateBeaconEvent(id string, event BeaconEvent) { m.beaconEvents.Lock.Lock() defer m.beaconEvents.Lock.Unlock() m.beaconEvents.Beacons[id] = event } // GetAllBeacons returns a copy of all beacons func (m *AppState) GetAllBeacons() map[string]Beacon { m.beacons.Lock.RLock() defer m.beacons.Lock.RUnlock() beacons := make(map[string]Beacon) for id, beacon := range m.beacons.Beacons { beacons[id] = beacon } return beacons } // GetBeaconCount returns the number of tracked beacons func (m *AppState) GetBeaconCount() int { m.beacons.Lock.RLock() defer m.beacons.Lock.RUnlock() return len(m.beacons.Beacons) } // GetSettingsValue returns current settings as a value func (m *AppState) GetSettingsValue() Settings { return m.settings } // UpdateSettings updates the system settings (thread-safe) func (m *AppState) UpdateSettings(settings map[string]any) { if err := mapstructure.Decode(settings, &m.settings); err != nil { msg := fmt.Sprintf("Error in persisting settings: %v", err) slog.Error(msg) } }