Skip to content

Commit

Permalink
Fixes #8. Replace radix v2 with v4
Browse files Browse the repository at this point in the history
  • Loading branch information
erickskrauch committed Dec 14, 2023
1 parent d678f61 commit 883a7bd
Show file tree
Hide file tree
Showing 8 changed files with 184 additions and 220 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed
- Bumped Go version to 1.21.

### Removed
- StatsD metrics:
- Gauges:
- `ely.skinsystem.{hostname}.app.redis.pool.available`

## [4.6.0] - 2021-03-04
### Added
- `/profile/{username}` endpoint, which returns a profile and its textures, equivalent of the Mojang's
Expand Down
237 changes: 132 additions & 105 deletions db/redis/redis.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,58 +3,65 @@ package redis
import (
"bytes"
"compress/zlib"
"context"
"encoding/json"
"fmt"
"io"
"strconv"
"strings"
"time"

"github.com/mediocregopher/radix.v2/pool"
"github.com/mediocregopher/radix.v2/redis"
"github.com/mediocregopher/radix.v2/util"
"github.com/mediocregopher/radix/v4"

"github.com/elyby/chrly/model"
)

var now = time.Now

func New(addr string, poolSize int) (*Redis, error) {
conn, err := pool.New("tcp", addr, poolSize)
func New(ctx context.Context, addr string, poolSize int) (*Redis, error) {
client, err := (radix.PoolConfig{Size: poolSize}).New(ctx, "tcp", addr)
if err != nil {
return nil, err
}

return &Redis{
pool: conn,
client: client,
context: ctx,
}, nil
}

const accountIdToUsernameKey = "hash:username-to-account-id" // TODO: this should be actually "hash:user-id-to-username"
const mojangUsernameToUuidKey = "hash:mojang-username-to-uuid"

type Redis struct {
pool *pool.Pool
client radix.Client
context context.Context
}

func (db *Redis) FindSkinByUsername(username string) (*model.Skin, error) {
conn, err := db.pool.Get()
if err != nil {
return nil, err
}
defer db.pool.Put(conn)
var skin *model.Skin
err := db.client.Do(db.context, radix.WithConn("", func(ctx context.Context, conn radix.Conn) error {
var err error
skin, err = findByUsername(ctx, conn, username)

return err
}))

return findByUsername(username, conn)
return skin, err
}

func findByUsername(username string, conn util.Cmder) (*model.Skin, error) {
func findByUsername(ctx context.Context, conn radix.Conn, username string) (*model.Skin, error) {
redisKey := buildUsernameKey(username)
response := conn.Cmd("GET", redisKey)
if response.IsType(redis.Nil) {
var encodedResult []byte
err := conn.Do(ctx, radix.Cmd(&encodedResult, "GET", redisKey))
if err != nil {
return nil, err
}

if len(encodedResult) == 0 {
return nil, nil
}

encodedResult, _ := response.Bytes()
result, err := zlibDecode(encodedResult)
if err != nil {
return nil, err
Expand All @@ -72,102 +79,112 @@ func findByUsername(username string, conn util.Cmder) (*model.Skin, error) {
}

func (db *Redis) FindSkinByUserId(id int) (*model.Skin, error) {
conn, err := db.pool.Get()
if err != nil {
return nil, err
}
defer db.pool.Put(conn)
var skin *model.Skin
err := db.client.Do(db.context, radix.WithConn("", func(ctx context.Context, conn radix.Conn) error {
var err error
skin, err = findByUserId(ctx, conn, id)

return findByUserId(id, conn)
}
return err
}))

func findByUserId(id int, conn util.Cmder) (*model.Skin, error) {
response := conn.Cmd("HGET", accountIdToUsernameKey, id)
if response.IsType(redis.Nil) {
return nil, nil
}
return skin, err
}

username, err := response.Str()
func findByUserId(ctx context.Context, conn radix.Conn, id int) (*model.Skin, error) {
var username string
err := conn.Do(ctx, radix.FlatCmd(&username, "HGET", accountIdToUsernameKey, id))
if err != nil {
return nil, err
}

return findByUsername(username, conn)
if username == "" {
return nil, nil
}

return findByUsername(ctx, conn, username)
}

func (db *Redis) SaveSkin(skin *model.Skin) error {
conn, err := db.pool.Get()
return db.client.Do(db.context, radix.WithConn("", func(ctx context.Context, conn radix.Conn) error {
return save(ctx, conn, skin)
}))
}

func save(ctx context.Context, conn radix.Conn, skin *model.Skin) error {
err := conn.Do(ctx, radix.Cmd(nil, "MULTI"))
if err != nil {
return err
}
defer db.pool.Put(conn)

return save(skin, conn)
}

func save(skin *model.Skin, conn util.Cmder) error {
conn.Cmd("MULTI")

// If user has changed username, then we must delete his old username record
if skin.OldUsername != "" && skin.OldUsername != skin.Username {
conn.Cmd("DEL", buildUsernameKey(skin.OldUsername))
err = conn.Do(ctx, radix.Cmd(nil, "DEL", buildUsernameKey(skin.OldUsername)))
if err != nil {
return err
}
}

// If this is a new record or if the user has changed username, we set the value in the hash table
if skin.OldUsername != "" || skin.OldUsername != skin.Username {
conn.Cmd("HSET", accountIdToUsernameKey, skin.UserId, skin.Username)
err = conn.Do(ctx, radix.FlatCmd(nil, "HSET", accountIdToUsernameKey, skin.UserId, skin.Username))
}

str, _ := json.Marshal(skin)
conn.Cmd("SET", buildUsernameKey(skin.Username), zlibEncode(str))
err = conn.Do(ctx, radix.FlatCmd(nil, "SET", buildUsernameKey(skin.Username), zlibEncode(str)))
if err != nil {
return err
}

conn.Cmd("EXEC")
err = conn.Do(ctx, radix.Cmd(nil, "EXEC"))
if err != nil {
return err
}

skin.OldUsername = skin.Username

return nil
}

func (db *Redis) RemoveSkinByUserId(id int) error {
conn, err := db.pool.Get()
return db.client.Do(db.context, radix.WithConn("", func(ctx context.Context, conn radix.Conn) error {
return removeByUserId(ctx, conn, id)
}))
}

func removeByUserId(ctx context.Context, conn radix.Conn, id int) error {
record, err := findByUserId(ctx, conn, id)
if err != nil {
return err
}
defer db.pool.Put(conn)

return removeByUserId(id, conn)
}

func removeByUserId(id int, conn util.Cmder) error {
record, err := findByUserId(id, conn)
err = conn.Do(ctx, radix.Cmd(nil, "MULTI"))
if err != nil {
return err
}

conn.Cmd("MULTI")
err = conn.Do(ctx, radix.FlatCmd(nil, "HDEL", accountIdToUsernameKey, id))
if err != nil {
return err
}

conn.Cmd("HDEL", accountIdToUsernameKey, id)
if record != nil {
conn.Cmd("DEL", buildUsernameKey(record.Username))
err = conn.Do(ctx, radix.Cmd(nil, "DEL", buildUsernameKey(record.Username)))
if err != nil {
return err
}
}

conn.Cmd("EXEC")

return nil
return conn.Do(ctx, radix.Cmd(nil, "EXEC"))
}

func (db *Redis) RemoveSkinByUsername(username string) error {
conn, err := db.pool.Get()
if err != nil {
return err
}
defer db.pool.Put(conn)

return removeByUsername(username, conn)
return db.client.Do(db.context, radix.WithConn("", func(ctx context.Context, conn radix.Conn) error {
return removeByUsername(ctx, conn, username)
}))
}

func removeByUsername(username string, conn util.Cmder) error {
record, err := findByUsername(username, conn)
func removeByUsername(ctx context.Context, conn radix.Conn, username string) error {
record, err := findByUsername(ctx, conn, username)
if err != nil {
return err
}
Expand All @@ -176,82 +193,92 @@ func removeByUsername(username string, conn util.Cmder) error {
return nil
}

conn.Cmd("MULTI")
err = conn.Do(ctx, radix.Cmd(nil, "MULTI"))
if err != nil {
return err
}

conn.Cmd("DEL", buildUsernameKey(record.Username))
conn.Cmd("HDEL", accountIdToUsernameKey, record.UserId)
err = conn.Do(ctx, radix.Cmd(nil, "DEL", buildUsernameKey(record.Username)))
if err != nil {
return err
}

conn.Cmd("EXEC")
err = conn.Do(ctx, radix.FlatCmd(nil, "HDEL", accountIdToUsernameKey, record.UserId))
if err != nil {
return err
}

return nil
return conn.Do(ctx, radix.Cmd(nil, "EXEC"))
}

func (db *Redis) GetUuid(username string) (string, bool, error) {
conn, err := db.pool.Get()
if err != nil {
return "", false, err
}
defer db.pool.Put(conn)
var uuid string
var found bool
err := db.client.Do(db.context, radix.WithConn("", func(ctx context.Context, conn radix.Conn) error {
var err error
uuid, found, err = findMojangUuidByUsername(ctx, conn, username)

return findMojangUuidByUsername(username, conn)
return err
}))

return uuid, found, err
}

func findMojangUuidByUsername(username string, conn util.Cmder) (string, bool, error) {
func findMojangUuidByUsername(ctx context.Context, conn radix.Conn, username string) (string, bool, error) {
key := strings.ToLower(username)
response := conn.Cmd("HGET", mojangUsernameToUuidKey, key)
if response.IsType(redis.Nil) {
var result string
err := conn.Do(ctx, radix.Cmd(&result, "HGET", mojangUsernameToUuidKey, key))
if err != nil {
return "", false, err
}

if result == "" {
return "", false, nil
}

data, _ := response.Str()
parts := strings.Split(data, ":")
parts := strings.Split(result, ":")
// https://github.com/elyby/chrly/issues/28
if len(parts) < 2 {
conn.Cmd("HDEL", mojangUsernameToUuidKey, key)
return "", false, fmt.Errorf("Got unexpected response from the mojangUsernameToUuid hash: \"%s\"", data)
err = conn.Do(ctx, radix.Cmd(nil, "HDEL", mojangUsernameToUuidKey, key))
if err != nil {
return "", false, err
}

return "", false, fmt.Errorf("got unexpected response from the mojangUsernameToUuid hash: \"%s\"", result)
}

timestamp, _ := strconv.ParseInt(parts[1], 10, 64)
storedAt := time.Unix(timestamp, 0)
if storedAt.Add(time.Hour * 24 * 30).Before(now()) {
conn.Cmd("HDEL", mojangUsernameToUuidKey, key)
err = conn.Do(ctx, radix.Cmd(nil, "HDEL", mojangUsernameToUuidKey, key))
if err != nil {
return "", false, err
}

return "", false, nil
}

return parts[0], true, nil
}

func (db *Redis) StoreUuid(username string, uuid string) error {
conn, err := db.pool.Get()
if err != nil {
return err
}
defer db.pool.Put(conn)

return storeMojangUuid(username, uuid, conn)
return db.client.Do(db.context, radix.WithConn("", func(ctx context.Context, conn radix.Conn) error {
return storeMojangUuid(ctx, conn, username, uuid)
}))
}

func storeMojangUuid(username string, uuid string, conn util.Cmder) error {
func storeMojangUuid(ctx context.Context, conn radix.Conn, username string, uuid string) error {
value := uuid + ":" + strconv.FormatInt(now().Unix(), 10)
res := conn.Cmd("HSET", mojangUsernameToUuidKey, strings.ToLower(username), value)
if res.IsType(redis.Err) {
return res.Err
err := conn.Do(ctx, radix.Cmd(nil, "HSET", mojangUsernameToUuidKey, strings.ToLower(username), value))
if err != nil {
return err
}

return nil
}

func (db *Redis) Ping() error {
r := db.pool.Cmd("PING")
if r.Err != nil {
return r.Err
}

return nil
}

func (db *Redis) Avail() int {
return db.pool.Avail()
return db.client.Do(db.context, radix.Cmd(nil, "PING"))
}

func buildUsernameKey(username string) string {
Expand Down
Loading

0 comments on commit 883a7bd

Please sign in to comment.