Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
32 / 32
100.00% covered (success)
100.00%
2 / 2
CRAP
100.00% covered (success)
100.00%
1 / 1
CaptureWorker
100.00% covered (success)
100.00%
32 / 32
100.00% covered (success)
100.00%
2 / 2
4
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 query
100.00% covered (success)
100.00%
31 / 31
100.00% covered (success)
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
13namespace Clansuite\Capture\Worker;
14
15use function array_merge;
16use function count;
17use function usleep;
18use Clansuite\ServerQuery\CSQuery;
19use Clansuite\ServerQuery\Util\UdpClient;
20
21/**
22 * Worker class for performing server queries with timeout and retry logic.
23 */
24final 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}