diff --git a/gdxsv/db.go b/gdxsv/db.go index b192418..cf02eaf 100644 --- a/gdxsv/db.go +++ b/gdxsv/db.go @@ -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"` @@ -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 { @@ -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"` @@ -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) @@ -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) } diff --git a/gdxsv/db_sqlite.go b/gdxsv/db_sqlite.go index a6793b5..66a6bca 100644 --- a/gdxsv/db_sqlite.go +++ b/gdxsv/db_sqlite.go @@ -4,6 +4,10 @@ import ( "context" "database/sql" "fmt" + "log" + "os" + "sort" + "strconv" "strings" "sync" "time" @@ -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, @@ -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, @@ -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() } @@ -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 } @@ -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) @@ -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 @@ -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 +} diff --git a/gdxsv/db_test.go b/gdxsv/db_test.go index 5cf866f..7fc4e32 100644 --- a/gdxsv/db_test.go +++ b/gdxsv/db_test.go @@ -54,9 +54,6 @@ func Test002GetInvalidAccount(t *testing.T) { func Test101RegisterUser(t *testing.T) { u, err := getDB().RegisterUser(testLoginKey) must(t, err) - if u == nil { - t.FailNow() - } assertEq(t, testLoginKey, u.LoginKey) assertEq(t, 6, len(u.UserID)) assertEq(t, 0, u.BattleCount) @@ -346,6 +343,173 @@ func Test300Ranking(t *testing.T) { assertEq(t, 3, titansRanking[2].Rank) } +func Test400Replay(t *testing.T) { + _, err := getDB().(SQLiteDB).Exec("DELETE FROM user") + must(t, err) + _, err = getDB().(SQLiteDB).Exec("DELETE FROM battle_record") + must(t, err) + + var users []*DBUser + for i := 0; i < 4; i++ { + ac, err := getDB().RegisterAccount("12.34.56.78") + must(t, err) + u, err := getDB().RegisterUser(ac.LoginKey) + must(t, err) + users = append(users, u) + } + + for _, br := range []*BattleRecord{ + { + Disk: "dc2", + BattleCode: "replaytest0", + UserID: users[0].UserID, + Players: 4, + Aggregate: 1, + Pos: 2, + Round: 10, + Win: 4, + Lose: 6, + Kill: 1000, + Death: 0, + Frame: 9999, + Result: "", + Team: 1, + System: 1, + ReplayURL: "example.com/replay1", + }, + { + Disk: "dc2", + BattleCode: "replaytest0", + UserID: users[1].UserID, + Players: 4, + Aggregate: 1, + Pos: 1, + Round: 10, + Win: 4, + Lose: 6, + Kill: 1000, + Death: 0, + Frame: 9999, + Result: "", + Team: 1, + System: 1, + ReplayURL: "example.com/replay1", + }, + { + Disk: "dc2", + BattleCode: "replaytest0", + UserID: users[2].UserID, + Players: 4, + Aggregate: 1, + Pos: 4, + Round: 10, + Win: 6, + Lose: 4, + Kill: 0, + Death: 1000, + Frame: 9999, + Result: "", + Team: 2, + System: 1, + ReplayURL: "example.com/replay1", + }, + { + Disk: "dc2", + BattleCode: "replaytest0", + UserID: users[3].UserID, + Players: 4, + Aggregate: 1, + Pos: 3, + Round: 10, + Win: 6, + Lose: 4, + Kill: 0, + Death: 1000, + Frame: 9999, + Result: "", + Team: 2, + System: 1, + ReplayURL: "example.com/replay1", + }} { + err := getDB().AddBattleRecord(br) + must(t, err) + err = getDB().UpdateBattleRecord(br) + must(t, err) + } + + foundReplay, err := getDB().FindReplay(&FindReplayQuery{ + BattleCode: "replaytest0", + Aggregate: -1, + LobbyID: -1, + Players: -1, + Reverse: false, + Page: 0, + }) + must(t, err) + assertEq(t, 1, len(foundReplay)) + assertEq(t, "example.com/replay1", foundReplay[0].ReplayURL) + + foundReplay, err = getDB().FindReplay(&FindReplayQuery{ + BattleCode: "", + UserID: "ABC123", + Aggregate: -1, + LobbyID: -1, + Players: -1, + Reverse: false, + Page: 0, + }) + must(t, err) + assertEq(t, 0, len(foundReplay)) +} + +func Test450SetReplayURL(t *testing.T) { + err := getDB().AddBattleRecord(&BattleRecord{ + BattleCode: "Test450SetReplayURL", + UserID: "Test450", + }) + must(t, err) + + err = getDB().SetReplayURL("Test450SetReplayURL", "http://example.com/replay_test") + must(t, err) + + br, err := getDB().GetBattleRecordUser("Test450SetReplayURL", "Test450") + must(t, err) + + assertEq(t, "http://example.com/replay_test", br.ReplayURL) +} + +func Test460SetReplayURLBulk(t *testing.T) { + err := getDB().AddBattleRecord(&BattleRecord{ + Disk: "", + BattleCode: "Test460SetReplayURLBulk1", + UserID: "Test460", + }) + must(t, err) + + err = getDB().AddBattleRecord(&BattleRecord{ + Disk: "", + BattleCode: "Test460SetReplayURLBulk2", + UserID: "Test460", + }) + must(t, err) + + err = getDB().SetReplayURLBulk( + []string{"Test460SetReplayURLBulk1", "Test460SetReplayURLBulk2"}, + []string{"http://example.com/replay_test1", "http://example.com/replay_test2"}, + []string{"dc1", "dc2"}) + must(t, err) + + br, err := getDB().GetBattleRecordUser("Test460SetReplayURLBulk1", "Test460") + must(t, err) + assertEq(t, "http://example.com/replay_test1", br.ReplayURL) + assertEq(t, "dc1", br.Disk) + + br, err = getDB().GetBattleRecordUser("Test460SetReplayURLBulk2", "Test460") + must(t, err) + assertEq(t, "http://example.com/replay_test2", br.ReplayURL) + assertEq(t, "dc2", br.Disk) +} + func mustInsertDBAccount(a DBAccount) { db := getDB().(SQLiteDB) _, err := db.NamedExec(`INSERT INTO account @@ -403,6 +567,7 @@ VALUES (:battle_code, :user_id, :user_name, :pilot_name, + :disk, :lobby_id, :players, :aggregate, @@ -415,6 +580,7 @@ VALUES (:battle_code, :death, :frame, :result, + :replay_url, :created, :updated, :system)`, record) @@ -516,3 +682,17 @@ VALUES (:platform, panic(err) } } + +func TestNewFindReplayQuery(t *testing.T) { + x := NewFindReplayQuery() + assertEq(t, "", x.BattleCode) + assertEq(t, "", x.Disk) + assertEq(t, "", x.UserID) + assertEq(t, "", x.UserName) + assertEq(t, "", x.PilotName) + assertEq(t, -1, x.LobbyID) + assertEq(t, -1, x.Players) + assertEq(t, -1, x.Aggregate) + assertEq(t, false, x.Reverse) + assertEq(t, 0, x.Page) +} diff --git a/gdxsv/lbs_api.go b/gdxsv/lbs_api.go index 7ae48c4..4db9377 100644 --- a/gdxsv/lbs_api.go +++ b/gdxsv/lbs_api.go @@ -5,6 +5,7 @@ import ( "go.uber.org/zap" "golang.org/x/sync/singleflight" "net/http" + "strconv" "time" ) @@ -35,6 +36,8 @@ func (lbs *Lbs) RegisterHTTPHandlers() { } http.HandleFunc("/lbs/status", func(w http.ResponseWriter, r *http.Request) { + // Public API: get lobby status + type onlineUser struct { UserID string `json:"user_id,omitempty"` Name string `json:"name,omitempty"` @@ -130,7 +133,112 @@ func (lbs *Lbs) RegisterHTTPHandlers() { } }) + http.HandleFunc("/lbs/replay", func(w http.ResponseWriter, r *http.Request) { + // Public API: find replays + + if err := r.ParseForm(); err != nil { + w.WriteHeader(http.StatusBadRequest) + return + } + + var err error + q := NewFindReplayQuery() + q.BattleCode = r.FormValue("battle_code") + q.Disk = r.FormValue("disk") + q.UserID = r.FormValue("user_id") + q.UserName = r.FormValue("user_name") + q.PilotName = r.FormValue("pilot_name") + if r.FormValue("lobby_id") != "" { + if q.LobbyID, err = strconv.Atoi(r.FormValue("lobby_id")); err != nil { + http.Error(w, "invalid query", http.StatusBadRequest) + return + } + } + if r.FormValue("players") != "" { + if q.Players, err = strconv.Atoi(r.FormValue("players")); err != nil { + http.Error(w, "invalid query", http.StatusBadRequest) + return + } + } + if r.FormValue("aggregate") != "" { + if q.Aggregate, err = strconv.Atoi(r.FormValue("aggregate")); err != nil { + http.Error(w, "invalid query", http.StatusBadRequest) + return + } + } + if r.FormValue("reverse") != "" { + if reverse, err := strconv.Atoi(r.FormValue("reverse")); err != nil { + http.Error(w, "invalid query", http.StatusBadRequest) + return + } else { + q.Reverse = reverse == 1 + } + } + if r.FormValue("page") != "" { + if q.Page, err = strconv.Atoi(r.FormValue("page")); err != nil { + http.Error(w, "invalid query", http.StatusBadRequest) + return + } + } + + replays, err := getDB().FindReplay(q) + if err != nil { + http.Error(w, "server error", http.StatusInternalServerError) + return + } + + if len(replays) == 0 { + w.WriteHeader(http.StatusNoContent) + return + } + + w.Header().Set("Content-Type", "application/json") + err = json.NewEncoder(w).Encode(replays) + if err != nil { + logger.Error("JSON encode failed", zap.Error(err)) + } + }) + + http.HandleFunc("/ops/replay_uploaded", func(w http.ResponseWriter, r *http.Request) { + // Private API: Called when a replay is uploaded + + battleCode := r.URL.Query().Get("battle_code") + url := r.URL.Query().Get("url") + if battleCode == "" || url == "" { + http.Error(w, "", http.StatusBadRequest) + return + } + + resp, err := http.Head(url) + if err != nil { + logger.Warn("replay_uploaded: Head failure", zap.Error(err)) + http.Error(w, "", http.StatusBadRequest) + return + } + defer resp.Body.Close() + + if resp.StatusCode != 200 { + logger.Warn("replay_uploaded: Head Invalid status code", zap.Int("status", resp.StatusCode)) + http.Error(w, "", http.StatusBadRequest) + return + } + + if err := getDB().SetReplayURL(battleCode, url); err != nil { + logger.Warn("SetReplayURL failure", zap.Error(err)) + http.Error(w, "", http.StatusInternalServerError) + return + } + + w.WriteHeader(http.StatusOK) + _, err = w.Write([]byte("OK")) + if err != nil { + logger.Error("Write response failed", zap.Error(err)) + } + }) + http.HandleFunc("/ops/reload", func(w http.ResponseWriter, r *http.Request) { + // Private API: Reloads settings from database + lbs.Locked(func(lbs *Lbs) { lbs.reload = true }) diff --git a/gdxsv/lbs_battle.go b/gdxsv/lbs_battle.go index 25790b1..d8fbc26 100644 --- a/gdxsv/lbs_battle.go +++ b/gdxsv/lbs_battle.go @@ -9,6 +9,8 @@ import ( "go.uber.org/zap" ) +const BattleCodeLength = 13 + func genBattleCode() string { return fmt.Sprintf("%013d", time.Now().UnixNano()/1000000) } diff --git a/gdxsv/lbs_lobby.go b/gdxsv/lbs_lobby.go index 6647719..d4d44c9 100644 --- a/gdxsv/lbs_lobby.go +++ b/gdxsv/lbs_lobby.go @@ -777,11 +777,11 @@ func (l *LbsLobby) checkLobbyBattleStart(force bool) { participants := l.pickLobbyBattleParticipants() - for _, q := range participants { + for i, q := range participants { b.Add(q) q.Battle = b aggregate := 1 - if force || l.Rule.NoRanking == 1 { + if force || l.Rule.NoRanking == 1 || len(participants) != 4 { aggregate = 0 } err := getDB().AddBattleRecord(&BattleRecord{ @@ -793,6 +793,7 @@ func (l *LbsLobby) checkLobbyBattleStart(force bool) { Team: int(q.Team), Players: len(participants), Aggregate: aggregate, + Pos: i + 1, }) if err != nil { logger.Error("AddBattleRecord failed", zap.Error(err)) @@ -941,11 +942,11 @@ func (l *LbsLobby) checkRoomBattleStart() { return } - for _, q := range participants { + for i, q := range participants { b.Add(q) q.Battle = b aggregate := 1 - if l.Rule.NoRanking == 1 { + if l.Rule.NoRanking == 1 || len(participants) != 4 { aggregate = 0 } err := getDB().AddBattleRecord(&BattleRecord{ @@ -957,6 +958,7 @@ func (l *LbsLobby) checkRoomBattleStart() { Team: int(q.Team), Players: len(participants), Aggregate: aggregate, + Pos: i + 1, }) if err != nil { logger.Error("AddBattleRecord failed", zap.Error(err)) diff --git a/gdxsv/lbs_test.go b/gdxsv/lbs_test.go index 3eae4c5..298d949 100644 --- a/gdxsv/lbs_test.go +++ b/gdxsv/lbs_test.go @@ -334,11 +334,13 @@ func forceEnterLobby(t *testing.T, lbs *Lbs, cli *TestLbsClient, lobbyID uint16, p := lbs.FindPeer(cli.UserID) if p == nil { t.Fatal("user not found", cli.DBUser) + return } lobby := lbs.GetLobby(p.Platform, p.GameDisk, lobbyID) if lobby == nil { t.Fatal("lobby not found") + return } p.Team = team @@ -440,6 +442,7 @@ func TestLbs_RegisterBattleResult(t *testing.T) { p := lbs.FindPeer("TEST01") if p == nil { t.Fatal("peer not found") + return } lbs.RegisterBattleResult(p, &BattleResult{ diff --git a/gdxsv/main.go b/gdxsv/main.go index 9027036..f31c117 100644 --- a/gdxsv/main.go +++ b/gdxsv/main.go @@ -1,30 +1,28 @@ package main import ( + "bufio" "context" - "encoding/hex" - "encoding/json" "flag" "fmt" - "gdxsv/gdxsv/proto" "log" "math/rand" "net/http" _ "net/http/pprof" "os" "os/signal" + "path/filepath" "runtime" + "strings" "syscall" "time" - "google.golang.org/api/option" - pb "google.golang.org/protobuf/proto" - "cloud.google.com/go/profiler" "github.com/caarlos0/env" "github.com/jmoiron/sqlx" stackdriver "github.com/tommy351/zap-stackdriver" "go.uber.org/zap" + "google.golang.org/api/option" ) var ( @@ -99,8 +97,7 @@ Usage: gdxsv [lbs, mcs, initdb, migratedb] migratedb: Update database schema. It is supposed to run this command before you run updated gdxsv. - battlelog2json: Convert battle log file to json. - + update_replay_url: Update battle_record.replay_url in database from 'gsutil ls' result. Flags: `) @@ -324,27 +321,42 @@ func main() { } else { logger.Info("Migration done") } - case "battlelog2json": - b, err := os.ReadFile(args[1]) - if err != nil { - logger.Fatal("Failed to open log file", zap.Error(err)) - } - logfile := new(proto.BattleLogFile) - err = pb.Unmarshal(b, logfile) - if err != nil { - logger.Fatal("Failed to Unmarshal", zap.Error(err)) - } - for _, data := range logfile.BattleData { - fmt.Println(data.UserId, data.Seq, hex.EncodeToString(data.Body)) + case "update_replay_url": + prepareDB() + var battleCodes []string + var urls []string + var disks []string + + sc := bufio.NewScanner(os.Stdin) + sc.Split(bufio.ScanLines) + for sc.Scan() { + line := sc.Text() + if strings.HasPrefix(line, "gs://") && strings.HasSuffix(line, ".pb") { + path := strings.TrimPrefix(line, "gs://") + url := "https://storage.googleapis.com/" + path + + fileNameWithoutExt := strings.TrimSuffix(filepath.Base(path), ".pb") + if len(fileNameWithoutExt) == BattleCodeLength { + battleCodes = append(battleCodes, fileNameWithoutExt) + urls = append(urls, url) + disks = append(disks, "") + } else if strings.Contains(fileNameWithoutExt, "-") { + sp := strings.SplitN(fileNameWithoutExt, "-", 2) + if len(sp) == 2 && len(sp[1]) == BattleCodeLength { + battleCodes = append(battleCodes, sp[1]) + urls = append(urls, url) + disks = append(disks, strings.TrimPrefix(sp[0], "disk")) + } + } + } } - logfile.BattleData = nil - js, err := json.MarshalIndent(logfile, "", " ") + err := getDB().SetReplayURLBulk(battleCodes, urls, disks) if err != nil { - logger.Fatal("Failed to Unmarshal", zap.Error(err)) + logger.Error("SetReplayURLBulk failed:", zap.Error(err)) + } else { + logger.Info("SetReplayURLBulk done") } - fmt.Print(string(js)) - default: printUsage() os.Exit(1) diff --git a/infra/lbs/launch-lbs.sh b/infra/lbs/launch-lbs.sh index 9b3afba..1bb648a 100644 --- a/infra/lbs/launch-lbs.sh +++ b/infra/lbs/launch-lbs.sh @@ -28,4 +28,4 @@ if [[ ! -d $LATEST_TAG/bin ]]; then "$GDXSV_BIN" -prodlog migratedb >> /var/log/gdxsv-lbs.log 2>&1 fi -exec "$GDXSV_BIN" -notempban -prodlog -cprof=2 lbs >> /var/log/gdxsv-lbs.log 2>&1 \ No newline at end of file +exec "$GDXSV_BIN" -notempban -prodlog -cprof=2 lbs >> /var/log/gdxsv-lbs.log 2>&1