Skip to content

Commit

Permalink
Create demoparse pkg
Browse files Browse the repository at this point in the history
  • Loading branch information
leighmacdonald committed Dec 29, 2024
1 parent 8356c65 commit 3a96a70
Show file tree
Hide file tree
Showing 12 changed files with 515 additions and 245 deletions.
75 changes: 4 additions & 71 deletions internal/demo/demo_usecase.go
Original file line number Diff line number Diff line change
@@ -1,22 +1,18 @@
package demo

import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"io"
"log/slog"
"mime/multipart"
"net/http"
"os"
"strconv"
"strings"
"time"

"github.com/dustin/go-humanize"
"github.com/gin-gonic/gin"
"github.com/leighmacdonald/gbans/internal/domain"
"github.com/leighmacdonald/gbans/pkg/demoparse"
"github.com/leighmacdonald/gbans/pkg/fs"
"github.com/leighmacdonald/gbans/pkg/log"
"github.com/ricochet2200/go-disk-usage/du"
Expand Down Expand Up @@ -205,69 +201,6 @@ func (d demoUsecase) GetDemos(ctx context.Context) ([]domain.DemoFile, error) {
return d.repository.GetDemos(ctx)
}

func (d demoUsecase) SendAndParseDemo(ctx context.Context, path string) (*domain.DemoDetails, error) {
fileHandle, errDF := os.Open(path)
if errDF != nil {
return nil, errors.Join(errDF, domain.ErrDemoLoad)
}

content, errContent := io.ReadAll(fileHandle)
if errContent != nil {
return nil, errors.Join(errDF, domain.ErrDemoLoad)
}

info, errInfo := fileHandle.Stat()
if errInfo != nil {
return nil, errors.Join(errInfo, domain.ErrDemoLoad)
}

log.Closer(fileHandle)

body := new(bytes.Buffer)
writer := multipart.NewWriter(body)

part, errCreate := writer.CreateFormFile("file", info.Name())
if errCreate != nil {
return nil, errors.Join(errCreate, domain.ErrDemoLoad)
}

if _, err := part.Write(content); err != nil {
return nil, errors.Join(errCreate, domain.ErrDemoLoad)
}

if errClose := writer.Close(); errClose != nil {
return nil, errors.Join(errClose, domain.ErrDemoLoad)
}

req, errReq := http.NewRequestWithContext(ctx, http.MethodPost, d.config.Config().Demo.DemoParserURL, body)
if errReq != nil {
return nil, errors.Join(errReq, domain.ErrDemoLoad)
}
req.Header.Set("Content-Type", writer.FormDataContentType())

client := &http.Client{}
resp, errSend := client.Do(req)
if errSend != nil {
return nil, errors.Join(errSend, domain.ErrDemoLoad)
}

defer resp.Body.Close()

var demo domain.DemoDetails

// TODO remove this extra copy once this feature doesnt have much need for debugging/inspection.
rawBody, errRead := io.ReadAll(resp.Body)
if errRead != nil {
return nil, errors.Join(errRead, domain.ErrDemoLoad)
}

if errDecode := json.NewDecoder(bytes.NewReader(rawBody)).Decode(&demo); errDecode != nil {
return nil, errors.Join(errDecode, domain.ErrDemoLoad)
}

return &demo, nil
}

