110 lines
2.5 KiB
Go
110 lines
2.5 KiB
Go
package rooms
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"log"
|
|
|
|
"github.com/redis/go-redis/v9"
|
|
)
|
|
|
|
type Person struct {
|
|
PersonId int
|
|
Name string
|
|
PasswordHash string
|
|
}
|
|
|
|
type Room struct {
|
|
Name string // will be unique ID
|
|
AdminIds []int
|
|
PasswordHash string
|
|
Paricipants []Person
|
|
// TODO hands, for each type of hand?
|
|
// i guess participants order fixed for now?
|
|
// and i'll still need 'current' for each hand level
|
|
}
|
|
|
|
// well, it seems that i'd better do marshalling into bytes then
|
|
// see https://github.com/redis/go-redis/issues/2512
|
|
func (r *Room) MarshalBinary() (data []byte, err error) {
|
|
return json.Marshal(r)
|
|
}
|
|
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
|
|
}
|
|
const roomRedisPrefix = "room"
|
|
func roomNameToRedisId(roomName string) string {
|
|
return fmt.Sprintf("%s:%s", roomRedisPrefix, roomName)
|
|
}
|
|
|
|
|
|
type RedisRM struct {
|
|
Rdb *redis.Client
|
|
}
|
|
|
|
func (redisRM RedisRM) Get(roomName string) (Room, bool, error) {
|
|
var readRoom Room
|
|
err := redisRM.Rdb.Get(context.TODO(), roomNameToRedisId(roomName)).Scan(&readRoom)
|
|
if err == redis.Nil {
|
|
return Room{}, false, nil
|
|
}
|
|
if err != nil {
|
|
log.Printf("error reading room with id %s : %s", roomName, err)
|
|
return Room{}, false, err
|
|
}
|
|
|
|
return readRoom, true, nil
|
|
}
|
|
|
|
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 {
|
|
var savedRoom Room
|
|
err := tx.Get(ctx, roomKey).Scan(&savedRoom)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
room := f(savedRoom)
|
|
|
|
_, err = tx.Pipelined(ctx, func(pipe redis.Pipeliner) error {
|
|
pipe.Set(ctx, roomKey, &room, 0)
|
|
return nil
|
|
})
|
|
|
|
return err
|
|
}
|
|
for i := 0; i < maxRetries; i++ {
|
|
err := redisRM.Rdb.Watch(ctx, txf, roomKey)
|
|
if err == nil {
|
|
return nil // success
|
|
}
|
|
if err == redis.TxFailedErr {
|
|
// optimistic lock will keep spinning
|
|
continue
|
|
}
|
|
// non tx errror are returned, including redis.Nil
|
|
return err
|
|
}
|
|
|
|
return errors.New("update reached maximum amount of retries")
|
|
}
|
|
|
|
func (redisRM RedisRM) Save(room Room) error {
|
|
err := redisRM.Rdb.Set(context.TODO(), roomNameToRedisId(room.Name), &room, 0).Err() // maybe even set expiration?
|
|
return err
|
|
}
|