Passed
Push — main ( 322a86...2c83d5 )
by Siad
06:11
created

FileUtils::copyFile()   D

Complexity

Conditions 19
Paths 121

Size

Total Lines 65
Code Lines 27

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 25
CRAP Score 19.0206

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 27
c 1
b 0
f 0
dl 0
loc 65
ccs 25
cts 26
cp 0.9615
rs 4.3416
cc 19
nc 121
nop 9
crap 19.0206

How to fix   Long Method    Complexity    Many Parameters   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php
2
/**
3
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
4
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
5
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
6
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
7
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
8
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
9
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
10
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
11
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
12
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
13
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
14
 *
15
 * This software consists of voluntary contributions made by many individuals
16
 * and is licensed under the LGPL. For more information please see
17
 * <http://phing.info>.
18
 */
19
20
namespace Phing\Io;
21
22
use Exception;
23
use Phing\Exception\BuildException;
24
use Phing\Filter\ChainReaderHelper;
25
use Phing\Phing;
26
use Phing\Project;
27
use Phing\Util\Character;
28
use Phing\Util\StringHelper;
29
30
/**
31
 * File utility class.
32
 * - handles os independent stuff etc
33
 * - mapper stuff
34
 * - filter stuff
35
 *
36
 */
37
class FileUtils
38
{
39
    /**
40
     * path separator string, static, obtained from FileSystem (; or :)
41
     */
42
    private static $pathSeparator;
43
44
    /**
45
     * separator string, static, obtained from FileSystem
46
     */
47
    private static $separator;
48
49
    /**
50
     * @var false
51
     */
52
    private $dosWithDrive;
53
54
    /**
55
     * @return string
56
     * @throws IOException
57
     */
58 4
    public static function getPathSeparator(): string
59
    {
60 4
        if (self::$pathSeparator === null) {
61
            self::$pathSeparator = FileSystem::getFileSystem()->getPathSeparator();
62
        }
63 4
        return self::$pathSeparator;
64
    }
65
66
    /**
67
     * @return string
68
     * @throws IOException
69
     */
70 889
    public static function getSeparator(): string
71
    {
72 889
        if (self::$separator === null) {
73
            self::$separator = FileSystem::getFileSystem()->getSeparator();
74
        }
75 889
        return self::$separator;
76
    }
77
78
    /**
79
     * Returns the path to the temp directory.
80
     *
81
     * @return string
82
     */
83 9
    public static function getTempDir()
84
    {
85 9
        return Phing::getProperty('php.tmpdir');
86
    }
87
88
    /**
89
     * Returns the default file/dir creation mask value
90
     * (The mask value is prepared w.r.t the current user's file-creation mask value)
91
     *
92
     * @param bool $dirmode Directory creation mask to select
93
     *
94
     * @return int  Creation Mask in octal representation
95
     */
96
    public static function getDefaultFileCreationMask($dirmode = false): int
97
    {
98
        // Preparing the creation mask base permission
99
        $permission = ($dirmode === true) ? 0777 : 0666;
100
101
        // Default mask information
102
        $defaultmask = sprintf('%03o', ($permission & ($permission - (int) sprintf('%04o', umask()))));
103
104
        return octdec($defaultmask);
0 ignored issues
show
Bug Best Practice introduced by
The expression return octdec($defaultmask) could return the type double which is incompatible with the type-hinted return integer. Consider adding an additional type-check to rule them out.
Loading history...
105
    }
106
107
    /**
108
     * Returns a new Reader with filterchains applied.  If filterchains are empty,
109
     * simply returns passed reader.
110
     *
111
     * @param Reader $in Reader to modify (if appropriate).
112
     * @param array   &$filterChains filter chains to apply.
113
     * @return Reader  Assembled Reader (w/ filter chains).
114
     */
115 37
    public static function getChainedReader(Reader $in, &$filterChains, Project $project)
116
    {
117 37
        if (!empty($filterChains)) {
118 28
            $crh = new ChainReaderHelper();
119 28
            $crh->setBufferSize(65536); // 64k buffer, but isn't being used (yet?)
120 28
            $crh->setPrimaryReader($in);
121 28
            $crh->setFilterChains($filterChains);
122 28
            $crh->setProject($project);
123 28
            return $crh->getAssembledReader();
124
        }
125
126 10
        return $in;
127
    }
128
129
    /**
130
     * Copies a file using filter chains.
131
     *
132
     * @param File $sourceFile
133
     * @param File $destFile
134
     * @param Project $project
135
     * @param bool $overwrite
136
     * @param bool $preserveLastModified
137
     * @param array $filterChains
138
     * @param int $mode
139
     * @param bool $preservePermissions
140
     * @param int $granularity
141
     * @throws IOException
142
     */
143 49
    public function copyFile(
144
        File $sourceFile,
145
        File $destFile,
146
        Project $project,
147
        $overwrite = false,
148
        $preserveLastModified = true,
149
        &$filterChains = null,
150
        $mode = 0755,
151
        $preservePermissions = true,
152
        int $granularity = 0
153
    ) {
154
        if (
155 49
            $overwrite
156 39
            || !$destFile->exists()
157 49
            || $destFile->lastModified() < $sourceFile->lastModified() - $granularity
158
        ) {
159 48
            if ($destFile->exists() && ($destFile->isFile() || $destFile->isLink())) {
160 1
                $destFile->delete();
161
            }
162
163
            // ensure that parent dir of dest file exists!
164 48
            $parent = $destFile->getParentFile();
165 48
            if ($parent !== null && !$parent->exists()) {
166
                // Setting source directory permissions to target
167
                // (On permissions preservation, the target directory permissions
168
                // will be inherited from the source directory, otherwise the 'mode'
169
                // will be used)
170 3
                $dirMode = ($preservePermissions ? $sourceFile->getParentFile()->getMode() : $mode);
171
172 3
                $parent->mkdirs($dirMode);
173
            }
174
175 48
            if ((is_array($filterChains)) && (!empty($filterChains))) {
176 21
                $in = self::getChainedReader(new BufferedReader(new FileReader($sourceFile)), $filterChains, $project);
177 21
                $out = new BufferedWriter(new FileWriter($destFile));
178
179
                // New read() methods returns a big buffer.
180 21
                while (-1 !== ($buffer = $in->read())) { // -1 indicates EOF
181 21
                    $out->write($buffer);
182
                }
183
184 21
                if ($in !== null) {
185 21
                    $in->close();
186
                }
187 21
                if ($out !== null) {
188 21
                    $out->close();
189
                }
190
191
                // Set/Copy the permissions on the target
192 21
                if ($preservePermissions === true) {
193 21
                    $destFile->setMode($sourceFile->getMode());
194
                }
195
            } else {
196
                // simple copy (no filtering)
197 27
                $sourceFile->copyTo($destFile);
198
199
                // By default, PHP::Copy also copies the file permissions. Therefore,
200
                // re-setting the mode with the "user file-creation mask" information.
201 27
                if ($preservePermissions === false) {
202
                    $destFile->setMode(FileUtils::getDefaultFileCreationMask());
203
                }
204
            }
205
206 48
            if ($preserveLastModified && !$destFile->isLink()) {
207 2
                $destFile->setLastModified($sourceFile->lastModified());
208
            }
209
        }
210 49
    }
211
212
    /**
213
     * Attempts to rename a file from a source to a destination.
214
     * If overwrite is set to true, this method overwrites existing file even if the destination file is newer.
215
     * Otherwise, the source file is renamed only if the destination file is older than it.
216
     *
217
     * @throws IOException
218
     */
219 1
    public function renameFile(File $sourceFile, File $destFile, $overwrite = false): void
220
    {
221
        // ensure that parent dir of dest file exists!
222 1
        $parent = $destFile->getParentFile();
223 1
        if ($parent !== null) {
224 1
            if (!$parent->exists()) {
225
                $parent->mkdirs();
226
            }
227
        }
228
229 1
        if ($overwrite || !$destFile->exists() || $destFile->lastModified() < $sourceFile->lastModified()) {
230 1
            if ($destFile->exists()) {
231
                try {
232
                    $destFile->delete();
233
                } catch (Exception $e) {
234
                    throw new BuildException(
235
                        "Unable to remove existing file " . $destFile->__toString() . ": " . $e->getMessage()
236
                    );
237
                }
238
            }
239
        }
240
241 1
        $sourceFile->renameTo($destFile);
242 1
    }
243
244
    /**
245
     * Interpret the filename as a file relative to the given file -
246
     * unless the filename already represents an absolute filename.
247
     *
248
     * @param File $file the "reference" file for relative paths. This
249
     *                             instance must be an absolute file and must
250
     *                             not contain ./ or ../ sequences (same for \
251
     *                             instead of /).
252
     * @param string $filename a file name
253
     *
254
     * @return File A PhingFile object pointing to an absolute file that doesn't contain ./ or ../ sequences
255
     *                   and uses the correct separator for the current platform.
256
     * @throws IOException
257
     *
258
     */
259 476
    public function resolveFile(File $file, string $filename): File
260
    {
261
        // remove this and use the static class constant File::separator
262
        // as soon as ZE2 is ready
263 476
        $fs = FileSystem::getFileSystem();
264
265 476
        $filename = str_replace(['\\', '/'], $fs->getSeparator(), $filename);
266
267
        // deal with absolute files
268
        if (
269 476
            StringHelper::startsWith($fs->getSeparator(), $filename)
270 451
            || (strlen($filename) >= 2
271 451
                && Character::isLetter($filename[0])
272 476
                && $filename[1] === ':')
273
        ) {
274 105
            return new File($this->normalize($filename));
275
        }
276
277 451
        if (strlen($filename) >= 2 && Character::isLetter($filename[0]) && $filename[1] === ':') {
278
            return new File($this->normalize($filename));
279
        }
280
281 451
        $helpFile = new File($file->getAbsolutePath());
282
283 451
        $tok = strtok($filename, $fs->getSeparator());
284 451
        while ($tok !== false) {
285 451
            $part = $tok;
286 451
            if ($part === '..') {
287 170
                $parentFile = $helpFile->getParent();
288 170
                if ($parentFile === null) {
289
                    $msg = "The file or path you specified ($filename) is invalid relative to " . $file->getPath();
290
                    throw new IOException($msg);
291
                }
292 170
                $helpFile = new File($parentFile);
293 451
            } elseif ($part !== '.') {
294 425
                $helpFile = new File($helpFile, $part);
295
            }
296 451
            $tok = strtok($fs->getSeparator());
297
        }
298
299 451
        return new File($helpFile->getAbsolutePath());
300
    }
301
302
    /**
303
     * Normalize the given absolute path.
304
     *
305
     * This includes:
306
     *   - Uppercase the drive letter if there is one.
307
     *   - Remove redundant slashes after the drive spec.
308
     *   - resolve all ./, .\, ../ and ..\ sequences.
309
     *   - DOS style paths that start with a drive letter will have
310
     *     \ as the separator.
311
     *
312
     * @param string $path Path to normalize.
313
     *
314
     * @return string
315
     * @throws IOException
316
     * @throws BuildException
317
     */
318 873
    public function normalize(string $path): string
319
    {
320 873
        $dissect = $this->dissect($path);
321 873
        $sep = self::getSeparator();
322
323 873
        $s = [];
324 873
        $s[] = $dissect[0];
325 873
        $tok = strtok($dissect[1], $sep);
326 873
        while ($tok !== false) {
327 870
            $thisToken = $tok;
328 870
            if ("." === $thisToken) {
329
                $tok = strtok($sep);
330
                continue;
331
            }
332
333 870
            if (".." === $thisToken) {
334 1
                if (count($s) < 2) {
335
                    // using '..' in path that is too short
336
                    throw new IOException("Cannot resolve path: $path");
337
                }
338
339 1
                array_pop($s);
340
            } else { // plain component
341 870
                $s[] = $thisToken;
342
            }
343 870
            $tok = strtok($sep);
344
        }
345
346 873
        $sb = "";
347 873
        foreach ($s as $i => $v) {
348 873
            if ($i > 1) {
349
                // not before the filesystem root and not after it, since root
350
                // already contains one
351 870
                $sb .= $sep;
352
            }
353 873
            $sb .= $v;
354
        }
355
356 873
        $path = $sb;
357 873
        if ($this->dosWithDrive === true) {
358
            $path = str_replace('/', '\\', $path);
359
        }
360
361 873
        return $path;
362
    }
363
364
    /**
365
     * Dissect the specified absolute path.
366
     * @param string $path
367
     * @return array {root, remainig path}
368
     * @throws BuildException
369
     * @throws IOException
370
     */
371 873
    public function dissect(string $path): array
372
    {
373 873
        $sep = self::getSeparator();
374 873
        $path = str_replace(['\\', '/'], $sep, $path);
375
376
        // make sure we are dealing with an absolute path
377
        if (
378 873
            !StringHelper::startsWith($sep, $path)
379
            && !(strlen($path) >= 2
380
                && Character::isLetter($path[0])
381 873
                && $path[1] === ':')
382
        ) {
383
            throw new BuildException("$path is not an absolute path");
384
        }
385
386 873
        $this->dosWithDrive = false;
387 873
        $root = null;
388
389
        // Eliminate consecutive slashes after the drive spec
390
391 873
        if (strlen($path) >= 2 && Character::isLetter($path[0]) && $path[1] === ':') {
392
            $this->dosWithDrive = true;
0 ignored issues
show
Documentation Bug introduced by
The property $dosWithDrive was declared of type false, but true is of type true. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
393
394
            $ca = str_replace('/', '\\', $path);
395
396
            $path = strtoupper($ca[0]) . ':';
397
398
            for ($i = 2, $_i = strlen($ca); $i < $_i; $i++) {
399
                if (
400
                    ($ca[$i] !== '\\')
401
                    || ($ca[$i] === '\\'
402
                        && $ca[$i - 1] !== '\\')
403
                ) {
404
                    $path .= $ca[$i];
405
                }
406
            }
407
408
            $path = str_replace('\\', $sep, $path);
409
410
            if (strlen($path) === 2) {
411
                $root = $path;
412
                $path = "";
413
            } else {
414
                $root = substr($path, 0, 3);
415
                $path = substr($path, 3);
416
            }
417
        } else {
418 873
            if (strlen($path) === 1) {
419 3
                $root = $sep;
420 3
                $path = "";
421
            } else {
422 870
                if ($path[1] === $sep) {
423
                    // UNC drive
424
                    $root = $sep . $sep;
425
                    $path = substr($path, 2);
426
                } else {
427 870
                    $root = $sep;
428 870
                    $path = substr($path, 1);
429
                }
430
            }
431
        }
432
433 873
        return [$root, $path];
434
    }
435
436
    /**
437
     * Create a temporary file in a given directory.
438
     *
439
     * <p>The file denoted by the returned abstract pathname did not
440
     * exist before this method was invoked, any subsequent invocation
441
     * of this method will yield a different file name.</p>
442
     *
443
     * @param string $prefix prefix before the random number.
444
     * @param string $suffix file extension; include the '.'.
445
     * @param File $parentDir Directory to create the temporary file in;
446
     *                                sys_get_temp_dir() used if not specified.
447
     * @param bool $deleteOnExit whether to set the tempfile for deletion on
448
     *                                normal exit.
449
     * @param bool $createFile true if the file must actually be created. If false
450
     *                                chances exist that a file with the same name is
451
     *                                created in the time between invoking this method
452
     *                                and the moment the file is actually created. If
453
     *                                possible set to true.
454
     * @return File            a File reference to the new temporary file.
455
     * @throws BuildException
456
     */
457 1
    public function createTempFile(
458
        $prefix,
459
        $suffix,
460
        File $parentDir = null,
461
        $deleteOnExit = false,
462
        $createFile = false
463
    ): File {
464 1
        $result = null;
465 1
        $parent = ($parentDir === null) ? self::getTempDir() : $parentDir->getPath();
466
467 1
        if ($createFile) {
468
            try {
469
                $directory = new File($parent);
470
                // quick but efficient hack to create a unique filename ;-)
471
                $result = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $result is dead and can be removed.
Loading history...
472
                do {
473
                    $result = new File($directory, $prefix . substr(md5(time()), 0, 8) . $suffix);
474
                } while (file_exists($result->getPath()));
475
476
                $fs = FileSystem::getFileSystem();
477
                $fs->createNewFile($result->getPath());
478
                $fs->lock($result);
479
            } catch (IOException $e) {
480
                throw new BuildException("Could not create tempfile in " . $parent, $e);
481
            }
482
        } else {
483
            do {
484 1
                $result = new File($parent, $prefix . substr(md5((string) time()), 0, 8) . $suffix);
485 1
            } while ($result->exists());
486
        }
487
488 1
        if ($deleteOnExit) {
489
            $result->deleteOnExit();
490
        }
491
492 1
        return $result;
493
    }
494
495
    /**
496
     * @param File $file1
497
     * @param File $file2
498
     * @return bool Whether contents of two files is the same.
499
     * @throws IOException
500
     */
501 14
    public function contentEquals(File $file1, File $file2): bool
502
    {
503 14
        if (!($file1->exists() && $file2->exists())) {
504 2
            return false;
505
        }
506
507 13
        if (!($file1->canRead() && $file2->canRead())) {
508
            return false;
509
        }
510
511 13
        if ($file1->isDirectory() || $file2->isDirectory()) {
512 1
            return false;
513
        }
514
515 13
        $c1 = file_get_contents($file1->getAbsolutePath());
516 13
        $c2 = file_get_contents($file2->getAbsolutePath());
517
518 13
        return trim($c1) === trim($c2);
519
    }
520
}
521