Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
| Total | |
100.00% |
32 / 32 |
|
100.00% |
2 / 2 |
CRAP | |
100.00% |
1 / 1 |
| CaptureWorker | |
100.00% |
32 / 32 |
|
100.00% |
2 / 2 |
4 | |
100.00% |
1 / 1 |
| __construct | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| query | |
100.00% |
31 / 31 |
|
100.00% |
1 / 1 |
3 | |||
| 1 | <?php declare(strict_types=1); |
| 2 | |
| 3 | /** |
| 4 | * Clansuite Server Query |
| 5 | * |
| 6 | * SPDX-FileCopyrightText: 2003-2025 Jens A. Koch |
| 7 | * SPDX-License-Identifier: MIT |
| 8 | * |
| 9 | * For the full copyright and license information, please view |
| 10 | * the LICENSE file that was distributed with this source code. |
| 11 | */ |
| 12 | |
| 13 | namespace Clansuite\Capture\Worker; |
| 14 | |
| 15 | use function array_merge; |
| 16 | use function count; |
| 17 | use function usleep; |
| 18 | use Clansuite\ServerQuery\CSQuery; |
| 19 | use Clansuite\ServerQuery\Util\UdpClient; |
| 20 | |
| 21 | /** |
| 22 | * Worker class for performing server queries with timeout and retry logic. |
| 23 | */ |
| 24 | final readonly class CaptureWorker |
| 25 | { |
| 26 | /** |
| 27 | * Constructor. |
| 28 | */ |
| 29 | public function __construct( |
| 30 | private int $timeout = 5, |
| 31 | private int $maxRetries = 2 |
| 32 | ) { |
| 33 | } |
| 34 | |
| 35 | /** |
| 36 | * Query a game server and return the results. |
| 37 | * |
| 38 | * @param string $protocol The protocol name (e.g., 'source', 'quake3') |
| 39 | * @param string $ip Server IP address |
| 40 | * @param int $port Server port |
| 41 | * |
| 42 | * @return array{debug: array<mixed>, server_info: array<string,mixed>} Query results with debug info and server info |
| 43 | */ |
| 44 | public function query(string $protocol, string $ip, int $port): array |
| 45 | { |
| 46 | $factory = new CSQuery; |
| 47 | $server = $factory->createInstance($protocol, $ip, $port); |
| 48 | |
| 49 | // Set timeout on the UDP client to prevent hanging |
| 50 | $udpClient = new UdpClient; |
| 51 | $udpClient->setTimeout($this->timeout); |
| 52 | $server->setUdpClient($udpClient); |
| 53 | |
| 54 | // Initialize debug collection |
| 55 | $allDebug = []; |
| 56 | |
| 57 | // Run a single query (may block); worker's lifetime is controlled by the parent process |
| 58 | // First attempt: info + players |
| 59 | $server->query_server(true, true); |
| 60 | $allDebug = array_merge($allDebug, $server->debug); |
| 61 | |
| 62 | // If no players were returned, retry a players-only query a couple of times. |
| 63 | // The parent process controls the worker timeout, so these retries are bounded. |
| 64 | if (count($server->players) === 0) { |
| 65 | for ($i = 0; $i < $this->maxRetries; $i++) { |
| 66 | // try players-only (avoid re-fetching rules to be faster) |
| 67 | $server->query_server(true, false); |
| 68 | $allDebug = array_merge($allDebug, $server->debug); |
| 69 | |
| 70 | // small backoff before retrying |
| 71 | usleep(200000); // 200ms |
| 72 | } |
| 73 | } |
| 74 | |
| 75 | return [ |
| 76 | 'debug' => $allDebug, |
| 77 | 'server_info' => [ |
| 78 | 'address' => $server->address, |
| 79 | 'queryport' => $server->queryport, |
| 80 | 'online' => $server->online, |
| 81 | 'gamename' => $server->gamename, |
| 82 | 'gameversion' => $server->gameversion, |
| 83 | 'servertitle' => $server->servertitle, |
| 84 | 'mapname' => $server->mapname, |
| 85 | 'gametype' => $server->gametype, |
| 86 | 'numplayers' => $server->numplayers, |
| 87 | 'maxplayers' => $server->maxplayers, |
| 88 | 'rules' => $server->rules, |
| 89 | 'players' => $server->players, |
| 90 | 'errstr' => $server->errstr, |
| 91 | ], |
| 92 | ]; |
| 93 | } |
| 94 | } |