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\Strategy;
14:
15: use function is_array;
16: use function is_bool;
17: use function is_int;
18: use function is_string;
19: use function time;
20: use Clansuite\Capture\CaptureResult;
21: use Clansuite\Capture\Protocol\ProtocolInterface;
22: use Clansuite\Capture\ServerAddress;
23: use Clansuite\Capture\ServerInfo;
24: use Clansuite\Capture\Worker\CaptureWorker;
25: use Override;
26: use Throwable;
27:
28: /**
29: * Capture strategy that uses worker processes to perform server queries asynchronously.
30: */
31: final 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: }
113: