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