go-ssb-room/roomstate/roomstate.go

145 lines
3.6 KiB
Go

// SPDX-FileCopyrightText: 2021 The NGI Pointer Secure-Scuttlebutt Team of 2020/2021
//
// SPDX-License-Identifier: MIT
package roomstate
import (
"fmt"
"sort"
"sync"
"github.com/ssbc/go-muxrpc/v2"
kitlog "go.mindeco.de/log"
refs "github.com/ssbc/go-ssb-refs"
"github.com/ssbc/go-ssb-room/v2/internal/broadcasts"
)
type Manager struct {
logger kitlog.Logger
endpointsUpdater broadcasts.EndpointsEmitter
endpointsbroadcaster *broadcasts.EndpointsBroadcast
attendantsUpdater broadcasts.AttendantsEmitter
attendantsbroadcaster *broadcasts.AttendantsBroadcast
roomMu *sync.Mutex
room roomStateMap
}
func NewManager(log kitlog.Logger) *Manager {
var m Manager
m.logger = log
m.endpointsUpdater, m.endpointsbroadcaster = broadcasts.NewEndpointsEmitter()
m.attendantsUpdater, m.attendantsbroadcaster = broadcasts.NewAttendantsEmitter()
m.roomMu = new(sync.Mutex)
m.room = make(roomStateMap)
return &m
}
// roomStateMap is a single room
type roomStateMap map[string]muxrpc.Endpoint
// copy map entries to list for broadcast update
func (rsm roomStateMap) AsList() []string {
memberList := make([]string, 0, len(rsm))
for m := range rsm {
memberList = append(memberList, m)
}
sort.Strings(memberList)
return memberList
}
func (m *Manager) RegisterLegacyEndpoints(sink broadcasts.EndpointsEmitter) {
m.endpointsbroadcaster.Register(sink)
}
func (m *Manager) RegisterAttendantsUpdates(sink broadcasts.AttendantsEmitter) {
m.attendantsbroadcaster.Register(sink)
}
// List just returns a list of feed references as strings
func (m *Manager) List() []string {
m.roomMu.Lock()
defer m.roomMu.Unlock()
return m.room.AsList()
}
func (m *Manager) ListAsRefs() []refs.FeedRef {
m.roomMu.Lock()
lst := m.room.AsList()
m.roomMu.Unlock()
rlst := make([]refs.FeedRef, len(lst))
for i, s := range lst {
fr, err := refs.ParseFeedRef(s)
if err != nil {
panic(fmt.Errorf("invalid feed ref in room state: %d: %s", i, err))
}
rlst[i] = fr
}
return rlst
}
// AddEndpoint adds the endpoint to the room
func (m *Manager) AddEndpoint(who refs.FeedRef, edp muxrpc.Endpoint) {
m.roomMu.Lock()
// add ref to to the room map
m.room[who.String()] = edp
currentMembers := m.room.AsList()
m.roomMu.Unlock()
// update all the connected tunnel.endpoints calls
m.endpointsUpdater.Update(currentMembers)
// update all the connected room.attendants calls
m.attendantsUpdater.Joined(who)
}
// Remove removes the peer from the room
func (m *Manager) Remove(who refs.FeedRef) {
m.roomMu.Lock()
// remove ref from lobby
delete(m.room, who.String())
currentMembers := m.room.AsList()
m.roomMu.Unlock()
// update all the connected tunnel.endpoints calls
m.endpointsUpdater.Update(currentMembers)
// update all the connected room.attendants calls
m.attendantsUpdater.Left(who)
}
// AlreadyAdded returns true if the peer was already added to the room.
// if it isn't it will be added.
func (m *Manager) AlreadyAdded(who refs.FeedRef, edp muxrpc.Endpoint) bool {
m.roomMu.Lock()
var currentMembers []string
// if the peer didn't call tunnel.announce()
_, has := m.room[who.String()]
if !has {
// register them as if they didnt
m.room[who.String()] = edp
currentMembers = m.room.AsList()
}
m.roomMu.Unlock()
if !has {
// update everyone
m.endpointsUpdater.Update(currentMembers)
m.attendantsUpdater.Joined(who)
}
return has
}
// Has returns true and the endpoint if the peer is in the room
func (m *Manager) Has(who refs.FeedRef) (muxrpc.Endpoint, bool) {
m.roomMu.Lock()
// add ref to to the room map
edp, has := m.room[who.String()]
m.roomMu.Unlock()
return edp, has
}