Skip to content

Commit

Permalink
Merge pull request #245 from inada-s/replay-search
Browse files Browse the repository at this point in the history
Replay search api
  • Loading branch information
inada-s authored May 12, 2023
2 parents ac45325 + cdf4921 commit 0f8370d
Show file tree
Hide file tree
Showing 9 changed files with 620 additions and 39 deletions.
57 changes: 54 additions & 3 deletions gdxsv/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ type BattleRecord struct {
UserID string `db:"user_id" json:"user_id,omitempty"`
UserName string `db:"user_name" json:"user_name,omitempty"`
PilotName string `db:"pilot_name" json:"pilot_name,omitempty"`
Disk string `db:"disk" json:"disk,omitempty"`
LobbyID int `db:"lobby_id" json:"lobby_id,omitempty"`
Players int `db:"players" json:"players,omitempty"`
Aggregate int `db:"aggregate" json:"aggregate,omitempty"`
Expand All @@ -89,9 +90,10 @@ type BattleRecord struct {
Frame int `db:"frame" json:"frame,omitempty"`
Result string `db:"result" json:"result,omitempty"`

Created time.Time `db:"created" json:"created,omitempty"`
Updated time.Time `db:"updated" json:"updated,omitempty"`
System uint32 `db:"system" json:"system,omitempty"`
Created time.Time `db:"created" json:"created,omitempty"`
Updated time.Time `db:"updated" json:"updated,omitempty"`
System uint32 `db:"system" json:"system,omitempty"`
ReplayURL string `db:"replay_url" json:"replay_url,omitempty"`
}

type BattleCountResult struct {
Expand All @@ -107,6 +109,46 @@ type RankingRecord struct {
DBUser
}

type FindReplayQuery struct {
BattleCode string `db:"battle_code" json:"battle_code"`
Disk string `db:"disk" json:"disk"`
UserID string `db:"user_id" json:"user_id"`
UserName string `db:"user_name" json:"user_name"`
PilotName string `db:"pilot_name" json:"pilot_name"`
LobbyID int `db:"lobby_id" json:"lobby_id"`
Players int `db:"players" json:"players"`
Aggregate int `db:"aggregate" json:"aggregate"`
Reverse bool `db:"reverse" json:"reverse"`
Page int `db:"page" json:"page"`
}

func NewFindReplayQuery() *FindReplayQuery {
return &FindReplayQuery{
LobbyID: -1,
Players: -1,
Aggregate: -1,
}
}

type ReplayUser struct {
UserID string `json:"user_id"`
UserName string `json:"user_name"`
PilotName string `json:"pilot_name"`
Team int `json:"team"`
Pos int `json:"pos"`
}

type FoundReplay struct {
BattleCode string `json:"battle_code,omitempty"`
Disk string `json:"disk,omitempty"`
Users []*ReplayUser `json:"users,omitempty"`
Round int `json:"round,omitempty"`
RenpoWin int `json:"renpo_win,omitempty"`
ZeonWin int `json:"zeon_win,omitempty"`
StartAt int64 `json:"start_at,omitempty"`
ReplayURL string `json:"replay_url,omitempty"`
}

type MLobbySetting struct {
Platform string `db:"platform" json:"platform"`
Disk string `db:"disk" json:"disk"`
Expand Down Expand Up @@ -207,6 +249,12 @@ type DB interface {
// GetBattleRecordUser load a battle record by battle_code and user_id.
GetBattleRecordUser(battleCode string, userID string) (*BattleRecord, error)

// SetReplayURL updates battle_record to set replay_url.
SetReplayURL(battleCode string, url string) error

// SetReplayURLBulk updates battle_record to set replay_url.
SetReplayURLBulk(battleCodes, urls, disks []string) error

// ResetDailyBattleCount clears daily battle count of all users.
ResetDailyBattleCount() (err error)

Expand Down Expand Up @@ -243,4 +291,7 @@ type DB interface {

// GetPatch returns game patch.
GetPatch(platform, disk, name string) (*MPatch, error)

// FindReplay returns list of FoundReplay filtered by Query.
FindReplay(q *FindReplayQuery) ([]*FoundReplay, error)
}
229 changes: 226 additions & 3 deletions gdxsv/db_sqlite.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ import (
"context"
"database/sql"
"fmt"
"log"
"os"
"sort"
"strconv"
"strings"
"sync"
"time"
Expand Down Expand Up @@ -84,6 +88,7 @@ CREATE TABLE IF NOT EXISTS battle_record
user_id text,
user_name text,
pilot_name text,
disk text,
lobby_id integer,
players integer default 0,
aggregate integer default 0,
Expand All @@ -96,6 +101,7 @@ CREATE TABLE IF NOT EXISTS battle_record
death integer default 0,
frame integer default 0,
result text default '',
replay_url text default '',
created timestamp,
updated timestamp,
system integer default 0,
Expand Down Expand Up @@ -279,6 +285,42 @@ func (db SQLiteDB) Migrate() error {
}
}

if os.Getenv("MIGRATE_202305") != "" {
_, err = tx.Exec("UPDATE battle_record SET disk = 'dc2' WHERE disk IS NULL")
if err != nil {
_ = tx.Rollback()
return errors.Wrap(err, "set disk dc2 failure")
}

_, err = tx.Exec("UPDATE battle_record SET lobby_id = 0 WHERE lobby_id IS NULL")
if err != nil {
_ = tx.Rollback()
return errors.Wrap(err, "set lobby_id = 0 failure")
}

_, err = tx.Exec(`
UPDATE battle_record SET pos = (
SELECT COUNT(*) + 1
FROM battle_record AS b2
WHERE b2.battle_code = battle_record.battle_code AND b2.created < battle_record.created
)
WHERE pos = 0`)
if err != nil {
_ = tx.Rollback()
return errors.Wrap(err, "set pos failure")
}

_, err = tx.Exec(`
UPDATE battle_record
SET pilot_name = substr(pilot_name, 1, length(pilot_name) - length(''))
WHERE pilot_name like '%' || X'00';
`)
if err != nil {
_ = tx.Rollback()
return errors.Wrap(err, "fix pilot name failure")
}
}

return tx.Commit()
}

Expand Down Expand Up @@ -454,9 +496,9 @@ func (db SQLiteDB) AddBattleRecord(battleRecord *BattleRecord) error {
battleRecord.Created = now
_, err := db.NamedExec(`
INSERT INTO battle_record
(battle_code, user_id, user_name, pilot_name, lobby_id, players, aggregate, pos, team, created, updated, system)
(disk, battle_code, user_id, user_name, pilot_name, lobby_id, players, aggregate, pos, team, created, updated, system, replay_url)
VALUES
(:battle_code, :user_id, :user_name, :pilot_name, :lobby_id, :players, :aggregate, :pos, :team, :created, :updated, :system)`,
(:disk, :battle_code, :user_id, :user_name, :pilot_name, :lobby_id, :players, :aggregate, :pos, :team, :created, :updated, :system, :replay_url)`,
battleRecord)
return err
}
Expand All @@ -474,7 +516,8 @@ SET
frame = :frame,
result = :result,
updated = :updated,
system = :system
system = :system,
replay_url = :replay_url
WHERE
battle_code = :battle_code AND user_id = :user_id`, battle)

Expand All @@ -491,6 +534,49 @@ func (db SQLiteDB) GetBattleRecordUser(battleCode string, userID string) (*Battl
return b, err
}

func (db SQLiteDB) SetReplayURL(battleCode string, url string) error {
_, err := db.Exec(`UPDATE battle_record SET replay_url = ? WHERE battle_code = ?`, url, battleCode)
return err
}

func (db SQLiteDB) SetReplayURLBulk(battleCodes, urls, disks []string) error {
if len(battleCodes) != len(urls) {
return errors.New("Invalid parameter length")
}

// begin tx
ctx := context.Background()
tx, err := db.BeginTx(ctx, &sql.TxOptions{Isolation: sql.LevelDefault})
if err != nil {
return errors.Wrap(err, "Begin failed")
}

for i := 0; i < len(battleCodes); i++ {
battleCode := battleCodes[i]
url := urls[i]
disk := ""
if i < len(disks) {
disk = disks[i]
}

if disk == "" {
_, err := tx.Exec(`UPDATE battle_record SET replay_url = ? WHERE battle_code = ?`, url, battleCode)
if err != nil {
_ = tx.Rollback()
return err
}
} else {
_, err := tx.Exec(`UPDATE battle_record SET replay_url = ?, disk = ? WHERE battle_code = ?`, url, disk, battleCode)
if err != nil {
_ = tx.Rollback()
return err
}
}
}

return tx.Commit()
}

func (db SQLiteDB) ResetDailyBattleCount() (err error) {
_, err = db.Exec(`
UPDATE
Expand Down Expand Up @@ -690,3 +776,140 @@ func (db SQLiteDB) GetPatch(platform, disk, name string) (*MPatch, error) {
}
return m, nil
}

func (db SQLiteDB) FindReplay(q *FindReplayQuery) ([]*FoundReplay, error) {
order := "DESC"
if q.Reverse {
order = "ASC"
}

rows, err := db.NamedQuery(`
SELECT
disk,
battle_code,
lobby_id,
players,
MAX(round) as round,
GROUP_CONCAT(pos, '/') AS pos_list,
GROUP_CONCAT(team, '/') AS team_list,
GROUP_CONCAT(win, '/') AS win_list,
GROUP_CONCAT(user_id, '/') AS user_id_list,
GROUP_CONCAT(user_name, '/') AS user_name_list,
GROUP_CONCAT(pilot_name, '/') AS pilot_name_list,
created AS start_at,
replay_url
FROM battle_record
WHERE battle_code IN (
SELECT DISTINCT
battle_code
FROM
battle_record
WHERE
replay_url != ''
AND (disk = :disk OR :disk = '')
AND (battle_code = :battle_code OR :battle_code = '')
AND (user_id = :user_id OR :user_id = '')
AND (user_name LIKE :user_name OR :user_name = '')
AND (pilot_name LIKE :pilot_name OR :pilot_name = '')
AND (lobby_id = :lobby_id OR :lobby_id = -1)
AND (players = :players OR :players = -1)
AND (aggregate = :aggregate OR :aggregate = -1)
ORDER BY created `+order+`
LIMIT 100 OFFSET (:page) * 100)
GROUP BY battle_code
`, q)
if err != nil {
return nil, err
}
defer rows.Close()

type sqlRow struct {
Disk string `db:"disk"`
BattleCode string `db:"battle_code"`
LobbyID int `db:"lobby_id"`
Players int `db:"players"`
Round int `db:"round"`
PosList string `db:"pos_list"`
TeamList string `db:"team_list"`
WinList string `db:"win_list"`
UserIDList string `db:"user_id_list"`
UserNameList string `db:"user_name_list"`
PilotNameList string `db:"pilot_name_list"`
StartAt time.Time `db:"start_at"`
ReplayURL string `db:"replay_url"`
}

var result []*FoundReplay
var r sqlRow
for rows.Next() {
err = rows.StructScan(&r)
if err != nil {
log.Println(err)
return nil, err
}
n := r.Players

replay := FoundReplay{}
replay.Round = r.Round
replay.Disk = r.Disk
replay.StartAt = r.StartAt.Unix()
replay.ReplayURL = r.ReplayURL

posList := strings.SplitN(r.PosList, "/", n)
teamList := strings.SplitN(r.TeamList, "/", n)
winList := strings.SplitN(r.WinList, "/", n)
userIDList := strings.SplitN(r.UserIDList, "/", n)
userNameList := strings.SplitN(r.UserNameList, "/", n)
pilotNameList := strings.SplitN(r.PilotNameList, "/", n)

if len(teamList) != n ||
len(winList) != n ||
len(userIDList) != n ||
len(userNameList) != n ||
len(pilotNameList) != n {
continue
}

for i := 0; i < n; i++ {
team, err := strconv.Atoi(teamList[i])
if err != nil {
return nil, err
}

pos, err := strconv.Atoi(posList[i])
if err != nil {
return nil, err
}

if team == TeamRenpo && replay.RenpoWin == 0 {
replay.RenpoWin, err = strconv.Atoi(winList[i])
if err != nil {
return nil, err
}
}

if team == TeamZeon && replay.ZeonWin == 0 {
replay.ZeonWin, err = strconv.Atoi(winList[i])
if err != nil {
return nil, err
}
}

replay.Users = append(replay.Users, &ReplayUser{
UserID: userIDList[i],
UserName: userNameList[i],
PilotName: pilotNameList[i],
Team: team,
Pos: pos,
})
}

sort.Slice(replay.Users, func(i, j int) bool {
return replay.Users[i].Pos < replay.Users[j].Pos
})

result = append(result, &replay)
}

return result, nil
}
Loading

0 comments on commit 0f8370d

Please sign in to comment.