IsoTool   C
last analyzed

Complexity

Total Complexity 56

Size/Duplication

Total Lines 268
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 56
eloc 134
c 1
b 0
f 0
dl 0
loc 268
rs 5.5199

11 Methods

Rating   Name   Duplication   Size   Complexity  
B displayFiles() 0 32 9
A checkIsoFile() 0 8 3
C extractFiles() 0 43 13
A infoAction() 0 20 4
A parseCliArgs() 0 14 2
B extractAction() 0 21 7
A displayError() 0 3 1
A infoVolume() 0 23 3
A infoBoot() 0 5 1
C run() 0 44 12
A displayHelp() 0 14 1

How to fix   Complexity   

Complex Class

Complex classes like IsoTool often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use IsoTool, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
declare(strict_types=1);
4
5
namespace PhpIso\Cli;
6
7
use PhpIso\Descriptor;
8
use PhpIso\Descriptor\Boot;
9
use PhpIso\Descriptor\PrimaryVolume;
10
use PhpIso\Descriptor\SupplementaryVolume;
11
use PhpIso\Descriptor\Type;
12
use PhpIso\Descriptor\Volume;
13
use PhpIso\Exception;
14
use PhpIso\FileDirectory;
15
use PhpIso\IsoFile;
16
use PhpIso\PathTableRecord;
17
use Throwable;
18
19
class IsoTool
20
{
21
    public function run(): void
22
    {
23
        $options = $this->parseCliArgs();
24
25
        if ($options === []) {
26
            $this->displayHelp();
27
            exit(1);
28
        }
29
30
        $fileValue = $options['file'] ?? $options['f'];
31
32
        $file = '';
33
34
        if (is_array($fileValue)) {
35
            $file = current($fileValue);
36
        } elseif (is_string($fileValue)) {
37
            $file = $fileValue;
38
        }
39
40
        if (! is_string($file) || $file === '') {
41
            $this->displayError('Invalid value for file received');
42
            exit(2);
43
        }
44
45
        $extractPath = '';
46
        if (isset($options['extract']) && is_string($options['extract'])) {
47
            $extractPath = $options['extract'];
48
        } elseif (isset($options['x']) && is_string($options['x'])) {
49
            $extractPath = $options['x'];
50
        }
51
52
        echo 'Input ISO file: ' . $file . PHP_EOL;
53
54
        try {
55
            $this->checkIsoFile($file);
56
57
            if ($extractPath !== '') {
58
                $this->extractAction($file, $extractPath);
59
            } else {
60
                $this->infoAction($file);
61
            }
62
        } catch (Throwable $ex) {
63
            $this->displayError($ex->getMessage());
64
            exit(3);
65
        }
66
    }
67
68
    protected function checkIsoFile(string $file): void
69
    {
70
        if (! file_exists($file)) {
71
            throw new Exception('ISO file does not exist.');
72
        }
73
74
        if (! is_file($file)) {
75
            throw new Exception('Path is not a valid file.');
76
        }
77
    }
78
79
    protected function infoAction(string $file): void
80
    {
81
        $isoFile = new IsoFile($file);
82
83
        echo PHP_EOL;
84
85
        echo 'Number of descriptors: ' . count($isoFile->descriptors) . PHP_EOL;
86
87
        /** @var Descriptor $descriptor */
88
        foreach ($isoFile->descriptors as $descriptor) {
89
            echo '  - ' . $descriptor->name . PHP_EOL;
90
91
            if ($descriptor instanceof Volume) {
92
                $this->infoVolume($descriptor);
93
                $this->displayFiles($descriptor, $isoFile);
94
            } elseif ($descriptor instanceof Boot) {
95
                $this->infoBoot($descriptor);
96
            }
97
98
            echo PHP_EOL;
99
        }
100
    }
101
102
    protected function extractAction(string $file, string $extractPath): void
103
    {
104
        if (! is_dir($extractPath)) {
105
            $mkdirResult = mkdir($extractPath, 0777, true);
106
107
            if ($mkdirResult === false) {
108
                throw new Exception('Failed to create extract output directory: ' . $extractPath);
109
            }
110
        }
111
112
        $isoFile = new IsoFile($file);
113
114
        echo 'Extract ISO file to: ' . $extractPath . PHP_EOL;
115
116
        if (isset($isoFile->descriptors[Type::SUPPLEMENTARY_VOLUME_DESC]) && $isoFile->descriptors[Type::SUPPLEMENTARY_VOLUME_DESC] instanceof SupplementaryVolume) {
117
            $this->extractFiles($isoFile->descriptors[Type::SUPPLEMENTARY_VOLUME_DESC], $isoFile, $extractPath);
118
        } elseif (isset($isoFile->descriptors[Type::PRIMARY_VOLUME_DESC]) && $isoFile->descriptors[Type::PRIMARY_VOLUME_DESC] instanceof PrimaryVolume) {
119
            $this->extractFiles($isoFile->descriptors[Type::PRIMARY_VOLUME_DESC], $isoFile, $extractPath);
120
        }
121
122
        echo 'Extract finished!' . PHP_EOL;
123
    }
124
125
    protected function extractFiles(Volume $volumeDescriptor, IsoFile $isoFile, string $destinationDir): void
126
    {
127
        $pathTable = $volumeDescriptor->loadTable($isoFile);
128
129
        if ($pathTable === null) {
130
            return;
131
        }
132
133
        $destinationDir = rtrim($destinationDir, DIRECTORY_SEPARATOR);
134
135
        /** @var PathTableRecord $pathRecord */
136
        foreach ($pathTable as $pathRecord) {
137
            // check extents
138
            $extents = $pathRecord->loadExtents($isoFile, $volumeDescriptor->blockSize, ($volumeDescriptor->getType() === Type::SUPPLEMENTARY_VOLUME_DESC), $volumeDescriptor->jolietLevel);
139
140
            if ($extents !== false) {
141
                /** @var FileDirectory $extentRecord */
142
                foreach ($extents as $extentRecord) {
143
                    $path = $extentRecord->fileId;
144
145
                    if (! $extentRecord->isThis() && ! $extentRecord->isParent()) {
146
                        $fullPath = $destinationDir . $pathRecord->getFullPath($pathTable) . $path;
147
                        if ($extentRecord->isDirectory()) {
148
                            $fullPath .= DIRECTORY_SEPARATOR;
149
                        }
150
151
                        if (! $extentRecord->isDirectory()) {
152
                            $location = $extentRecord->location;
153
                            $dataLength = $extentRecord->dataLength;
154
                            echo $fullPath . ' (location: ' . $location . ') (length: ' . $dataLength . ')'  . PHP_EOL;
155
156
                            $dirPath = dirname($fullPath);
157
                            if (! is_dir($dirPath)) {
158
                                if (mkdir($dirPath, 0777, true) === false) {
159
                                    throw new Exception('Failed to create directory: ' . $dirPath);
160
                                }
161
                            }
162
163
                            $pathRecord->extractFile($isoFile, $volumeDescriptor->blockSize, $location, $dataLength, $fullPath);
164
                        } else {
165
                            if (! is_dir($fullPath)) {
166
                                if (mkdir($fullPath, 0777, true) === false) {
167
                                    throw new Exception('Failed to create directory: ' . $fullPath);
168
                                }
169
                            }
170
                        }
171
                    }
172
                }
173
            }
174
        }
175
    }
176
177
    protected function infoVolume(Volume $volumeDescriptor): void
178
    {
179
        echo '   - System ID: ' . $volumeDescriptor->systemId . PHP_EOL;
180
        echo '   - Volume ID: ' . $volumeDescriptor->volumeId . PHP_EOL;
181
        echo '   - App ID: ' . $volumeDescriptor->appId . PHP_EOL;
182
        echo '   - File Structure Version: ' . $volumeDescriptor->fileStructureVersion . PHP_EOL;
183
        echo '   - Volume Space Size: ' . $volumeDescriptor->volumeSpaceSize . PHP_EOL;
184
        echo '   - Volume Set Size: ' . $volumeDescriptor->volumeSetSize . PHP_EOL;
185
        echo '   - Volume SeqNum: ' . $volumeDescriptor->volumeSeqNum . PHP_EOL;
186
        echo '   - Block size: ' . $volumeDescriptor->blockSize . PHP_EOL;
187
        echo '   - Volume Set ID: ' . $volumeDescriptor->volumeSetId . PHP_EOL;
188
        echo '   - Publisher ID: ' . $volumeDescriptor->publisherId . PHP_EOL;
189
        echo '   - Preparer ID: ' . $volumeDescriptor->preparerId . PHP_EOL;
190
        echo '   - Copyright File ID: ' . $volumeDescriptor->copyrightFileId . PHP_EOL;
191
        echo '   - Abstract File ID: ' . $volumeDescriptor->abstractFileId . PHP_EOL;
192
        echo '   - Bibliographic File ID: ' . $volumeDescriptor->bibliographicFileId . PHP_EOL;
193
        echo '   - Creation Date: ' . $volumeDescriptor->creationDate?->toDateTimeString() . PHP_EOL;
194
        echo '   - Modification Date: ' . $volumeDescriptor->modificationDate?->toDateTimeString() . PHP_EOL;
195
        echo '   - Expiration Date: ' . $volumeDescriptor->expirationDate?->toDateTimeString() . PHP_EOL;
196
        echo '   - Effective Date: ' . $volumeDescriptor->effectiveDate?->toDateTimeString() . PHP_EOL;
197
198
        if ($volumeDescriptor instanceof SupplementaryVolume && $volumeDescriptor->jolietLevel !== 0) {
199
            echo '   - Joliet Level: ' . $volumeDescriptor->jolietLevel . PHP_EOL;
200
        }
201
    }
202
203
    protected function displayFiles(Volume $volumeDescriptor, IsoFile $isoFile): void
204
    {
205
        $pathTable = $volumeDescriptor->loadTable($isoFile);
206
207
        if ($pathTable === null) {
208
            return;
209
        }
210
211
        echo '   - Files:' . PHP_EOL;
212
213
        /** @var PathTableRecord $pathRecord */
214
        foreach ($pathTable as $pathRecord) {
215
            // check extents
216
            $extents = $pathRecord->loadExtents($isoFile, $volumeDescriptor->blockSize, ($volumeDescriptor->getType() === Type::SUPPLEMENTARY_VOLUME_DESC), $volumeDescriptor->jolietLevel);
217
218
            if ($extents !== false) {
219
                /** @var FileDirectory $extentRecord */
220
                foreach ($extents as $extentRecord) {
221
                    $path = $extentRecord->fileId;
222
223
                    if (! $extentRecord->isThis() && ! $extentRecord->isParent()) {
224
                        $fullPath = $pathRecord->getFullPath($pathTable) . $path;
225
                        if ($extentRecord->isDirectory()) {
226
                            $fullPath .= DIRECTORY_SEPARATOR;
227
                        }
228
229
                        if (! $extentRecord->isDirectory()) {
230
                            $location = $extentRecord->location;
231
                            $dataLength = $extentRecord->dataLength;
232
                            echo $fullPath . ' (location: ' . $location . ') (length: ' . $dataLength . ')'  . PHP_EOL;
233
                        } else {
234
                            echo $fullPath . PHP_EOL;
235
                        }
236
                    }
237
                }
238
            }
239
        }
240
    }
241
242
    protected function infoBoot(Boot $bootDescriptor): void
243
    {
244
        echo '   - Boot System ID: ' . $bootDescriptor->bootSysId . PHP_EOL;
245
        echo '   - Boot ID: ' . $bootDescriptor->bootId . PHP_EOL;
246
        echo '   - Boot Catalog Location: ' . $bootDescriptor->bootCatalogLocation . PHP_EOL;
247
    }
248
249
    /**
250
     * @return array<string, mixed>
251
     */
252
    protected function parseCliArgs(): array
253
    {
254
        $shortopts = 'f:x::';
255
        $longopts = [
256
            'file:',
257
            'extract::',
258
        ];
259
        $options = getopt($shortopts, $longopts, $restIndex);
260
261
        if ($options === false) {
262
            return [];
263
        }
264
265
        return $options;
266
    }
267
268
    protected function displayError(string $error): void
269
    {
270
        echo 'ERROR: ' . $error . PHP_EOL;
271
    }
272
273
    protected function displayHelp(): void
274
    {
275
        $help = '
276
Description:
277
  Tool to process ISO files
278
279
Usage:
280
  isotool [options] --file=<path>
281
282
Options:
283
  -f, --file                     Path for the ISO file (mandatory)
284
  -x, --extract=<extract_path>   Extract files in the given location
285
';
286
        echo $help;
287
    }
288
}
289