59 lines
1.6 KiB
Go
59 lines
1.6 KiB
Go
package live
|
|
|
|
import (
|
|
"sort"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
// RosterEntry is one observed Sendspin client. Populated from
|
|
// client/hello messages — independent of whether the connection was
|
|
// then accepted or rejected. This is what gives the UI the ability to
|
|
// show the *actual* `client_id` of every speaker on the network, even
|
|
// when ClientFilter rejected its current attempt.
|
|
type RosterEntry struct {
|
|
ClientID string `json:"client_id"`
|
|
Name string `json:"name"`
|
|
LastSeen time.Time `json:"last_seen"`
|
|
}
|
|
|
|
// Roster caches every (client_id, name) pair the sendspin server has
|
|
// observed via client/hello. Records are kept indefinitely — speakers
|
|
// usually keep the same client_id across reboots, so a stale entry
|
|
// remains useful when the user is configuring presets offline.
|
|
type Roster struct {
|
|
mu sync.RWMutex
|
|
entries map[string]RosterEntry
|
|
}
|
|
|
|
func NewRoster() *Roster {
|
|
return &Roster{entries: make(map[string]RosterEntry)}
|
|
}
|
|
|
|
// Record is intended to be plugged into ServerConfig.OnClientHello.
|
|
// Updates the entry's LastSeen on every call so the UI can show
|
|
// "active now" vs. "last seen 5 minutes ago".
|
|
func (r *Roster) Record(clientID, name string) {
|
|
if clientID == "" {
|
|
return
|
|
}
|
|
r.mu.Lock()
|
|
defer r.mu.Unlock()
|
|
r.entries[clientID] = RosterEntry{
|
|
ClientID: clientID,
|
|
Name: name,
|
|
LastSeen: time.Now(),
|
|
}
|
|
}
|
|
|
|
func (r *Roster) List() []RosterEntry {
|
|
r.mu.RLock()
|
|
defer r.mu.RUnlock()
|
|
out := make([]RosterEntry, 0, len(r.entries))
|
|
for _, e := range r.entries {
|
|
out = append(out, e)
|
|
}
|
|
sort.Slice(out, func(i, j int) bool { return out[i].ClientID < out[j].ClientID })
|
|
return out
|
|
}
|