func (d demoUsecase) CreateFromAsset(ctx context.Context, asset domain.Asset, serverID int) (*domain.DemoFile, error) {
_, errGetServer := d.servers.Server(ctx, serverID)
if errGetServer != nil {
Expand All @@ -290,13 +223,13 @@ func (d demoUsecase) CreateFromAsset(ctx context.Context, asset domain.Asset, se
// TODO change this data shape as we have not needed this in a long time. Only keys the are used.
intStats := map[string]gin.H{}

demoDetail, errDetail := d.SendAndParseDemo(ctx, asset.LocalPath)
demoDetail, errDetail := demoparse.Submit(ctx, d.config.Config().Demo.DemoParserURL, asset.LocalPath)
if errDetail != nil {
return nil, errDetail
}

for key := range demoDetail.State.Users {
intStats[fmt.Sprintf("%d", key)] = gin.H{}
intStats[strconv.Itoa(key)] = gin.H{}
}

timeStr := fmt.Sprintf("%s-%s", namePartsAll[0], namePartsAll[1])
Expand Down
9 changes: 6 additions & 3 deletions internal/discord/discord_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -614,12 +614,15 @@ func (h discordService) makeOnLogs() func(context.Context, *discordgo.Session, *

for _, match := range matches {
status := ":x:"
if match.IsWinner {
status = ":white_check_mark:"

for _, player := range match.Players {
if player.SteamID == author.SteamID && match.Winner == player.Team {
status = ":white_check_mark:"
}
}

_, _ = matchesWriter.WriteString(fmt.Sprintf("%s [%s](%s) `%s` `%s`\n",
status, match.Title, h.config.ExtURL(match), match.MapName, match.TimeStart.Format(time.DateOnly)))
status, match.Title, h.config.ExtURLRaw("/match/%s", match.MatchID.String()), match.MapName, match.TimeStart.Format(time.DateOnly)))
}

return LogsMessage(count, matchesWriter.String()), nil
Expand Down
80 changes: 0 additions & 80 deletions internal/domain/demo.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@ package domain

import (
"context"
"github.com/leighmacdonald/gbans/pkg/logparse"
"github.com/leighmacdonald/steamid/v4/steamid"
"time"

"github.com/gin-gonic/gin"
Expand All @@ -18,7 +16,6 @@ type DemoUsecase interface {
GetDemos(ctx context.Context) ([]DemoFile, error)
CreateFromAsset(ctx context.Context, asset Asset, serverID int) (*DemoFile, error)
Cleanup(ctx context.Context)
SendAndParseDemo(ctx context.Context, path string) (*DemoDetails, error)
}

type DemoRepository interface {
Expand Down Expand Up @@ -63,80 +60,3 @@ type DemoInfo struct {
Title string
AssetID uuid.UUID
}

type DemoPlayer struct {
Classes map[logparse.PlayerClass]int `json:"classes"`
Name string `json:"name"`
UserID int `json:"userId"` //nolint:tagliatelle
SteamID steamid.SteamID `json:"steamId"` //nolint:tagliatelle
Team logparse.Team `json:"team"`
}

type DemoHeader struct {
DemoType string `json:"demo_type"`
Version int `json:"version"`
Protocol int `json:"protocol"`
Server string `json:"server"`
Nick string `json:"nick"`
Map string `json:"map"`
Game string `json:"game"`
Duration float64 `json:"duration"`
Ticks int `json:"ticks"`
Frames int `json:"frames"`
Signon int `json:"signon"`
}

type DemoWeaponDetail struct {
Kills int `json:"kills"`
Damage int `json:"damage"`
Shots int `json:"shots"`
Hits int `json:"hits"`
Backstabs int `json:"backstabs,`
Headshots int `json:"headshots"`
Airshots int `json:"airshots"`
}

type DemoPlayerSummaries struct {
Points int `json:"points"`
Kills int `json:"kills"`
Assists int `json:"assists"`
Deaths int `json:"deaths"`
BuildingsDestroyed int `json:"buildings_destroyed"`
Captures int `json:"captures"`
Defenses int `json:"defenses"`
Dominations int `json:"dominations"`
Revenges int `json:"revenges"`
Ubercharges int `json:"ubercharges"`
Headshots int `json:"headshots"`
Teleports int `json:"teleports"`
Healing int `json:"healing"`
Backstabs int `json:"backstabs"`
BonusPoints int `json:"bonus_points"`
Support int `json:"support"`
DamgageDealt int `json:"damgage_dealt"`
WeaponMap map[logparse.Weapon]DemoWeaponDetail `json:"weapon_map"`
}

type DemoChatMessage struct {
}

type DemoMatchSummary struct {
ScoreBlu int `json:"score_blu"`
ScoreRed int `json:"score_red"`
Chat []DemoChatMessage `json:"chat"`
}

type DemoRoundSummary struct {
}

type DemoState struct {
DemoPlayerSummaries map[int]DemoPlayerSummaries `json:"player_summaries"` //nolint:tagliatelle
Users map[int]DemoPlayer `json:"users"`
DemoMatchSummary DemoMatchSummary `json:"match_summary"` //nolint:tagliatelle
DemoRoundSummary DemoRoundSummary `json:"round_summary"`
}

type DemoDetails struct {
State DemoState `json:"state"`
Header DemoHeader `json:"header"`
}
1 change: 0 additions & 1 deletion internal/domain/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -166,5 +166,4 @@ var (
ErrOpenFile = errors.New("could not open output file")
ErrFrontendRoutes = errors.New("failed to initialize frontend asset routes")
ErrPathInvalid = errors.New("invalid path specified")
ErrDemoLoad = errors.New("could not load demo file")
)
39 changes: 17 additions & 22 deletions internal/domain/match.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,15 @@ import (
"time"

"github.com/gofrs/uuid/v5"
"github.com/leighmacdonald/gbans/pkg/demoparse"
"github.com/leighmacdonald/gbans/pkg/fp"
"github.com/leighmacdonald/gbans/pkg/logparse"
"github.com/leighmacdonald/steamid/v4/steamid"
"golang.org/x/exp/slices"
)

type MatchRepository interface {
Matches(ctx context.Context, opts MatchesQueryOpts) ([]MatchSummary, int64, error)
Matches(ctx context.Context, opts MatchesQueryOpts) ([]MatchResult, int64, error)
MatchGetByID(ctx context.Context, matchID uuid.UUID, match *MatchResult) error
MatchSave(ctx context.Context, match *logparse.Match, weaponMap fp.MutexMap[logparse.Weapon, int]) error
StatsPlayerClass(ctx context.Context, sid64 steamid.SteamID) (PlayerClassStatsCollection, error)
Expand All @@ -38,9 +39,9 @@ type MatchRepository interface {
GetMatchIDFromServerID(serverID int) (uuid.UUID, bool)
}
type MatchUsecase interface {
CreateFromDemo(ctx context.Context, serverID int, details DemoDetails) (MatchSummary, error)
CreateFromDemo(ctx context.Context, serverID int, details demoparse.Demo) (MatchResult, error)
GetMatchIDFromServerID(serverID int) (uuid.UUID, bool)
Matches(ctx context.Context, opts MatchesQueryOpts) ([]MatchSummary, int64, error)
Matches(ctx context.Context, opts MatchesQueryOpts) ([]MatchResult, int64, error)
MatchGetByID(ctx context.Context, matchID uuid.UUID, match *MatchResult) error
MatchSave(ctx context.Context, match *logparse.Match, weaponMap fp.MutexMap[logparse.Weapon, int]) error
StatsPlayerClass(ctx context.Context, sid64 steamid.SteamID) (PlayerClassStatsCollection, error)
Expand Down Expand Up @@ -167,6 +168,13 @@ type MatchWeapon struct {
MatchPlayerID int64 `json:"match_player_id"`
}

type MatchChat struct {
SteamID steamid.SteamID `json:"steam_id"`
PersonaName string `json:"persona_name"`
Body string `json:"body"`
Team bool `json:"team"`
}

type MatchResult struct {
MatchID uuid.UUID `json:"match_id"`
ServerID int `json:"server_id"`
Expand All @@ -177,7 +185,11 @@ type MatchResult struct {
TimeEnd time.Time `json:"time_end"`
Winner logparse.Team `json:"winner"`
Players []*MatchPlayer `json:"players"`
Chat PersonMessages `json:"chat"`
Chat []MatchChat `json:"chat"`
}

func (match *MatchResult) Path() string {
return "/log/" + match.MatchID.String()
}

func (match *MatchResult) TopPlayers() []*MatchPlayer {
Expand Down Expand Up @@ -424,7 +436,7 @@ type PlayerMedicStats struct {
type CommonPlayerStats struct {
SteamID steamid.SteamID `json:"steam_id"`
Name string `json:"name"`
AvatarHash string `json:"avatar_hash"` //todo make
AvatarHash string `json:"avatar_hash"` // todo make
Kills int `json:"kills"`
Assists int `json:"assists"`
Deaths int `json:"deaths"`
Expand Down Expand Up @@ -455,20 +467,3 @@ type PlayerStats struct {
MatchesWon int `json:"matches_won"`
PlayTime time.Duration `json:"play_time"`
}

type MatchSummary struct {
MatchID uuid.UUID `json:"match_id"`
ServerID int `json:"server_id"`
IsWinner bool `json:"is_winner"`
ShortName string `json:"short_name"`
Title string `json:"title"`
MapName string `json:"map_name"`
ScoreBlu int `json:"score_blu"`
ScoreRed int `json:"score_red"`
TimeStart time.Time `json:"time_start"`
TimeEnd time.Time `json:"time_end"`
}

func (m MatchSummary) Path() string {
return "/log/" + m.MatchID.String()
}
24 changes: 16 additions & 8 deletions internal/match/match_repository.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ func (r *matchRepository) GetMatchIDFromServerID(serverID int) (uuid.UUID, bool)
return r.matchUUIDMap.Get(serverID)
}

func (r *matchRepository) Matches(ctx context.Context, opts domain.MatchesQueryOpts) ([]domain.MatchSummary, int64, error) {
func (r *matchRepository) Matches(ctx context.Context, opts domain.MatchesQueryOpts) ([]domain.MatchResult, int64, error) {
countBuilder := r.database.
Builder().
Select("count(m.match_id) as count").
Expand Down Expand Up @@ -103,12 +103,14 @@ func (r *matchRepository) Matches(ctx context.Context, opts domain.MatchesQueryO

defer rows.Close()

var matches []domain.MatchSummary
var matches []domain.MatchResult

for rows.Next() {
var summary domain.MatchSummary
if errScan := rows.Scan(&summary.MatchID, &summary.ServerID, &summary.IsWinner, &summary.ShortName,
&summary.Title, &summary.MapName, &summary.ScoreBlu, &summary.ScoreRed, &summary.TimeStart,
var summary domain.MatchResult
var winner bool
var shortName string
if errScan := rows.Scan(&summary.MatchID, &summary.ServerID, &winner, &shortName,
&summary.Title, &summary.MapName, &summary.TeamScores.Blu, &summary.TeamScores.Red, &summary.TimeStart,
&summary.TimeEnd); errScan != nil {
return nil, 0, errors.Join(errScan, domain.ErrScanResult)
}
Expand Down Expand Up @@ -533,15 +535,21 @@ func (r *matchRepository) MatchGetByID(ctx context.Context, matchID uuid.UUID, m
}

chat, errChat := r.matchGetChat(ctx, matchID)

if errChat != nil && !errors.Is(errChat, domain.ErrNoResult) {
return errChat
}

match.Chat = chat
for _, msg := range chat {
match.Chat = append(match.Chat, domain.MatchChat{
SteamID: msg.SteamID,
PersonaName: msg.PersonaName,
Body: msg.Body,
Team: msg.Team,
})
}

if match.Chat == nil {
match.Chat = domain.PersonMessages{}
match.Chat = []domain.MatchChat{}
}

for _, player := range match.Players {
Expand Down
Loading

0 comments on commit 3a96a70

Please sign in to comment.