Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
96.59% covered (success)
96.59%
85 / 88
76.92% covered (warning)
76.92%
10 / 13
CRAP
0.00% covered (danger)
0.00%
0 / 1
MaxFieldHelper
96.59% covered (success)
96.59%
85 / 88
76.92% covered (warning)
76.92%
10 / 13
38
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
 getList
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
4
 getParser
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
3
 getMaxField
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getLog
85.71% covered (warning)
85.71%
6 / 7
0.00% covered (danger)
0.00%
0 / 1
3.03
 filesFinished
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 framesDirCount
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 getMovieSize
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
4
 getMaxfieldVersion
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 parsePlanResults
100.00% covered (success)
100.00%
27 / 27
100.00% covered (success)
100.00%
1 / 1
9
 getPreviewImage
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 getWaypointCount
85.71% covered (warning)
85.71%
6 / 7
0.00% covered (danger)
0.00%
0 / 1
3.03
 getWaypointsIdMap
93.75% covered (success)
93.75%
15 / 16
0.00% covered (danger)
0.00%
0 / 1
4.00
1<?php
2
3declare(strict_types=1);
4
5namespace App\Service;
6
7use RuntimeException;
8use UnexpectedValueException;
9use InvalidArgumentException;
10use App\Type\WaypointMap;
11use DirectoryIterator;
12use Elkuku\MaxfieldParser\MaxfieldParser;
13use Elkuku\MaxfieldParser\Type\MaxField;
14use FilesystemIterator;
15use Symfony\Component\DependencyInjection\Attribute\Autowire;
16use Symfony\Component\Filesystem\Exception\FileNotFoundException;
17
18readonly class MaxFieldHelper
19{
20    private string $rootDir;
21
22    public function __construct(
23        #[Autowire('%kernel.project_dir%')] string $projectDir,
24        #[Autowire('%env(MAXFIELD_VERSION)%')] private int $maxfieldVersion
25    )
26    {
27        $this->rootDir = $projectDir.'/public/maxfields';
28    }
29
30    /**
31     * @return array<string>
32     */
33    public function getList(): array
34    {
35        $list = [];
36
37        foreach (new DirectoryIterator($this->rootDir) as $fileInfo) {
38            if (!$fileInfo->isDir()) {
39                continue;
40            }
41
42            if ($fileInfo->isDot()) {
43                continue;
44            }
45
46            $list[] = $fileInfo->getFilename();
47        }
48
49        sort($list);
50
51        return $list;
52    }
53
54    public function getParser(string $item = ''): MaxfieldParser
55    {
56        $dir = $item !== '' && $item !== '0' ? $this->rootDir.'/'.$item : $this->rootDir;
57
58        return new MaxfieldParser($dir);
59    }
60
61    public function getMaxField(string $item): MaxField
62    {
63        return $this->getParser()->parse($item);
64    }
65
66    public function getLog(string $item): bool|string
67    {
68        $path = $this->rootDir.'/'.$item.'/log.txt';
69
70        if (false === file_exists($path)) {
71            throw new FileNotFoundException();
72        }
73
74        $contents = file_get_contents($path);
75
76        if (false === $contents) {
77            throw new RuntimeException('Cannot read file: '.$path);
78        }
79
80        return str_replace($this->rootDir, '...', $contents);
81    }
82
83    public function filesFinished(string $item): bool
84    {
85        return file_exists($this->rootDir.sprintf('/%s/key_preparation.txt', $item));
86    }
87
88    public function framesDirCount(string $item): string
89    {
90        $path = $this->rootDir.sprintf('/%s/frames', $item);
91
92        return (is_dir($path))
93            ? (string)iterator_count(new FilesystemIterator($path))
94            : 'n/a';
95    }
96
97    public function getMovieSize(string $item): string
98    {
99        $path = $this->rootDir.sprintf('/%s/plan_movie.gif', $item);
100
101        if (file_exists($path)) {
102            $bytes = filesize($path);
103
104            if (false === $bytes || 0 === $bytes) {
105                return '0 B';
106            }
107
108            $sizes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB'];
109            $factor = (int)floor(log($bytes, 1024));
110            return round($bytes / 1024 ** $factor, 2).' '.$sizes[$factor];
111        }
112
113        return 'n/a';
114    }
115
116    public function getMaxfieldVersion(): int
117    {
118        return $this->maxfieldVersion;
119    }
120
121    /**
122     * Parse "Maxfield Plan Results" section from log.txt content.
123     * Returns associative array with keys: portals, links, fields, max_keys_needed,
124     * ap_from_portals, ap_from_links, ap_from_fields, total_ap
125     * or null if section not found.
126     */
127    public function parsePlanResults(string $logContent): ?array
128    {
129        $lines = explode("\n", $logContent);
130        $inResults = false;
131        $results = [];
132
133        $keyMap = [
134            'portals' => 'portals',
135            'links' => 'links',
136            'fields' => 'fields',
137            'max keys needed' => 'max_keys_needed',
138            'AP from portals' => 'ap_from_portals',
139            'AP from links' => 'ap_from_links',
140            'AP from fields' => 'ap_from_fields',
141            'TOTAL AP' => 'total_ap',
142        ];
143
144        foreach ($lines as $line) {
145            $trimmed = trim($line);
146
147            if (str_contains($trimmed, 'Maxfield Plan Results:')) {
148                $inResults = true;
149                continue;
150            }
151
152            if ($inResults && $trimmed === '===============================') {
153                break; // End of results section
154            }
155
156            if ($inResults && str_contains($trimmed, '=')) {
157                [$key, $value] = explode('=', $trimmed, 2);
158                $key = trim($key);
159                $value = trim($value);
160
161                if (array_key_exists($key, $keyMap)) {
162                    $results[$keyMap[$key]] = (int) $value;
163                }
164            }
165        }
166
167        return !empty($results) ? $results : null;
168    }
169
170    public function getPreviewImage(string $item): string
171    {
172        $path = $this->rootDir.sprintf('/%s/link_map.png', $item);
173        $webPath = sprintf('maxfields/%s/link_map.png', $item);
174
175        return file_exists($path) ? $webPath : '';
176    }
177
178    public function getWaypointCount(string $item): int
179    {
180        $path = $this->rootDir.sprintf('/%s/portals.txt', $item);
181
182        if (false === file_exists($path)) {
183            return 0;
184        }
185
186        $contents = file($path, FILE_IGNORE_NEW_LINES);
187
188        if (false === $contents) {
189            throw new UnexpectedValueException('Can not read file in '.$path);
190        }
191
192        return count($contents);
193    }
194
195    /**
196     * TODO Move this somewhere else...
197     *
198     * @return WaypointMap[]
199     */
200    public function getWaypointsIdMap(string $item): array
201    {
202        $path = $this->rootDir.sprintf('/%s/portals_id_map.csv', $item);
203        $map = [];
204        if (!file_exists($path)) {
205            throw new InvalidArgumentException('Can not open '.$path);
206        }
207
208        $handle = fopen($path, 'r');
209
210        if (false === $handle) {
211            throw new InvalidArgumentException('Can not open '.$path);
212        }
213
214        while (false !== ($data = fgetcsv($handle, 1000, ',', '"', escape: '\\'))) {
215            $waypoint = new WaypointMap();
216
217            $waypoint->mapNo = (int)$data[0];
218            $waypoint->dbId = (int)$data[1];
219            $waypoint->guid = (string)$data[2];
220            $waypoint->name = (string)$data[3];
221
222            $map[] = $waypoint;
223        }
224
225        fclose($handle);
226
227        return $map;
228    }
229}