Passed
Push — master ( fedd27...f10ad5 )
by Michiel
11:13
created

CopyTask::doWork()   C

Complexity

Conditions 14
Paths 12

Size

Total Lines 78
Code Lines 49

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 32
CRAP Score 14.0398

Importance

Changes 0
Metric Value
cc 14
eloc 49
nc 12
nop 0
dl 0
loc 78
ccs 32
cts 34
cp 0.9412
crap 14.0398
rs 6.2666
c 0
b 0
f 0

How to fix   Long Method    Complexity   

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:

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
/**
21
 * A phing copy task.  Copies a file or directory to a new file
22
 * or directory.  Files are only copied if the source file is newer
23
 * than the destination file, or when the destination file does not
24
 * exist. It is possible to explicitly overwrite existing files.
25
 *
26
 * @author Andreas Aderhold, [email protected]
27
 *
28
 * @package phing.tasks.system
29
 */
30
class CopyTask extends Task
31
{
32
    use ResourceAware;
33
    use FilterChainAware;
34
35
    /**
36
     * @var PhingFile
37
     */
38
    protected $file = null; // the source file (from xml attribute)
39
40
    /**
41
     * @var PhingFile
42
     */
43
    protected $destFile = null; // the destiantion file (from xml attribute)
44
45
    /**
46
     * @var PhingFile
47
     */
48
    protected $destDir = null; // the destination dir (from xml attribute)
49
50
    protected $overwrite = false; // overwrite destination (from xml attribute)
51
    protected $preserveLMT = false; // sync timestamps (from xml attribute)
52
    protected $preservePermissions = true; // sync permissions (from xml attribute)
53
    protected $includeEmpty = true; // include empty dirs? (from XML)
54
    protected $flatten = false; // apply the FlattenMapper right way (from XML)
55
56
    /**
57
     * @var Mapper
58
     */
59
    protected $mapperElement = null;
60
61
    protected $fileCopyMap = []; // asoc array containing mapped file names
62
    protected $dirCopyMap = []; // asoc array containing mapped file names
63
    protected $completeDirMap = []; // asoc array containing complete dir names
64
65
    /**
66
     * @var FileUtils
67
     */
68
    protected $fileUtils = null; // a instance of fileutils
69
70
    protected $verbosity = Project::MSG_VERBOSE;
71
72
    /**
73
     * @var int $mode
74
     */
75
    protected $mode = 0; // mode to create directories with
76
77
    /**
78
     * @var bool $haltonerror
79
     */
80
    protected $haltonerror = true; // stop build on errors
81
82
    protected $enableMultipleMappings = false;
83
84
    /** @var int $granularity */
85
    protected $granularity = 0;
86
87
    /**
88
     * Sets up this object internal stuff.
89
     * i.e. the Fileutils instance and default mode.
90
     */
91 42
    public function __construct()
92
    {
93 42
        parent::__construct();
94 42
        $this->fileUtils = new FileUtils();
95 42
        $this->mode = 0777 - umask();
96 42
    }
97
98
    /**
99
     * Set the number of seconds leeway to give before deciding a
100
     * target is out of date.
101
     *
102
     * @param int $granularity the granularity used to decide if a target is out of date.
103
     */
104 2
    public function setGranularity(int $granularity): void
105
    {
106 2
        $this->granularity = $granularity;
107 2
    }
108
109
    /**
110
     * Set the overwrite flag. IntrospectionHelper takes care of
111
     * booleans in set* methods so we can assume that the right
112
     * value (boolean primitive) is coming in here.
113
     *
114
     * @param boolean $bool Overwrite the destination file(s) if it/they already exist
115
     *
116
     * @return void
117
     */
118 9
    public function setOverwrite($bool)
119
    {
120 9
        $this->overwrite = (bool) $bool;
121 9
    }
122
123
    /**
124
     * Set whether files copied from directory trees will be "flattened"
125
     * into a single directory.  If there are multiple files with
126
     * the same name in the source directory tree, only the first
127
     * file will be copied into the "flattened" directory, unless
128
     * the forceoverwrite attribute is true.
129
     *
130
     * @param bool $flatten if true flatten the destination directory. Default
131
     *                is false.
132
     */
133
    public function setFlatten($flatten)
134
    {
135
        $this->flatten = $flatten;
136
    }
137
138
    /**
139
     * Used to force listing of all names of copied files.
140
     *
141
     * @param boolean $verbosity
142
     */
143 1
    public function setVerbose($verbosity)
144
    {
145 1
        if ($verbosity) {
146 1
            $this->verbosity = Project::MSG_INFO;
147
        } else {
148
            $this->verbosity = Project::MSG_VERBOSE;
149
        }
150 1
    }
151
152
    /**
153
     * @see CopyTask::setPreserveLastModified
154
     * @param $bool
155
     */
156
    public function setTstamp($bool)
157
    {
158
        $this->setPreserveLastModified($bool);
159
    }
160
161
    /**
162
     * Set the preserve timestamp flag. IntrospectionHelper takes care of
163
     * booleans in set* methods so we can assume that the right
164
     * value (boolean primitive) is coming in here.
165
     *
166
     * @param  boolean $bool Preserve the timestamp on the destination file
167
     * @return void
168
     */
169 1
    public function setPreserveLastModified($bool)
170
    {
171 1
        $this->preserveLMT = (bool) $bool;
172 1
    }
173
174
    /**
175
     * Set the preserve permissions flag. IntrospectionHelper takes care of
176
     * booleans in set* methods so we can assume that the right
177
     * value (boolean primitive) is coming in here.
178
     *
179
     * @param  boolean $bool Preserve the timestamp on the destination file
180
     * @return void
181
     */
182
    public function setPreservepermissions($bool)
183
    {
184
        $this->preservePermissions = (bool) $bool;
185
    }
186
187
    /**
188
     * @param $bool
189
     */
190
    public function setPreservemode($bool)
191
    {
192
        $this->setPreservepermissions($bool);
193
    }
194
195
    /**
196
     * Set the include empty dirs flag. IntrospectionHelper takes care of
197
     * booleans in set* methods so we can assume that the right
198
     * value (boolean primitive) is coming in here.
199
     *
200
     * @param  boolean $bool Flag if empty dirs should be cpoied too
201
     * @return void
202
     */
203
    public function setIncludeEmptyDirs($bool)
204
    {
205
        $this->includeEmpty = (bool) $bool;
206
    }
207
208
    /**
209
     * Set the file. We have to manually take care of the
210
     * type that is coming due to limited type support in php
211
     * in and convert it manually if necessary.
212
     *
213
     * @param PhingFile $file The source file. Either a string or an PhingFile object
214
     *
215
     * @return void
216
     */
217 17
    public function setFile(PhingFile $file)
218
    {
219 17
        $this->file = $file;
220 17
    }
221
222
    /**
223
     * Set the toFile. We have to manually take care of the
224
     * type that is coming due to limited type support in php
225
     * in and convert it manually if necessary.
226
     *
227
     * @param PhingFile $file The dest file. Either a string or an PhingFile object
228
     *
229
     * @return void
230
     */
231 9
    public function setTofile(PhingFile $file)
232
    {
233 9
        $this->destFile = $file;
234 9
    }
235
236
    /**
237
     * Sets the mode to create destination directories with (ignored on Windows).
238
     * Default mode is taken from umask()
239
     *
240
     * @param integer $mode Octal mode
241
     *
242
     * @return void
243
     */
244
    public function setMode($mode)
245
    {
246
        $this->mode = (int) base_convert($mode, 8, 10);
247
    }
248
249
    /**
250
     * Set the toDir. We have to manually take care of the
251
     * type that is coming due to limited type support in php
252
     * in and convert it manually if necessary.
253
     *
254
     * @param PhingFile $dir The directory, either a string or an PhingFile object
255
     *
256
     * @return void
257
     */
258 33
    public function setTodir(PhingFile $dir)
259
    {
260 33
        $this->destDir = $dir;
261 33
    }
262
263 2
    public function setEnableMultipleMappings($enableMultipleMappings)
264
    {
265 2
        $this->enableMultipleMappings = (bool) $enableMultipleMappings;
266 2
    }
267
268
    public function isEnabledMultipleMappings()
269
    {
270
        return $this->enableMultipleMappings;
271
    }
272
273
    /**
274
     * Set the haltonerror attribute - when true, will
275
     * make the build fail when errors are detected.
276
     *
277
     * @param boolean $haltonerror Flag if the build should be stopped on errors
278
     *
279
     * @return void
280
     */
281 1
    public function setHaltonerror($haltonerror)
282
    {
283 1
        $this->haltonerror = (bool) $haltonerror;
284 1
    }
285
286
    /**
287
     * Nested creator, creates one Mapper for this task
288
     *
289
     * @return Mapper         The created Mapper type object
290
     * @throws BuildException
291
     */
292 3
    public function createMapper()
293
    {
294 3
        if ($this->mapperElement !== null) {
295
            throw new BuildException("Cannot define more than one mapper", $this->getLocation());
296
        }
297 3
        $this->mapperElement = new Mapper($this->project);
298
299 3
        return $this->mapperElement;
300
    }
301
302
    /**
303
     * The main entry point where everything gets in motion.
304
     *
305
     * @return true           on success
306
     * @throws BuildException
307
     */
308 42
    public function main()
309
    {
310 42
        $this->validateAttributes();
311
312 42
        if ($this->file !== null) {
313 16
            if ($this->file->exists()) {
314 15
                if ($this->destFile === null) {
315 8
                    $this->destFile = new PhingFile($this->destDir, (string) $this->file->getName());
316
                }
317
                if (
318 15
                    $this->overwrite === true
319 8
                    || ($this->file->lastModified() - $this->granularity > $this->destFile->lastModified())
320
                ) {
321 13
                    $this->fileCopyMap[$this->file->getAbsolutePath()] = $this->destFile->getAbsolutePath();
322
                } else {
323 2
                    $this->log($this->file->getName() . " omitted, " . $this->destFile->getName() . " is up to date");
324
                }
325
            } else {
326
                // terminate build
327 1
                $this->logError("Could not find file " . $this->file->__toString() . " to copy.");
328
            }
329
        }
330
331 42
        $project = $this->getProject();
332
333
        // process filelists
334 42
        foreach ($this->filelists as $fl) {
335 2
            $fromDir = $fl->getDir($project);
336 2
            $srcFiles = $fl->getFiles($project);
337 2
            $srcDirs = [$fl->getDir($project)];
338
339 2
            if (!$this->flatten && $this->mapperElement === null) {
340 2
                $this->completeDirMap[$fromDir->getAbsolutePath()] = $this->destDir->getAbsolutePath();
341
            }
342
343 2
            $this->_scan($fromDir, $this->destDir, $srcFiles, $srcDirs);
344
        }
345
346 42
        foreach ($this->dirsets as $dirset) {
347
            try {
348 1
                $ds = $dirset->getDirectoryScanner($project);
349 1
                $fromDir = $dirset->getDir($project);
350 1
                $srcDirs = $ds->getIncludedDirectories();
351
352 1
                $srcFiles = [];
353 1
                foreach ($srcDirs as $srcDir) {
354 1
                    $srcFiles[] = $srcDir;
355
                }
356
357
                if (
358 1
                    !$this->flatten &&
359 1
                    $this->mapperElement === null &&
360 1
                    $ds->isEverythingIncluded()
361
                ) {
362
                    $this->completeDirMap[$fromDir->getAbsolutePath()] = $this->destDir->getAbsolutePath();
363
                }
364
365 1
                $this->_scan($fromDir, $this->destDir, $srcFiles, $srcDirs);
366
            } catch (BuildException $e) {
367
                if ($this->haltonerror === true) {
368
                    throw $e;
369
                }
370
371
                $this->logError($e->getMessage());
372
            }
373
        }
374
375
        // process filesets
376 42
        foreach ($this->filesets as $fs) {
377
            try {
378 26
                $ds = $fs->getDirectoryScanner($project);
379 26
                $fromDir = $fs->getDir($project);
380 26
                $srcFiles = $ds->getIncludedFiles();
381 26
                $srcDirs = $ds->getIncludedDirectories();
382
383
                if (
384 26
                    !$this->flatten
385 26
                    && $this->mapperElement === null
386 23
                    && $ds->isEverythingIncluded()
387
                ) {
388 18
                    $this->completeDirMap[$fromDir->getAbsolutePath()] = $this->destDir->getAbsolutePath();
389
                }
390
391 26
                $this->_scan($fromDir, $this->destDir, $srcFiles, $srcDirs);
392
            } catch (BuildException $e) {
393
                if ($this->haltonerror == true) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
394
                    throw $e;
395
                }
396
397
                $this->logError($e->getMessage());
398
            }
399
        }
400
401
        // go and copy the stuff
402 42
        $this->doWork();
403
404 42
        if ($this->destFile !== null) {
405 17
            $this->destDir = null;
406
        }
407 42
    }
