Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
| Total | |
86.05% |
74 / 86 |
|
60.00% |
3 / 5 |
CRAP | |
0.00% |
0 / 1 |
| JsonFixtureStorage | |
86.05% |
74 / 86 |
|
60.00% |
3 / 5 |
77.83 | |
0.00% |
0 / 1 |
| __construct | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| save | |
100.00% |
11 / 11 |
|
100.00% |
1 / 1 |
2 | |||
| load | |
84.31% |
43 / 51 |
|
0.00% |
0 / 1 |
63.84 | |||
| listAll | |
80.00% |
16 / 20 |
|
0.00% |
0 / 1 |
9.65 | |||
| buildPath | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
1 | |||
| 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\Storage; |
| 14 | |
| 15 | use const GLOB_ONLYDIR; |
| 16 | use const JSON_PRETTY_PRINT; |
| 17 | use function base64_decode; |
| 18 | use function base64_encode; |
| 19 | use function dirname; |
| 20 | use function file_exists; |
| 21 | use function file_get_contents; |
| 22 | use function file_put_contents; |
| 23 | use function glob; |
| 24 | use function is_array; |
| 25 | use function is_bool; |
| 26 | use function is_dir; |
| 27 | use function is_int; |
| 28 | use function is_string; |
| 29 | use function json_decode; |
| 30 | use function json_encode; |
| 31 | use function mkdir; |
| 32 | use function serialize; |
| 33 | use function sprintf; |
| 34 | use function str_replace; |
| 35 | use function strtolower; |
| 36 | use function unserialize; |
| 37 | use Clansuite\Capture\CaptureResult; |
| 38 | use Clansuite\Capture\ServerInfo; |
| 39 | use Override; |
| 40 | |
| 41 | /** |
| 42 | * Stores and retrieves capture results as JSON fixtures for testing and development purposes. |
| 43 | */ |
| 44 | final readonly class JsonFixtureStorage implements FixtureStorageInterface |
| 45 | { |
| 46 | /** |
| 47 | * Constructor. |
| 48 | */ |
| 49 | public function __construct(private string $fixturesDir) |
| 50 | { |
| 51 | } |
| 52 | |
| 53 | /** |
| 54 | * save method. |
| 55 | */ |
| 56 | #[Override] |
| 57 | public function save(string $protocol, string $version, string $ip, int $port, CaptureResult $result): string |
| 58 | { |
| 59 | $path = $this->buildPath($protocol, $version, $ip, $port); |
| 60 | $dir = dirname($path); |
| 61 | |
| 62 | if (!is_dir($dir)) { |
| 63 | mkdir($dir, 0o755, true); |
| 64 | } |
| 65 | $data = [ |
| 66 | 'metadata' => $result->metadata, |
| 67 | 'packets' => base64_encode(serialize($result->rawPackets)), // Assuming rawPackets is array of packets |
| 68 | 'server_info' => $result->serverInfo->toArray(), |
| 69 | ]; |
| 70 | file_put_contents($path, json_encode($data, JSON_PRETTY_PRINT)); |
| 71 | |
| 72 | return $path; |
| 73 | } |
| 74 | |
| 75 | /** |
| 76 | * load method. |
| 77 | */ |
| 78 | #[Override] |
| 79 | public function load(string $protocol, string $version, string $ip, int $port): ?CaptureResult |
| 80 | { |
| 81 | $path = $this->buildPath($protocol, $version, $ip, $port); |
| 82 | |
| 83 | if (!file_exists($path)) { |
| 84 | return null; |
| 85 | } |
| 86 | |
| 87 | $contents = file_get_contents($path); |
| 88 | |
| 89 | if ($contents === false) { |
| 90 | return null; |
| 91 | } |
| 92 | |
| 93 | $data = json_decode($contents, true); |
| 94 | |
| 95 | if (!is_array($data)) { |
| 96 | return null; |
| 97 | } |
| 98 | |
| 99 | $serverInfoData = $data['server_info'] ?? null; |
| 100 | |
| 101 | if (!is_array($serverInfoData)) { |
| 102 | return null; |
| 103 | } |
| 104 | |
| 105 | $encodedPackets = $data['packets'] ?? null; |
| 106 | |
| 107 | if (!is_string($encodedPackets)) { |
| 108 | return null; |
| 109 | } |
| 110 | |
| 111 | $decodedPackets = base64_decode($encodedPackets, true); |
| 112 | |
| 113 | if ($decodedPackets === false) { |
| 114 | return null; |
| 115 | } |
| 116 | |
| 117 | $rawPackets = @unserialize($decodedPackets); |
| 118 | |
| 119 | if ($rawPackets === false && $decodedPackets !== serialize(false)) { |
| 120 | return null; |
| 121 | } |
| 122 | |
| 123 | $metadata = $data['metadata'] ?? []; |
| 124 | |
| 125 | if (!is_array($metadata)) { |
| 126 | $metadata = []; |
| 127 | } |
| 128 | |
| 129 | $serverInfoData = [ |
| 130 | 'address' => isset($serverInfoData['address']) && is_string($serverInfoData['address']) ? $serverInfoData['address'] : null, |
| 131 | 'queryport' => isset($serverInfoData['queryport']) && is_int($serverInfoData['queryport']) ? $serverInfoData['queryport'] : null, |
| 132 | 'online' => isset($serverInfoData['online']) && is_bool($serverInfoData['online']) ? $serverInfoData['online'] : false, |
| 133 | 'gamename' => isset($serverInfoData['gamename']) && is_string($serverInfoData['gamename']) ? $serverInfoData['gamename'] : null, |
| 134 | 'gameversion' => isset($serverInfoData['gameversion']) && is_string($serverInfoData['gameversion']) ? $serverInfoData['gameversion'] : null, |
| 135 | 'servertitle' => isset($serverInfoData['servertitle']) && is_string($serverInfoData['servertitle']) ? $serverInfoData['servertitle'] : null, |
| 136 | 'mapname' => isset($serverInfoData['mapname']) && is_string($serverInfoData['mapname']) ? $serverInfoData['mapname'] : null, |
| 137 | 'gametype' => isset($serverInfoData['gametype']) && is_string($serverInfoData['gametype']) ? $serverInfoData['gametype'] : null, |
| 138 | 'numplayers' => isset($serverInfoData['numplayers']) && is_int($serverInfoData['numplayers']) ? $serverInfoData['numplayers'] : 0, |
| 139 | 'maxplayers' => isset($serverInfoData['maxplayers']) && is_int($serverInfoData['maxplayers']) ? $serverInfoData['maxplayers'] : 0, |
| 140 | 'rules' => isset($serverInfoData['rules']) && is_array($serverInfoData['rules']) ? $serverInfoData['rules'] : [], |
| 141 | 'players' => isset($serverInfoData['players']) && is_array($serverInfoData['players']) ? $serverInfoData['players'] : [], |
| 142 | 'channels' => isset($serverInfoData['channels']) && is_array($serverInfoData['channels']) ? $serverInfoData['channels'] : [], |
| 143 | 'errstr' => isset($serverInfoData['errstr']) && is_string($serverInfoData['errstr']) ? $serverInfoData['errstr'] : null, |
| 144 | 'password' => isset($serverInfoData['password']) && is_bool($serverInfoData['password']) ? $serverInfoData['password'] : null, |
| 145 | 'name' => isset($serverInfoData['name']) && is_string($serverInfoData['name']) ? $serverInfoData['name'] : null, |
| 146 | 'map' => isset($serverInfoData['map']) && is_string($serverInfoData['map']) ? $serverInfoData['map'] : null, |
| 147 | 'players_current' => isset($serverInfoData['players_current']) && is_int($serverInfoData['players_current']) ? $serverInfoData['players_current'] : null, |
| 148 | 'players_max' => isset($serverInfoData['players_max']) && is_int($serverInfoData['players_max']) ? $serverInfoData['players_max'] : null, |
| 149 | 'version' => isset($serverInfoData['version']) && is_string($serverInfoData['version']) ? $serverInfoData['version'] : null, |
| 150 | 'motd' => isset($serverInfoData['motd']) && is_string($serverInfoData['motd']) ? $serverInfoData['motd'] : null, |
| 151 | ]; |
| 152 | |
| 153 | $serverInfo = new ServerInfo(...$serverInfoData); |
| 154 | |
| 155 | if (!is_array($rawPackets)) { |
| 156 | $rawPackets = []; |
| 157 | } |
| 158 | |
| 159 | return new CaptureResult($rawPackets, $serverInfo, $metadata); |
| 160 | } |
| 161 | |
| 162 | /** |
| 163 | * listAll method. |
| 164 | * |
| 165 | * @return array<mixed> |
| 166 | */ |
| 167 | #[Override] |
| 168 | public function listAll(): array |
| 169 | { |
| 170 | $captures = []; |
| 171 | $protocolDirs = glob($this->fixturesDir . '/*', GLOB_ONLYDIR); |
| 172 | |
| 173 | if ($protocolDirs === false) { |
| 174 | $protocolDirs = []; |
| 175 | } |
| 176 | |
| 177 | foreach ($protocolDirs as $protocolDir) { |
| 178 | $versionDirs = glob($protocolDir . '/*', GLOB_ONLYDIR); |
| 179 | |
| 180 | if ($versionDirs === false) { |
| 181 | $versionDirs = []; |
| 182 | } |
| 183 | |
| 184 | foreach ($versionDirs as $versionDir) { |
| 185 | $files = glob($versionDir . '/*.json'); |
| 186 | |
| 187 | if ($files === false) { |
| 188 | $files = []; |
| 189 | } |
| 190 | |
| 191 | foreach ($files as $file) { |
| 192 | $c = file_get_contents($file); |
| 193 | |
| 194 | if ($c === false) { |
| 195 | continue; |
| 196 | } |
| 197 | |
| 198 | $data = json_decode($c, true); |
| 199 | |
| 200 | if (is_array($data)) { |
| 201 | $captures[] = $data; |
| 202 | } |
| 203 | } |
| 204 | } |
| 205 | } |
| 206 | |
| 207 | return $captures; |
| 208 | } |
| 209 | |
| 210 | private function buildPath(string $protocol, string $version, string $ip, int $port): string |
| 211 | { |
| 212 | $normalizedIp = str_replace('.', '_', $ip); |
| 213 | $filename = sprintf('capture_%s_%d.json', $normalizedIp, $port); |
| 214 | |
| 215 | return $this->fixturesDir . '/' . strtolower($protocol) . '/' . $version . '/' . $filename; |
| 216 | } |
| 217 | } |