diff --git a/api/ServerStatistics.api b/api/ServerStatistics.api index b36baa4..e13ec24 100644 --- a/api/ServerStatistics.api +++ b/api/ServerStatistics.api @@ -180,12 +180,15 @@ type ( ) type ( - A2SQueryReq { + A2SQueryReqServer { ServerIP string `json:"serverIP"` // Server IP address ServerPort int `json:"serverPort"` // Server port Timeout int `json:"timeout,example=3000,default=3000"` // Timeout in milliseconds (default: 3000ms) } - A2SQueryResp { + A2SQueryReq { + Servers []A2SQueryReqServer `json:"servers"` // List of servers to query + } + A2SQueryRespServer { ServerName string `json:"serverName"` // Server name MapName string `json:"mapName"` // Current map name GameDirectory string `json:"gameDirectory"` // Game directory @@ -200,6 +203,9 @@ type ( VAC int `json:"vac"` // VAC status (0 for unsecured, 1 for secured) Version string `json:"version"` // Server version } + A2SQueryResp { + Results []A2SQueryRespServer `json:"results"` // List of query results for each server + } ) @server ( diff --git a/src/internal/logic/a2squerylogic.go b/src/internal/logic/a2squerylogic.go index 2c53aca..4a555cc 100644 --- a/src/internal/logic/a2squerylogic.go +++ b/src/internal/logic/a2squerylogic.go @@ -3,6 +3,7 @@ package logic import ( "context" "fmt" + "sync" "time" "src/internal/svc" @@ -28,6 +29,45 @@ func NewA2sQueryLogic(ctx context.Context, svcCtx *svc.ServiceContext) *A2sQuery } func (l *A2sQueryLogic) A2sQuery(req *types.A2SQueryReq) (resp *types.A2SQueryResp, err error) { + if len(req.Servers) == 0 { + return &types.A2SQueryResp{Results: []types.A2SQueryRespServer{}}, nil + } + + // Channel to collect results + results := make(chan types.A2SQueryRespServer, len(req.Servers)) + var wg sync.WaitGroup + + // Query each server concurrently + for _, server := range req.Servers { + wg.Add(1) + go func(srv types.A2SQueryReqServer) { + defer wg.Done() + + result := l.querySingleServer(srv) + results <- result + }(server) + } + + // Close channel when all goroutines are done + go func() { + wg.Wait() + close(results) + }() + + // Collect all results + resp = &types.A2SQueryResp{ + Results: make([]types.A2SQueryRespServer, 0, len(req.Servers)), + } + + for result := range results { + resp.Results = append(resp.Results, result) + } + + return resp, nil +} + +// querySingleServer performs A2S query for a single server +func (l *A2sQueryLogic) querySingleServer(req types.A2SQueryReqServer) types.A2SQueryRespServer { // Set default timeout if not provided timeout := time.Duration(req.Timeout) * time.Millisecond if req.Timeout <= 0 { @@ -37,16 +77,20 @@ func (l *A2sQueryLogic) A2sQuery(req *types.A2SQueryReq) (resp *types.A2SQueryRe // Create A2S client with timeout option client, err := a2s.NewClient(fmt.Sprintf("%s:%d", req.ServerIP, req.ServerPort), a2s.TimeoutOption(timeout)) if err != nil { - l.Logger.Errorf("Failed to create A2S client: %v", err) - return nil, err + l.Logger.Errorf("Failed to create A2S client for %s:%d: %v", req.ServerIP, req.ServerPort, err) + return types.A2SQueryRespServer{ + ServerName: fmt.Sprintf("Error: %v", err), + } } defer client.Close() // Query server info info, err := client.QueryInfo() if err != nil { - l.Logger.Errorf("Failed to query server info: %v", err) - return nil, err + l.Logger.Errorf("Failed to query server info for %s:%d: %v", req.ServerIP, req.ServerPort, err) + return types.A2SQueryRespServer{ + ServerName: fmt.Sprintf("Error: %v", err), + } } // Convert boolean values to int @@ -60,7 +104,7 @@ func (l *A2sQueryLogic) A2sQuery(req *types.A2SQueryReq) (resp *types.A2SQueryRe } // Map response to our types - resp = &types.A2SQueryResp{ + return types.A2SQueryRespServer{ ServerName: info.Name, MapName: info.Map, GameDirectory: info.Folder, @@ -75,6 +119,4 @@ func (l *A2sQueryLogic) A2sQuery(req *types.A2SQueryReq) (resp *types.A2SQueryRe VAC: vac, Version: info.Version, } - - return resp, nil } diff --git a/src/internal/types/types.go b/src/internal/types/types.go index d0d0065..d77a808 100644 --- a/src/internal/types/types.go +++ b/src/internal/types/types.go @@ -4,12 +4,20 @@ package types type A2SQueryReq struct { + Servers []A2SQueryReqServer `json:"servers"` // List of servers to query +} + +type A2SQueryReqServer struct { ServerIP string `json:"serverIP"` // Server IP address ServerPort int `json:"serverPort"` // Server port Timeout int `json:"timeout,example=3000,default=3000"` // Timeout in milliseconds (default: 3000ms) } type A2SQueryResp struct { + Results []A2SQueryRespServer `json:"results"` // List of query results for each server +} + +type A2SQueryRespServer struct { ServerName string `json:"serverName"` // Server name MapName string `json:"mapName"` // Current map name GameDirectory string `json:"gameDirectory"` // Game directory