408
409
    /**
410
     * Validates attributes coming in from XML
411
     *
412
     * @return void
413
     * @throws BuildException
414
     */
415 41
    protected function validateAttributes()
416
    {
417 41
        if ($this->file === null && count($this->dirsets) === 0 && count($this->filesets) === 0 && count($this->filelists) === 0) {
418
            throw new BuildException("CopyTask. Specify at least one source - a file, fileset or filelist.");
419
        }
420
421 41
        if ($this->destFile !== null && $this->destDir !== null) {
422
            throw new BuildException("Only one of destfile and destdir may be set.");
423
        }
424
425 41
        if ($this->destFile === null && $this->destDir === null) {
426
            throw new BuildException("One of destfile or destdir must be set.");
427
        }
428
429 41
        if ($this->file !== null && $this->file->exists() && $this->file->isDirectory()) {
430
            throw new BuildException("Use a fileset to copy directories.");
431
        }
432
433 41
        if ($this->destFile !== null && (count($this->filesets) > 0 || count($this->dirsets) > 0)) {
434
            throw new BuildException("Cannot concatenate multiple files into a single file.");
435
        }
436
437 41
        if ($this->destFile !== null) {
438 8
            $this->destDir = new PhingFile($this->destFile->getParent());
439
        }
440 41
    }
