Passed
Push — master ( e1f86a...4e1a3a )
by Siad
05:23
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 0
Metric Value
cc 19
eloc 27
nc 121
nop 9
dl 0
loc 65
ccs 25
cts 26
cp 0.9615
crap 19.0206
rs 4.3416
c 0
b 0
f 0

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
 * @package phing.util
37
 */
38
class FileUtils
39
{
40
    /**
41
     * path separator string, static, obtained from FileSystem (; or :)
42
     */
43
    private static $pathSeparator;
44
45
    /**
46
     * separator string, static, obtained from FileSystem
47
     */
48
    private static $separator;
49
50 4
    public static function getPathSeparator(): string
51
    {
52 4
        if (self::$pathSeparator === null) {
53
            self::$pathSeparator = FileSystem::getFileSystem()->getPathSeparator();
54
        }
55 4
        return self::$pathSeparator;
56
    }
57
58 853
    public static function getSeparator(): string
59
    {
60 853
        if (self::$separator === null) {
61
            self::$separator = FileSystem::getFileSystem()->getSeparator();
62
        }
63 853
        return self::$separator;
64
    }
65
66
    /**
67
     * Returns the path to the temp directory.
68
     *
69
     * @return string
70
     */
71 9
    public static function getTempDir()
72
    {
73 9
        return Phing::getProperty('php.tmpdir');
74
    }
75
76
    /**
77
     * Returns the default file/dir creation mask value
78
     * (The mask value is prepared w.r.t the current user's file-creation mask value)
79
     *
80
     * @param boolean $dirmode Directory creation mask to select
81
     *
82
     * @return int  Creation Mask in octal representation
83
     */
84
    public static function getDefaultFileCreationMask($dirmode = false)
85
    {
86
87
        // Preparing the creation mask base permission
88
        $permission = ($dirmode === true) ? 0777 : 0666;
89
90
        // Default mask information
91
        $defaultmask = sprintf('%03o', ($permission & ($permission - (int) sprintf('%04o', umask()))));
92
93
        return octdec($defaultmask);
94
    }
95
96
    /**
97
     * Returns a new Reader with filterchains applied.  If filterchains are empty,
98
     * simply returns passed reader.
99
     *
100
     * @param Reader $in Reader to modify (if appropriate).
101
     * @param array   &$filterChains filter chains to apply.
102
     * @param Project $project
103
     * @return Reader  Assembled Reader (w/ filter chains).
104
     */
105 36
    public static function getChainedReader(Reader $in, &$filterChains, Project $project)
106
    {
107 36
        if (!empty($filterChains)) {
108 27
            $crh = new ChainReaderHelper();
109 27
            $crh->setBufferSize(65536); // 64k buffer, but isn't being used (yet?)
110 27
            $crh->setPrimaryReader($in);
111 27
            $crh->setFilterChains($filterChains);
112 27
            $crh->setProject($project);
113 27
            $rdr = $crh->getAssembledReader();
114
115 27
            return $rdr;
116
        }
117
118 10
        return $in;
119
    }
120
121
    /**
122
     * Copies a file using filter chains.
123
     *
124
     * @param File $sourceFile
125
     * @param File $destFile
126
     * @param boolean $overwrite
127
     * @param boolean $preserveLastModified
128
     * @param array $filterChains
129
     * @param Project $project
130
     * @param integer $mode
131
     * @param bool $preservePermissions
132
     * @param int $granularity
133
     * @return void
134
     * @throws Exception
135
     * @throws IOException
136
     */
137 49
    public function copyFile(
138
        File $sourceFile,
139
        File $destFile,
140
        Project $project,
141
        $overwrite = false,
142
        $preserveLastModified = true,
143
        &$filterChains = null,
144
        $mode = 0755,
145
        $preservePermissions = true,
146
        int $granularity = 0
147
    ) {
148
        if (
149 49
            $overwrite
150 39
            || !$destFile->exists()
151 49
            || $destFile->lastModified() < $sourceFile->lastModified() - $granularity
152
        ) {
153 48
            if ($destFile->exists() && ($destFile->isFile() || $destFile->isLink())) {
154 1
                $destFile->delete();
155
            }
156
157
            // ensure that parent dir of dest file exists!
158 48
            $parent = $destFile->getParentFile();
159 48
            if ($parent !== null && !$parent->exists()) {
160
                // Setting source directory permissions to target
161
                // (On permissions preservation, the target directory permissions
162
                // will be inherited from the source directory, otherwise the 'mode'
163
                // will be used)
164 3
                $dirMode = ($preservePermissions ? $sourceFile->getParentFile()->getMode() : $mode);
165
166 3
                $parent->mkdirs($dirMode);
167
            }
168
169 48
            if ((is_array($filterChains)) && (!empty($filterChains))) {
170 21
                $in = self::getChainedReader(new BufferedReader(new FileReader($sourceFile)), $filterChains, $project);
171 21
                $out = new BufferedWriter(new FileWriter($destFile));
172
173
                // New read() methods returns a big buffer.
174 21
                while (-1 !== ($buffer = $in->read())) { // -1 indicates EOF
175 21
                    $out->write($buffer);
176
                }
177
178 21
                if ($in !== null) {
179 21
                    $in->close();
180
                }
181 21
                if ($out !== null) {
182 21
                    $out->close();
183
                }
184
185
                // Set/Copy the permissions on the target
186 21
                if ($preservePermissions === true) {
187 21
                    $destFile->setMode($sourceFile->getMode());
188
                }
189
            } else {
190
                // simple copy (no filtering)
191 27
                $sourceFile->copyTo($destFile);
192
193
                // By default, PHP::Copy also copies the file permissions. Therefore,
194
                // re-setting the mode with the "user file-creation mask" information.
195 27
                if ($preservePermissions === false) {
196
                    $destFile->setMode(FileUtils::getDefaultFileCreationMask(false));
197
                }
198
            }
199
200 48
            if ($preserveLastModified && !$destFile->isLink()) {
201 2
                $destFile->setLastModified($sourceFile->lastModified());
202
            }
203
        }
204 49
    }
205
206
207
    /**
208
     * Attempts to rename a file from a source to a destination.
209
     * If overwrite is set to true, this method overwrites existing file even if the destination file is newer.
210
     * Otherwise, the source file is renamed only if the destination file is older than it.
211
     *
212
     * @param File $sourceFile
213
     * @param File $destFile
214
     * @return void
215
     */
216 1
    public function renameFile(File $sourceFile, File $destFile, $overwrite = false)
217
    {
218
        // ensure that parent dir of dest file exists!
219 1
        $parent = $destFile->getParentFile();
220 1
        if ($parent !== null) {
221 1
            if (!$parent->exists()) {
222
                $parent->mkdirs();
223
            }
224
        }
225
226 1
        if ($overwrite || !$destFile->exists() || $destFile->lastModified() < $sourceFile->lastModified()) {
227 1
            if ($destFile->exists()) {
228
                try {
229
                    $destFile->delete();
230
                } catch (Exception $e) {
231
                    throw new BuildException(
232
                        "Unable to remove existing file " . $destFile->__toString() . ": " . $e->getMessage()
233
                    );
234
                }
235
            }
236
        }
237
238 1
        $sourceFile->renameTo($destFile);
239 1
    }
240
241
    /**
242
     * Interpret the filename as a file relative to the given file -
243
     * unless the filename already represents an absolute filename.
244
     *
245
     * @param File $file the "reference" file for relative paths. This
246
     *                             instance must be an absolute file and must
247
     *                             not contain ./ or ../ sequences (same for \
248
     *                             instead of /).
249
     * @param string $filename a file name
250
     *
251
     * @return File A PhingFile object pointing to an absolute file that doesn't contain ./ or ../ sequences
252
     *                   and uses the correct separator for the current platform.
253
     * @throws IOException
254
     *
255
     */
256 464
    public function resolveFile($file, $filename)
257
    {
258
        // remove this and use the static class constant File::separator
259
        // as soon as ZE2 is ready
260 464
        $fs = FileSystem::getFileSystem();
261
262 464
        $filename = str_replace(['\\', '/'], $fs->getSeparator(), $filename);
263
264
        // deal with absolute files
265
        if (
266 464
            StringHelper::startsWith($fs->getSeparator(), $filename)
267 439
            || (strlen($filename) >= 2
268 439
                && Character::isLetter($filename[0])
269 464
                && $filename[1] === ':')
270
        ) {
271 105
            return new File($this->normalize($filename));
272
        }
273
274 439
        if (strlen($filename) >= 2 && Character::isLetter($filename[0]) && $filename[1] === ':') {
275
            return new File($this->normalize($filename));
276
        }
277
278 439
        $helpFile = new File($file->getAbsolutePath());
279
280 439
        $tok = strtok($filename, $fs->getSeparator());
281 439
        while ($tok !== false) {
282 439
            $part = $tok;
283 439
            if ($part === '..') {
284 170
                $parentFile = $helpFile->getParent();
285 170
                if ($parentFile === null) {
286
                    $msg = "The file or path you specified ($filename) is invalid relative to " . $file->getPath();
287
                    throw new IOException($msg);
288
                }
289 170
                $helpFile = new File($parentFile);
290 439
            } elseif ($part !== '.') {
291 413
                $helpFile = new File($helpFile, $part);
292
            }
293 439
            $tok = strtok($fs->getSeparator());
294
        }
295
296 439
        return new File($helpFile->getAbsolutePath());
297
    }
298
299
    /**
300
     * Normalize the given absolute path.
301
     *
302
     * This includes:
303
     *   - Uppercase the drive letter if there is one.
304
     *   - Remove redundant slashes after the drive spec.
305
     *   - resolve all ./, .\, ../ and ..\ sequences.
306
     *   - DOS style paths that start with a drive letter will have
307
     *     \ as the separator.
308
     *
309
     * @param string $path Path to normalize.
310
     *
311
     * @return string
312
     * @throws IOException
313
     *
314
     */
315 853
    public function normalize($path)
316
    {
317 853
        $path = (string) $path;
318 853
        $orig = $path;
319
320 853
        $path = str_replace(['\\', '/'], DIRECTORY_SEPARATOR, $path);
321
322
        // make sure we are dealing with an absolute path
323
        if (
324 853
            !StringHelper::startsWith(DIRECTORY_SEPARATOR, $path)
325
            && !(strlen($path) >= 2
326
                && Character::isLetter($path[0])
327 853
                && $path[1] === ':')
328
        ) {
329
            throw new IOException("$path is not an absolute path");
330
        }
331
332 853
        $dosWithDrive = false;
333 853
        $root = null;
334
335
        // Eliminate consecutive slashes after the drive spec
336
337 853
        if (strlen($path) >= 2 && Character::isLetter($path[0]) && $path[1] === ':') {
338
            $dosWithDrive = true;
339
340
            $ca = str_replace('/', '\\', $path);
341
342
            $path = strtoupper($ca[0]) . ':';
343
344
            for ($i = 2, $_i = strlen($ca); $i < $_i; $i++) {
345
                if (
346
                    ($ca[$i] !== '\\')
347
                    || ($ca[$i] === '\\'
348
                        && $ca[$i - 1] !== '\\')
349
                ) {
350
                    $path .= $ca[$i];
351
                }
352
            }
353
354
            $path = str_replace('\\', DIRECTORY_SEPARATOR, $path);
355
356
            if (strlen($path) == 2) {
357
                $root = $path;
358
                $path = "";
359
            } else {
360
                $root = substr($path, 0, 3);
361
                $path = substr($path, 3);
362
            }
363
        } else {
364 853
            if (strlen($path) == 1) {
365 3
                $root = DIRECTORY_SEPARATOR;
366 3
                $path = "";
367
            } else {
368 850
                if ($path[1] == DIRECTORY_SEPARATOR) {
369
                    // UNC drive
370
                    $root = DIRECTORY_SEPARATOR . DIRECTORY_SEPARATOR;
371
                    $path = substr($path, 2);
372
                } else {
373 850
                    $root = DIRECTORY_SEPARATOR;
374 850
                    $path = substr($path, 1);
375
                }
376
            }
377
        }
378
379 853
        $s = [];
380 853
        $s[] = $root;
381 853
        $tok = strtok($path, DIRECTORY_SEPARATOR);
382 853
        while ($tok !== false) {
383 850
            $thisToken = $tok;
384 850
            if ("." === $thisToken) {
385
                $tok = strtok(DIRECTORY_SEPARATOR);
386
                continue;
387
            }
388
389 850
            if (".." === $thisToken) {
390 1
                if (count($s) < 2) {
391
                    // using '..' in path that is too short
392
                    throw new IOException("Cannot resolve path: $orig");
393
                }
394
395 1
                array_pop($s);
396
            } else { // plain component
397 850
                $s[] = $thisToken;
398
            }
399 850
            $tok = strtok(DIRECTORY_SEPARATOR);
400
        }
401
402 853
        $sb = "";
403 853
        for ($i = 0, $_i = count($s); $i < $_i; $i++) {
404 853
            if ($i > 1) {
405
                // not before the filesystem root and not after it, since root
406
                // already contains one
407 850
                $sb .= DIRECTORY_SEPARATOR;
408
            }
409 853
            $sb .= (string) $s[$i];
410
        }
411
412
413 853
        $path = (string) $sb;
414 853
        if ($dosWithDrive === true) {
415
            $path = str_replace('/', '\\', $path);
416
        }
417
418 853
        return $path;
419
    }
420
421
    /**
422
     * Create a temporary file in a given directory.
423
     *
424
     * <p>The file denoted by the returned abstract pathname did not
425
     * exist before this method was invoked, any subsequent invocation
426
     * of this method will yield a different file name.</p>
427
     *
428
     * @param string $prefix prefix before the random number.
429
     * @param string $suffix file extension; include the '.'.
430
     * @param File $parentDir Directory to create the temporary file in;
431
     *                                sys_get_temp_dir() used if not specified.
432
     * @param boolean $deleteOnExit whether to set the tempfile for deletion on
433
     *                                normal exit.
434
     * @param boolean $createFile true if the file must actually be created. If false
435
     *                                chances exist that a file with the same name is
436
     *                                created in the time between invoking this method
437
     *                                and the moment the file is actually created. If
438
     *                                possible set to true.
439
     * @return File            a File reference to the new temporary file.
440
     * @throws BuildException
441
     */
442 1
    public function createTempFile(
443
        $prefix,
444
        $suffix,
445
        File $parentDir = null,
446
        $deleteOnExit = false,
447
        $createFile = false
448
    ) {
449 1
        $result = null;
450 1
        $parent = ($parentDir === null) ? self::getTempDir() : $parentDir->getPath();
451
452 1
        if ($createFile) {
453
            try {
454
                $directory = new File($parent);
455
                // quick but efficient hack to create a unique filename ;-)
456
                $result = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $result is dead and can be removed.
Loading history...
457
                do {
458
                    $result = new File($directory, $prefix . substr(md5(time()), 0, 8) . $suffix);
459
                } while (file_exists($result->getPath()));
460
461
                $fs = FileSystem::getFileSystem();
462
                $fs->createNewFile($result->getPath());
463
                $fs->lock($result);
464
            } catch (IOException $e) {
465
                throw new BuildException("Could not create tempfile in " . $parent, $e);
466
            }
467
        } else {
468
            do {
469 1
                $result = new File($parent, $prefix . substr(md5((string) time()), 0, 8) . $suffix);
470 1
            } while ($result->exists());
471
        }
472
473 1
        if ($deleteOnExit) {
474
            $result->deleteOnExit();
475
        }
476
477 1
        return $result;
478
    }
479
480
    /**
481
     * @param File $file1
482
     * @param File $file2
483
     *
484
     * @return boolean Whether contents of two files is the same.
485
     */
486 14
    public function contentEquals(File $file1, File $file2)
487
    {
488 14
        if (!($file1->exists() && $file2->exists())) {
489 2
            return false;
490
        }
491
492 13
        if (!($file1->canRead() && $file2->canRead())) {
493
            return false;
494
        }
495
496 13
        if ($file1->isDirectory() || $file2->isDirectory()) {
497 1
            return false;
498
        }
499
500 13
        $c1 = file_get_contents($file1->getAbsolutePath());
501 13
        $c2 = file_get_contents($file2->getAbsolutePath());
502
503 13
        return trim($c1) == trim($c2);
504
    }
505
}
506