feat: stream room updates SSE endpoint

via subscription to redis with enriched channel
This commit is contained in:
efim
2023-11-12 18:01:42 +00:00
parent 34d610a8c8
commit b90fcc3f20
6 changed files with 163 additions and 88 deletions

View File

@@ -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

View File

@@ -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
})