441
442
    /**
443
     * Compares source files to destination files to see if they
444
     * should be copied.
445
     *
446
     * @param $fromDir
447
     * @param $toDir
448
     * @param $files
449
     * @param $dirs
450
     *
451
     * @return void
452
     */
453 29
    private function _scan(&$fromDir, &$toDir, &$files, &$dirs)
454
    {
455
        /* mappers should be generic, so we get the mappers here and
456
        pass them on to builMap. This method is not redundan like it seems */
457 29
        $mapper = $this->getMapper();
458
459 29
        $this->buildMap($fromDir, $toDir, $files, $mapper, $this->fileCopyMap);
460
461 29
        if ($this->includeEmpty) {
462 29
            $this->buildMap($fromDir, $toDir, $dirs, $mapper, $this->dirCopyMap);
463
        }
464 29
    }
465
466 29
    private function getMapper()
467
    {
468 29
        $mapper = null;
469 29
        if ($this->mapperElement !== null) {
470 3
            $mapper = $this->mapperElement->getImplementation();
471 26
        } elseif ($this->flatten) {
472
            $mapper = new FlattenMapper();
473
        } else {
474 26
            $mapper = new IdentityMapper();
475
        }
476 29
        return $mapper;
477
    }
478
479
    /**
480
     * Builds a map of filenames (from->to) that should be copied
481
     *
482
     * @param $fromDir
483
     * @param $toDir
484
     * @param $names
485
     * @param FileNameMapper $mapper
486
     * @param $map
487
     *
488
     * @return void
489
     */
