Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
63.83% covered (warning)
63.83%
30 / 47
50.00% covered (danger)
50.00%
1 / 2
CRAP
0.00% covered (danger)
0.00%
0 / 1
WorkerCaptureStrategy
63.83% covered (warning)
63.83%
30 / 47
50.00% covered (danger)
50.00%
1 / 2
80.46
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 capture
63.04% covered (warning)
63.04%
29 / 46
0.00% covered (danger)
0.00%
0 / 1
79.51
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\Strategy;
14
15use function is_array;
16use function is_bool;
17use function is_int;
18use function is_string;
19use function time;
20use Clansuite\Capture\CaptureResult;
21use Clansuite\Capture\Protocol\ProtocolInterface;
22use Clansuite\Capture\ServerAddress;
23use Clansuite\Capture\ServerInfo;
24use Clansuite\Capture\Worker\CaptureWorker;
25use Override;
26use Throwable;
27
28/**
29 * Capture strategy that uses worker processes to perform server queries asynchronously.
30 */
31final readonly class WorkerCaptureStrategy implements CaptureStrategyInterface
32{
33    /**
34     * Constructor.
35     */
36    public function __construct(private int $timeout = 10)
37    {
38    }
39
40    /**
41     * capture method.
42     *
43     * @param array<mixed> $options
44     */
45    #[Override]
46    public function capture(ProtocolInterface $protocol, ServerAddress $addr, array $options): CaptureResult
47    {
48        $protocolName = isset($options['protocol_name']) && is_string($options['protocol_name']) ? $options['protocol_name'] : 'source'; // fallback to source if not provided
49
50        // Create worker instance with timeout configuration
51        $worker = new CaptureWorker($this->timeout);
52
53        try {
54            // Query the server directly using the worker
55            /** @var array{debug: array<mixed>, server_info: array<string, mixed>} $workerData */
56            $workerData = $worker->query($protocolName, $addr->ip, $addr->port);
57
58            $serverInfoData = $workerData['server_info'];
59
60            $serverInfo = new ServerInfo(
61                address: isset($serverInfoData['address']) && is_string($serverInfoData['address']) ? $serverInfoData['address'] : null,
62                queryport: isset($serverInfoData['queryport']) && is_int($serverInfoData['queryport']) ? $serverInfoData['queryport'] : null,
63                online: isset($serverInfoData['online']) && is_bool($serverInfoData['online']) ? $serverInfoData['online'] : false,
64                gamename: isset($serverInfoData['gamename']) && is_string($serverInfoData['gamename']) ? $serverInfoData['gamename'] : null,
65                gameversion: isset($serverInfoData['gameversion']) && is_string($serverInfoData['gameversion']) ? $serverInfoData['gameversion'] : null,
66                servertitle: isset($serverInfoData['servertitle']) && is_string($serverInfoData['servertitle']) ? $serverInfoData['servertitle'] : null,
67                mapname: isset($serverInfoData['mapname']) && is_string($serverInfoData['mapname']) ? $serverInfoData['mapname'] : null,
68                gametype: isset($serverInfoData['gametype']) && is_string($serverInfoData['gametype']) ? $serverInfoData['gametype'] : null,
69                numplayers: isset($serverInfoData['numplayers']) && is_int($serverInfoData['numplayers']) ? $serverInfoData['numplayers'] : 0,
70                maxplayers: isset($serverInfoData['maxplayers']) && is_int($serverInfoData['maxplayers']) ? $serverInfoData['maxplayers'] : 0,
71                rules: isset($serverInfoData['rules']) && is_array($serverInfoData['rules']) ? $serverInfoData['rules'] : [],
72                players: isset($serverInfoData['players']) && is_array($serverInfoData['players']) ? $serverInfoData['players'] : [],
73                errstr: isset($serverInfoData['errstr']) && is_string($serverInfoData['errstr']) ? $serverInfoData['errstr'] : null,
74            );
75
76            $debugLog = $workerData['debug'];
77
78            if (!is_array($debugLog)) {
79                $debugLog = [];
80            }
81
82            $metadata = [
83                'ip'          => $addr->ip,
84                'port'        => $addr->port,
85                'protocol'    => $protocol->getProtocolName(),
86                'timestamp'   => time(),
87                'worker_used' => true,
88            ];
89
90            return new CaptureResult($debugLog, $serverInfo, $metadata);
91        } catch (Throwable $e) {
92            // If worker fails, return a failed result
93            $serverInfo = new ServerInfo(
94                address: $addr->ip,
95                queryport: $addr->port,
96                online: false,
97                errstr: $e->getMessage(),
98            );
99
100            $metadata = [
101                'ip'          => $addr->ip,
102                'port'        => $addr->port,
103                'protocol'    => $protocol->getProtocolName(),
104                'timestamp'   => time(),
105                'worker_used' => true,
106                'error'       => $e->getMessage(),
107            ];
108
109            return new CaptureResult([], $serverInfo, $metadata);
110        }
111    }
112}