feat: stream room updates SSE endpoint
via subscription to redis with enriched channel
This commit is contained in:
@@ -35,10 +35,11 @@ func (r *Room)InitMaps() {
|
||||
// if you are speaking - change nothing
|
||||
// if nobody is speaking, set this person as a first speaker
|
||||
func (r *Room) RaiseHand(p PersonId, gesture HandGesture) Room {
|
||||
if (r.CurrentSpeaker == p) {
|
||||
// if person already speaking, should first end speaking
|
||||
return *r
|
||||
}
|
||||
// TODO This is temporary
|
||||
// if (r.CurrentSpeaker == p) {
|
||||
// // if person already speaking, should first end speaking
|
||||
// return *r
|
||||
// }
|
||||
r.ParticipantHands[p] = gesture
|
||||
if r.CurrentSpeaker == PersonId(0) {
|
||||
r.CurrentSpeaker = p
|
||||
|
||||
@@ -5,8 +5,8 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"log"
|
||||
"math/rand"
|
||||
|
||||
"github.com/redis/go-redis/v9"
|
||||
)
|
||||
@@ -15,26 +15,25 @@ type PersonId int
|
||||
|
||||
func (p PersonId) MarshalBinary() ([]byte, error) {
|
||||
bytes, err := json.Marshal(p)
|
||||
return bytes, err
|
||||
return bytes, err
|
||||
}
|
||||
|
||||
func (p *PersonId) UnmarshalBinary(data []byte) error {
|
||||
err := json.Unmarshal(data, p)
|
||||
return err
|
||||
return err
|
||||
}
|
||||
|
||||
// TODO move to rooms i guess
|
||||
func RandomPersonId() PersonId {
|
||||
randInt := rand.Int()
|
||||
if randInt == 0 {
|
||||
randInt = 1
|
||||
randInt = 1
|
||||
}
|
||||
return PersonId(randInt)
|
||||
}
|
||||
|
||||
|
||||
type Person struct {
|
||||
Id PersonId
|
||||
Id PersonId
|
||||
Name string
|
||||
PasswordHash string
|
||||
}
|
||||
@@ -48,20 +47,21 @@ func (r *Room) UnmarshalBinary(data []byte) error {
|
||||
return json.Unmarshal(data, r)
|
||||
}
|
||||
|
||||
|
||||
// let's check whether it will be possible to save nested structs
|
||||
|
||||
type RoomManager interface {
|
||||
Get(roomName string) (Room, bool, error)
|
||||
Save(room Room) error
|
||||
Update(ctx context.Context, roomName string, f func(fromRoom Room) (toRoom Room)) error
|
||||
Subscribe(ctx context.Context, roomName string) <-chan Room
|
||||
}
|
||||
|
||||
const roomRedisPrefix = "room"
|
||||
|
||||
func roomNameToRedisId(roomName string) string {
|
||||
return fmt.Sprintf("%s:%s", roomRedisPrefix, roomName)
|
||||
}
|
||||
|
||||
|
||||
type RedisRM struct {
|
||||
Rdb *redis.Client
|
||||
}
|
||||
@@ -80,11 +80,70 @@ func (redisRM RedisRM) Get(roomName string) (Room, bool, error) {
|
||||
return readRoom, true, nil
|
||||
}
|
||||
|
||||
func (redisRM RedisRM) Subscribe(ctx context.Context, roomName string) <-chan Room {
|
||||
// pubsub := redisRM.Rdb.Subscribe(ctx, roomNameToRedisId(roomName))
|
||||
key := fmt.Sprintf( "__keyspace@0__:room:%s", roomName)
|
||||
log.Printf("><> about to subscribe to %s\n", key)
|
||||
pubsub := redisRM.Rdb.Subscribe(ctx, key)
|
||||
|
||||
redisMsgChan := pubsub.Channel()
|
||||
|
||||
roomChannel := make(chan Room)
|
||||
|
||||
go func() {
|
||||
defer log.Printf(">>> stopping redis subscription to %s\n", roomName)
|
||||
defer pubsub.Close() // this was my problem, cooool
|
||||
|
||||
var initialRoom Room
|
||||
err := redisRM.Rdb.Get(ctx, roomNameToRedisId(roomName)).Scan(&initialRoom)
|
||||
if err != nil {
|
||||
log.Printf("in stream for %s got initial error %s/n", roomName, err)
|
||||
close(roomChannel)
|
||||
return
|
||||
}
|
||||
roomChannel <- initialRoom
|
||||
|
||||
for {
|
||||
select {
|
||||
case msg := <-redisMsgChan:
|
||||
log.Printf("> subscribe got message %v\n", msg)
|
||||
|
||||
if msg == nil {
|
||||
log.Print("> subscribe got nil redis message for some reason")
|
||||
return
|
||||
}
|
||||
log.Printf(">>> chan: %s, patt: %s, payload: %s, payloadSlice: %v\n", msg.Channel, msg.Pattern, msg.Payload, msg.PayloadSlice)
|
||||
if msg.Payload == "del" {
|
||||
// room has been deleted, can stop the stream
|
||||
close(roomChannel)
|
||||
return
|
||||
}
|
||||
// for any other event - read the room state and put into room channel
|
||||
var room Room
|
||||
err := redisRM.Rdb.Get(ctx, roomNameToRedisId(roomName)).Scan(&room)
|
||||
if err != nil {
|
||||
log.Printf("in stream for %s got error %s/n", roomName, err)
|
||||
close(roomChannel)
|
||||
}
|
||||
roomChannel <- room
|
||||
case <-ctx.Done():
|
||||
log.Println("got Done in subscribe")
|
||||
close(roomChannel)
|
||||
return
|
||||
}
|
||||
|
||||
}
|
||||
}()
|
||||
|
||||
return roomChannel
|
||||
}
|
||||
|
||||
var maxRetries int = 20
|
||||
|
||||
func (redisRM RedisRM) Update(ctx context.Context, roomName string, f func(fromRoom Room) (toRoom Room)) error {
|
||||
// transactional function
|
||||
roomKey := roomNameToRedisId(roomName)
|
||||
txf := func (tx *redis.Tx) error {
|
||||
txf := func(tx *redis.Tx) error {
|
||||
var savedRoom Room
|
||||
err := tx.Get(ctx, roomKey).Scan(&savedRoom)
|
||||
if err != nil {
|
||||
@@ -95,6 +154,7 @@ func (redisRM RedisRM) Update(ctx context.Context, roomName string, f func(fromR
|
||||
room := f(savedRoom)
|
||||
|
||||
_, err = tx.Pipelined(ctx, func(pipe redis.Pipeliner) error {
|
||||
log.Printf(">> about to Set %s to %v", roomName, room)
|
||||
pipe.Set(ctx, roomKey, &room, 0)
|
||||
return nil
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user