490 29
    private function buildMap(&$fromDir, &$toDir, &$names, &$mapper, &$map)
491
    {
492 29
        $toCopy = null;
493 29
        if ($this->overwrite) {
494 1
            $v = [];
495 1
            foreach ($names as $name) {
496 1
                $result = $mapper->main($name);
497 1
                if ($result !== null) {
498 1
                    $v[] = $name;
499
                }
500
            }
501 1
            $toCopy = $v;
502
        } else {
503 28
            $ds = new SourceFileScanner($this);
504 28
            $toCopy = $ds->restrict($names, $fromDir, $toDir, $mapper);
505
        }
506
507 29
        for ($i = 0, $_i = count($toCopy); $i < $_i; $i++) {
508 26
            $src = new PhingFile($fromDir, $toCopy[$i]);
509 26
            $mapped = $mapper->main($toCopy[$i]);
510 26
            if (!$this->enableMultipleMappings) {
511 24
                $dest = new PhingFile($toDir, $mapped[0]);
512 24
                $map[$src->getAbsolutePath()] = $dest->getAbsolutePath();
513
            } else {
514 2
                $mappedFiles = [];
515
516 2
                foreach ($mapped as $mappedFile) {
517 2
                    if ($mappedFile === null) {
518
                        continue;
519
                    }
520 2
                    $dest = new PhingFile($toDir, $mappedFile);
521 2
                    $mappedFiles[] = $dest->getAbsolutePath();
522
                }
523 2
                $map[$src->getAbsolutePath()] = $mappedFiles;
524
            }
525
        }
526 29
    }
527
528
    /**
529
     * Actually copies the files
530
     *
531
     * @return void
532
     * @throws BuildException
533
     */
534 36
    protected function doWork()
