mirror of
https://github.com/elyby/chrly.git
synced 2024-12-23 05:30:06 +05:30
290 lines
6.0 KiB
Go
290 lines
6.0 KiB
Go
package redis
|
|
|
|
import (
|
|
"bytes"
|
|
"compress/zlib"
|
|
"encoding/json"
|
|
"io"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/getsentry/raven-go"
|
|
"github.com/mediocregopher/radix.v2/pool"
|
|
"github.com/mediocregopher/radix.v2/redis"
|
|
"github.com/mediocregopher/radix.v2/util"
|
|
|
|
"github.com/elyby/chrly/model"
|
|
)
|
|
|
|
var now = time.Now
|
|
|
|
func New(addr string, poolSize int) (*Redis, error) {
|
|
conn, err := pool.New("tcp", addr, poolSize)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &Redis{
|
|
pool: conn,
|
|
}, 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
|
|
}
|
|
|
|
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)
|
|
|
|
return findByUsername(username, conn)
|
|
}
|
|
|
|
func findByUsername(username string, conn util.Cmder) (*model.Skin, error) {
|
|
redisKey := buildUsernameKey(username)
|
|
response := conn.Cmd("GET", redisKey)
|
|
if response.IsType(redis.Nil) {
|
|
return nil, nil
|
|
}
|
|
|
|
encodedResult, _ := response.Bytes()
|
|
result, err := zlibDecode(encodedResult)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var skin *model.Skin
|
|
err = json.Unmarshal(result, &skin)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
skin.OldUsername = skin.Username
|
|
|
|
return skin, nil
|
|
}
|
|
|
|
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)
|
|
|
|
return findByUserId(id, conn)
|
|
}
|
|
|
|
func findByUserId(id int, conn util.Cmder) (*model.Skin, error) {
|
|
response := conn.Cmd("HGET", accountIdToUsernameKey, id)
|
|
if response.IsType(redis.Nil) {
|
|
return nil, nil
|
|
}
|
|
|
|
username, err := response.Str()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return findByUsername(username, conn)
|
|
}
|
|
|
|
func (db *Redis) SaveSkin(skin *model.Skin) error {
|
|
conn, err := db.pool.Get()
|
|
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))
|
|
}
|
|
|
|
// 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)
|
|
}
|
|
|
|
str, _ := json.Marshal(skin)
|
|
conn.Cmd("SET", buildUsernameKey(skin.Username), zlibEncode(str))
|
|
|
|
conn.Cmd("EXEC")
|
|
|
|
skin.OldUsername = skin.Username
|
|
|
|
return nil
|
|
}
|
|
|
|
func (db *Redis) RemoveSkinByUserId(id int) error {
|
|
conn, err := db.pool.Get()
|
|
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)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
conn.Cmd("MULTI")
|
|
|
|
conn.Cmd("HDEL", accountIdToUsernameKey, id)
|
|
if record != nil {
|
|
conn.Cmd("DEL", buildUsernameKey(record.Username))
|
|
}
|
|
|
|
conn.Cmd("EXEC")
|
|
|
|
return nil
|
|
}
|
|
|
|
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)
|
|
}
|
|
|
|
func removeByUsername(username string, conn util.Cmder) error {
|
|
record, err := findByUsername(username, conn)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if record == nil {
|
|
return nil
|
|
}
|
|
|
|
conn.Cmd("MULTI")
|
|
|
|
conn.Cmd("DEL", buildUsernameKey(record.Username))
|
|
conn.Cmd("HDEL", accountIdToUsernameKey, record.UserId)
|
|
|
|
conn.Cmd("EXEC")
|
|
|
|
return nil
|
|
}
|
|
|
|
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)
|
|
|
|
return findMojangUuidByUsername(username, conn)
|
|
}
|
|
|
|
func findMojangUuidByUsername(username string, conn util.Cmder) (string, bool, error) {
|
|
key := strings.ToLower(username)
|
|
response := conn.Cmd("HGET", mojangUsernameToUuidKey, key)
|
|
if response.IsType(redis.Nil) {
|
|
return "", false, nil
|
|
}
|
|
|
|
data, _ := response.Str()
|
|
parts := strings.Split(data, ":")
|
|
// Temporary debug statement to investigate https://github.com/elyby/chrly/issues/28
|
|
if len(parts) < 2 {
|
|
raven.Capture(raven.NewPacketWithExtra(
|
|
"mojangUsernameToUuid hash contains corrupted data",
|
|
raven.Extra{
|
|
"rawValue": "hello world",
|
|
"username": "this is username",
|
|
},
|
|
), map[string]string{})
|
|
|
|
return "", false, nil
|
|
}
|
|
|
|
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)
|
|
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)
|
|
}
|
|
|
|
func storeMojangUuid(username string, uuid string, conn util.Cmder) 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
|
|
}
|
|
|
|
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()
|
|
}
|
|
|
|
func buildUsernameKey(username string) string {
|
|
return "username:" + strings.ToLower(username)
|
|
}
|
|
|
|
func zlibEncode(str []byte) []byte {
|
|
var buff bytes.Buffer
|
|
writer := zlib.NewWriter(&buff)
|
|
_, _ = writer.Write(str)
|
|
_ = writer.Close()
|
|
|
|
return buff.Bytes()
|
|
}
|
|
|
|
func zlibDecode(bts []byte) ([]byte, error) {
|
|
buff := bytes.NewReader(bts)
|
|
reader, err := zlib.NewReader(buff)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
resultBuffer := new(bytes.Buffer)
|
|
_, _ = io.Copy(resultBuffer, reader)
|
|
_ = reader.Close()
|
|
|
|
return resultBuffer.Bytes(), nil
|
|
}
|