mirror of
https://github.com/safing/portmaster
synced 2025-09-01 18:19:12 +00:00
188 lines
4.1 KiB
Go
188 lines
4.1 KiB
Go
package mgr
|
|
|
|
import (
|
|
"slices"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
// StateMgr is a simple state manager.
|
|
type StateMgr struct {
|
|
states []State
|
|
statesLock sync.Mutex
|
|
|
|
statesEventMgr *EventMgr[StateUpdate]
|
|
|
|
mgr *Manager
|
|
}
|
|
|
|
// State describes the state of a manager or module.
|
|
type State struct {
|
|
// ID is a program-unique ID.
|
|
// It must not only be unique within the StateMgr, but for the whole program,
|
|
// as it may be re-used with related systems.
|
|
// Required.
|
|
ID string
|
|
|
|
// Name is the name of the state.
|
|
// This may also serve as a notification title.
|
|
// Required.
|
|
Name string
|
|
|
|
// Message is a more detailed message about the state.
|
|
// Optional.
|
|
Message string
|
|
|
|
// Type defines the type of the state.
|
|
// Optional.
|
|
Type StateType
|
|
|
|
// Time is the time when the state was created or the originating incident occurred.
|
|
// Optional, will be set to current time if not set.
|
|
Time time.Time
|
|
|
|
// Data can hold any additional data necessary for further processing of connected systems.
|
|
// Optional.
|
|
Data any
|
|
}
|
|
|
|
// StateType defines commonly used states.
|
|
type StateType string
|
|
|
|
// State Types.
|
|
const (
|
|
StateTypeUndefined = ""
|
|
StateTypeHint = "hint"
|
|
StateTypeWarning = "warning"
|
|
StateTypeError = "error"
|
|
)
|
|
|
|
// Severity returns a number representing the gravity of the state for ordering.
|
|
func (st StateType) Severity() int {
|
|
switch st {
|
|
case StateTypeUndefined:
|
|
return 0
|
|
case StateTypeHint:
|
|
return 1
|
|
case StateTypeWarning:
|
|
return 2
|
|
case StateTypeError:
|
|
return 3
|
|
default:
|
|
return 0
|
|
}
|
|
}
|
|
|
|
// StateUpdate is used to update others about a state change.
|
|
type StateUpdate struct {
|
|
Module string
|
|
States []State
|
|
}
|
|
|
|
// StatefulModule is used for interface checks on modules.
|
|
type StatefulModule interface {
|
|
States() *StateMgr
|
|
}
|
|
|
|
// NewStateMgr returns a new state manager.
|
|
func NewStateMgr(mgr *Manager) *StateMgr {
|
|
return &StateMgr{
|
|
statesEventMgr: NewEventMgr[StateUpdate]("state update", mgr),
|
|
mgr: mgr,
|
|
}
|
|
}
|
|
|
|
// NewStateMgr returns a new state manager.
|
|
func (m *Manager) NewStateMgr() *StateMgr {
|
|
return NewStateMgr(m)
|
|
}
|
|
|
|
// Add adds a state.
|
|
// If a state with the same ID already exists, it is replaced.
|
|
func (m *StateMgr) Add(s State) {
|
|
m.statesLock.Lock()
|
|
defer m.statesLock.Unlock()
|
|
|
|
if s.Time.IsZero() {
|
|
s.Time = time.Now()
|
|
}
|
|
|
|
// Update or add state.
|
|
index := slices.IndexFunc(m.states, func(es State) bool {
|
|
return es.ID == s.ID
|
|
})
|
|
if index >= 0 {
|
|
m.states[index] = s
|
|
} else {
|
|
m.states = append(m.states, s)
|
|
}
|
|
|
|
m.statesEventMgr.Submit(m.export())
|
|
}
|
|
|
|
// Remove removes the state with the given ID.
|
|
func (m *StateMgr) Remove(id string) {
|
|
m.statesLock.Lock()
|
|
defer m.statesLock.Unlock()
|
|
|
|
// Quick check if slice is empty.
|
|
// It is a common pattern to remove a state when no error was encountered at
|
|
// a critical operation. This means that StateMgr.Remove will be called often.
|
|
if len(m.states) == 0 {
|
|
return
|
|
}
|
|
|
|
var entryRemoved bool
|
|
m.states = slices.DeleteFunc(m.states, func(s State) bool {
|
|
if s.ID == id {
|
|
entryRemoved = true
|
|
return true
|
|
}
|
|
return false
|
|
})
|
|
|
|
if entryRemoved {
|
|
m.statesEventMgr.Submit(m.export())
|
|
}
|
|
}
|
|
|
|
// Clear removes all states.
|
|
func (m *StateMgr) Clear() {
|
|
m.statesLock.Lock()
|
|
m.states = nil
|
|
m.statesLock.Unlock()
|
|
|
|
// Submit event without lock, because callbacks might come back to change states.
|
|
defer m.statesEventMgr.Submit(m.Export())
|
|
}
|
|
|
|
// Export returns the current states.
|
|
func (m *StateMgr) Export() StateUpdate {
|
|
m.statesLock.Lock()
|
|
defer m.statesLock.Unlock()
|
|
|
|
return m.export()
|
|
}
|
|
|
|
// export returns the current states.
|
|
func (m *StateMgr) export() StateUpdate {
|
|
name := ""
|
|
if m.mgr != nil {
|
|
name = m.mgr.name
|
|
}
|
|
|
|
return StateUpdate{
|
|
Module: name,
|
|
States: slices.Clone(m.states),
|
|
}
|
|
}
|
|
|
|
// Subscribe subscribes to state update events.
|
|
func (m *StateMgr) Subscribe(subscriberName string, chanSize int) *EventSubscription[StateUpdate] {
|
|
return m.statesEventMgr.Subscribe(subscriberName, chanSize)
|
|
}
|
|
|
|
// AddCallback adds a callback to state update events.
|
|
func (m *StateMgr) AddCallback(callbackName string, callback EventCallbackFunc[StateUpdate]) {
|
|
m.statesEventMgr.AddCallback(callbackName, callback)
|
|
}
|