535
    {
536
537
        // These "slots" allow filters to retrieve information about the currently-being-process files
538 36
        $fromSlot = $this->getRegisterSlot("currentFromFile");
539 36
        $fromBasenameSlot = $this->getRegisterSlot("currentFromFile.basename");
540
541 36
        $toSlot = $this->getRegisterSlot("currentToFile");
542 36
        $toBasenameSlot = $this->getRegisterSlot("currentToFile.basename");
543
544 36
        $mapSize = count($this->fileCopyMap);
545 36
        $total = $mapSize;
546
547
        // handle empty dirs if appropriate
548 36
        if ($this->includeEmpty) {
549 36
            $count = 0;
550 36
            foreach ($this->dirCopyMap as $srcdir => $destdir) {
551 4
                $s = new PhingFile((string) $srcdir);
552 4
                $d = new PhingFile((string) $destdir);
553 4
                if (!$d->exists()) {
554
                    // Setting source directory permissions to target
555
                    // (On permissions preservation, the target directory permissions
556
                    // will be inherited from the source directory, otherwise the 'mode'
557
                    // will be used)
558 4
                    $dirMode = ($this->preservePermissions ? $s->getMode() : $this->mode);
559
560
                    // Directory creation with specific permission mode
561 4
                    if (!$d->mkdirs($dirMode)) {
562
                        $this->logError("Unable to create directory " . $d->__toString());
563
                    } else {
564 4
                        if ($this->preserveLMT) {
565
                            $d->setLastModified($s->lastModified());
566
                        }
567
568 4
                        $count++;
569
                    }
570
                }
571
            }
572 36
            if ($count > 0) {
573 4
                $this->log(
574 4
                    "Created " . $count . " empty director" . ($count == 1 ? "y" : "ies") . " in " . $this->destDir->getAbsolutePath()
575
                );
576
            }
577
        }
578
579 36
        if ($mapSize == 0) {
580 4
            return;
581
        }
582
583 32
        $this->log(
584 32
            "Copying " . $mapSize . " file" . (($mapSize) === 1 ? '' : 's') . " to " . $this->destDir->getAbsolutePath()
585
        );
586
        // walks the map and actually copies the files
587 32
        $count = 0;
588 32
        foreach ($this->fileCopyMap as $from => $toFiles) {
589 32
            if (is_array($toFiles)) {
590 2
                foreach ($toFiles as $to) {
591 2
                    $this->copyToSingleDestination(
592 2
                        $from,
593
                        $to,
594
                        $fromSlot,
595
                        $fromBasenameSlot,
596
                        $toSlot,
597
                        $toBasenameSlot,
598
                        $count,
599
                        $total
600
                    );
601
                }
602
            } else {
603 30
                $this->copyToSingleDestination(
604 30
                    $from,
605
                    $toFiles,
606
                    $fromSlot,
607
                    $fromBasenameSlot,
608
                    $toSlot,
609
                    $toBasenameSlot,
610
                    $count,
611
                    $total
612
                );
613
            }
614
        }
615 32
    }
616
617
    /**
618
     * @param $from
619
     * @param $to
620
     * @param RegisterSlot $fromSlot
621
     * @param RegisterSlot $fromBasenameSlot
622
     * @param RegisterSlot $toSlot
623
     * @param RegisterSlot $toBasenameSlot
624
     * @param $count
625
     * @param $total
626
     */
627 32
    private function copyToSingleDestination(
628
        $from,
629
        $to,
630
        $fromSlot,
631
        $fromBasenameSlot,
632
        $toSlot,
633
        $toBasenameSlot,
634
        &$count,
635
        &$total
636
    ) {
637 32
        if ($from === $to) {
638
            $this->log("Skipping self-copy of " . $from, $this->verbosity);
639
            $total--;
640
            return;
641
        }
642 32
        $this->log("From " . $from . " to " . $to, $this->verbosity);
643
        try { // try to copy file
644 32
            $fromFile = new PhingFile($from);
645 32
            $toFile = new PhingFile($to);
646
647 32
            $fromSlot->setValue($fromFile->getPath());
648 32
            $fromBasenameSlot->setValue($fromFile->getName());
649
650 32
            $toSlot->setValue($toFile->getPath());
651 32
            $toBasenameSlot->setValue($toFile->getName());
652
653 32
            $this->fileUtils->copyFile(
654 32
                $fromFile,
655
                $toFile,
656 32
                $this->getProject(),
657 32
                $this->overwrite,
658 32
                $this->preserveLMT,
659 32
                $this->filterChains,
660 32
                $this->mode,
661 32
                $this->preservePermissions,
662 32
                $this->granularity
663
            );
664
665 32
            $count++;
666
        } catch (IOException $ioe) {
667
            $this->logError("Failed to copy " . $from . " to " . $to . ": " . $ioe->getMessage());
668
        }
669 32
    }
670
671
    /**
672
     * @param string $message
673
     * @param null $location
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $location is correct as it would always require null to be passed?
Loading history...
674
     *
675
     * @throws BuildException
676
     */
677 1
    protected function logError($message, $location = null)
678
    {
679 1
        if ($this->haltonerror) {
680
            throw new BuildException($message, $location);
681
        }
682
683 1
        $this->log($message, Project::MSG_ERR);
684 1
    }
685
}
686