Passed
Push — master ( b5ca42...8fe651 )
by Siad
05:05
created

CopyTask::buildMap()   B

Complexity

Conditions 8
Paths 6

Size

Total Lines 34
Code Lines 25

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 23
CRAP Score 8.0046

Importance

Changes 0
Metric Value
cc 8
eloc 25
nc 6
nop 5
dl 0
loc 34
ccs 23
cts 24
cp 0.9583
crap 8.0046
rs 8.4444
c 0
b 0
f 0
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
use Phing\Exception\BuildException;
21
use Phing\Io\FileUtils;
22
use Phing\Io\IOException;
23
use Phing\Io\File;
24
use Phing\Io\SourceFileScanner;
25
use Phing\Mapper\FileNameMapper;
26
use Phing\Mapper\FlattenMapper;
27
use Phing\Mapper\IdentityMapper;
28
use Phing\Project;
29
use Phing\Task;
30
use Phing\Type\Mapper;
31
use Phing\Util\RegisterSlot;
32
33
/**
34
 * A phing copy task.  Copies a file or directory to a new file
35
 * or directory.  Files are only copied if the source file is newer
36
 * than the destination file, or when the destination file does not
37
 * exist. It is possible to explicitly overwrite existing files.
38
 *
39
 * @author Andreas Aderhold, [email protected]
40
 *
41
 * @package phing.tasks.system
42
 */
43
class CopyTask extends Task
44
{
45
    use ResourceAware;
46
    use FilterChainAware;
47
48
    /**
49
     * @var File
50
     */
51
    protected $file = null; // the source file (from xml attribute)
52
53
    /**
54
     * @var File
55
     */
56
    protected $destFile = null; // the destiantion file (from xml attribute)
57
58
    /**
59
     * @var File
60
     */
61
    protected $destDir = null; // the destination dir (from xml attribute)
62
63
    protected $overwrite = false; // overwrite destination (from xml attribute)
64
    protected $preserveLMT = false; // sync timestamps (from xml attribute)
65
    protected $preservePermissions = true; // sync permissions (from xml attribute)
66
    protected $includeEmpty = true; // include empty dirs? (from XML)
67
    protected $flatten = false; // apply the FlattenMapper right way (from XML)
68
69
    /**
70
     * @var Mapper
71
     */
72
    protected $mapperElement = null;
73
74
    protected $fileCopyMap = []; // asoc array containing mapped file names
75
    protected $dirCopyMap = []; // asoc array containing mapped file names
76
    protected $completeDirMap = []; // asoc array containing complete dir names
77
78
    /**
79
     * @var FileUtils
80
     */
81
    protected $fileUtils = null; // a instance of fileutils
82
83
    protected $verbosity = Project::MSG_VERBOSE;
84
85
    /**
86
     * @var int $mode
87
     */
88
    protected $mode = 0; // mode to create directories with
89
90
    /**
91
     * @var bool $haltonerror
92
     */
93
    protected $haltonerror = true; // stop build on errors
94
95
    protected $enableMultipleMappings = false;
96
97
    /** @var int $granularity */
98
    protected $granularity = 0;
99
100
    /**
101
     * Sets up this object internal stuff.
102
     * i.e. the Fileutils instance and default mode.
103
     */
104 60
    public function __construct()
105
    {
106 60
        parent::__construct();
107 60
        $this->fileUtils = new FileUtils();
108 60
        $this->mode = 0777 - umask();
109 60
    }
110
111
    /**
112
     * Set the number of seconds leeway to give before deciding a
113
     * target is out of date.
114
     *
115
     * @param int $granularity the granularity used to decide if a target is out of date.
116
     */
117 2
    public function setGranularity(int $granularity): void
118
    {
119 2
        $this->granularity = $granularity;
120 2
    }
121
122
    /**
123
     * Set the overwrite flag. IntrospectionHelper takes care of
124
     * booleans in set* methods so we can assume that the right
125
     * value (boolean primitive) is coming in here.
126
     *
127
     * @param boolean $bool Overwrite the destination file(s) if it/they already exist
128
     *
129
     * @return void
130
     */
131 11
    public function setOverwrite($bool)
132
    {
133 11
        $this->overwrite = (bool) $bool;
134 11
    }
135
136
    /**
137
     * Set whether files copied from directory trees will be "flattened"
138
     * into a single directory.  If there are multiple files with
139
     * the same name in the source directory tree, only the first
140
     * file will be copied into the "flattened" directory, unless
141
     * the forceoverwrite attribute is true.
142
     *
143
     * @param bool $flatten if true flatten the destination directory. Default
144
     *                is false.
145
     */
146
    public function setFlatten($flatten)
147
    {
148
        $this->flatten = $flatten;
149
    }
150
151
    /**
152
     * Used to force listing of all names of copied files.
153
     *
154
     * @param boolean $verbosity
155
     */
156 1
    public function setVerbose($verbosity)
157
    {
158 1
        if ($verbosity) {
159 1
            $this->verbosity = Project::MSG_INFO;
160
        } else {
161
            $this->verbosity = Project::MSG_VERBOSE;
162
        }
163 1
    }
164
165
    /**
166
     * @see CopyTask::setPreserveLastModified
167
     * @param $bool
168
     */
169
    public function setTstamp($bool)
170
    {
171
        $this->setPreserveLastModified($bool);
172
    }
173
174
    /**
175
     * Set the preserve timestamp 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 1
    public function setPreserveLastModified($bool)
183
    {
184 1
        $this->preserveLMT = (bool) $bool;
185 1
    }
186
187
    /**
188
     * Set the preserve permissions flag. IntrospectionHelper takes care of
189
     * booleans in set* methods so we can assume that the right
190
     * value (boolean primitive) is coming in here.
191
     *
192
     * @param  boolean $bool Preserve the timestamp on the destination file
193
     * @return void
194
     */
195
    public function setPreservepermissions($bool)
196
    {
197
        $this->preservePermissions = (bool) $bool;
198
    }
199
200
    /**
201
     * @param $bool
202
     */
203
    public function setPreservemode($bool)
204
    {
205
        $this->setPreservepermissions($bool);
206
    }
207
208
    /**
209
     * Set the include empty dirs flag. IntrospectionHelper takes care of
210
     * booleans in set* methods so we can assume that the right
211
     * value (boolean primitive) is coming in here.
212
     *
213
     * @param  boolean $bool Flag if empty dirs should be cpoied too
214
     * @return void
215
     */
216
    public function setIncludeEmptyDirs($bool)
217
    {
218
        $this->includeEmpty = (bool) $bool;
219
    }
220
221
    /**
222
     * Set the file. We have to manually take care of the
223
     * type that is coming due to limited type support in php
224
     * in and convert it manually if necessary.
225
     *
226
     * @param File $file The source file. Either a string or an PhingFile object
227
     *
228
     * @return void
229
     */
230 16
    public function setFile(File $file)
231
    {
232 16
        $this->file = $file;
233 16
    }
234
235
    /**
236
     * Set the toFile. We have to manually take care of the
237
     * type that is coming due to limited type support in php
238
     * in and convert it manually if necessary.
239
     *
240
     * @param File $file The dest file. Either a string or an PhingFile object
241
     *
242
     * @return void
243
     */
244 9
    public function setTofile(File $file)
245
    {
246 9
        $this->destFile = $file;
247 9
    }
248
249
    /**
250
     * Sets the mode to create destination directories with (ignored on Windows).
251
     * Default mode is taken from umask()
252
     *
253
     * @param integer $mode Octal mode
254
     *
255
     * @return void
256
     */
257
    public function setMode($mode)
258
    {
259
        $this->mode = (int) base_convert($mode, 8, 10);
260
    }
261
262
    /**
263
     * Set the toDir. We have to manually take care of the
264
     * type that is coming due to limited type support in php
265
     * in and convert it manually if necessary.
266
     *
267
     * @param File $dir The directory, either a string or an PhingFile object
268
     *
269
     * @return void
270
     */
271 51
    public function setTodir(File $dir)
272
    {
273 51
        $this->destDir = $dir;
274 51
    }
275
276 2
    public function setEnableMultipleMappings($enableMultipleMappings)
277
    {
278 2
        $this->enableMultipleMappings = (bool) $enableMultipleMappings;
279 2
    }
280
281
    public function isEnabledMultipleMappings()
282
    {
283
        return $this->enableMultipleMappings;
284
    }
285
286
    /**
287
     * Set the haltonerror attribute - when true, will
288
     * make the build fail when errors are detected.
289
     *
290
     * @param boolean $haltonerror Flag if the build should be stopped on errors
291
     *
292
     * @return void
293
     */
294 1
    public function setHaltonerror($haltonerror)
295
    {
296 1
        $this->haltonerror = (bool) $haltonerror;
297 1
    }
298
299
    /**
300
     * Nested creator, creates one Mapper for this task
301
     *
302
     * @return Mapper         The created Mapper type object
303
     * @throws BuildException
304
     */
305 3
    public function createMapper()
306
    {
307 3
        if ($this->mapperElement !== null) {
308
            throw new BuildException("Cannot define more than one mapper", $this->getLocation());
309
        }
310 3
        $this->mapperElement = new Mapper($this->project);
311
312 3
        return $this->mapperElement;
313
    }
314
315
    /**
316
     * The main entry point where everything gets in motion.
317
     *
318
     * @return true           on success
319
     * @throws BuildException
320
     */
321 59
    public function main()
322
    {
323 59
        $this->validateAttributes();
324
325 59
        if ($this->file !== null) {
326 15
            if ($this->file->exists()) {
327 14
                if ($this->destFile === null) {
328 7
                    $this->destFile = new File($this->destDir, (string) $this->file->getName());
329
                }
330
                if (
331 14
                    $this->overwrite === true
332 14
                    || ($this->file->lastModified() - $this->granularity > $this->destFile->lastModified())
333
                ) {
334 12
                    $this->fileCopyMap[$this->file->getAbsolutePath()] = $this->destFile->getAbsolutePath();
335
                } else {
336 14
                    $this->log($this->file->getName() . " omitted, " . $this->destFile->getName() . " is up to date");
337
                }
338
            } else {
339
                // terminate build
340 1
                $this->logError("Could not find file " . $this->file->__toString() . " to copy.");
341
            }
342
        }
343
344 59
        $project = $this->getProject();
345
346
        // process filelists
347 59
        foreach ($this->filelists as $fl) {
348 2
            $fromDir = $fl->getDir($project);
349 2
            $srcFiles = $fl->getFiles($project);
350 2
            $srcDirs = [$fl->getDir($project)];
351
352 2
            if (!$this->flatten && $this->mapperElement === null) {
353 2
                $this->completeDirMap[$fromDir->getAbsolutePath()] = $this->destDir->getAbsolutePath();
354
            }
355
356 2
            $this->_scan($fromDir, $this->destDir, $srcFiles, $srcDirs);
357
        }
358
359 59
        foreach ($this->dirsets as $dirset) {
360
            try {
361 1
                $ds = $dirset->getDirectoryScanner($project);
362 1
                $fromDir = $dirset->getDir($project);
363 1
                $srcDirs = $ds->getIncludedDirectories();
364
365 1
                $srcFiles = [];
366 1
                foreach ($srcDirs as $srcDir) {
367 1
                    $srcFiles[] = $srcDir;
368
                }
369
370
                if (
371 1
                    !$this->flatten &&
372 1
                    $this->mapperElement === null &&
373 1
                    $ds->isEverythingIncluded()
374
                ) {
375
                    $this->completeDirMap[$fromDir->getAbsolutePath()] = $this->destDir->getAbsolutePath();
376
                }
377
378 1
                $this->_scan($fromDir, $this->destDir, $srcFiles, $srcDirs);
379
            } catch (BuildException $e) {
380
                if ($this->haltonerror === true) {
381
                    throw $e;
382
                }
383
384
                $this->logError($e->getMessage());
385
            }
386
        }
387
388
        // process filesets
389 59
        foreach ($this->filesets as $fs) {
390
            try {
391 44
                $ds = $fs->getDirectoryScanner($project);
392 40
                $fromDir = $fs->getDir($project);
393 40
                $srcFiles = $ds->getIncludedFiles();
394 40
                $srcDirs = $ds->getIncludedDirectories();
395
396
                if (
397 40
                    !$this->flatten
398 40
                    && $this->mapperElement === null
399 40
                    && $ds->isEverythingIncluded()
400
                ) {
401 18
                    $this->completeDirMap[$fromDir->getAbsolutePath()] = $this->destDir->getAbsolutePath();
402
                }
403
404 40
                $this->_scan($fromDir, $this->destDir, $srcFiles, $srcDirs);
405 4
            } catch (BuildException $e) {
406 4
                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...
407 4
                    throw $e;
408
                }
409
410
                $this->logError($e->getMessage());
411
            }
412
        }
413
414
        // go and copy the stuff
415 55
        $this->doWork();
416
417 55
        if ($this->destFile !== null) {
418 16
            $this->destDir = null;
419
        }
420 55
    }
421
422
    /**
423
     * Validates attributes coming in from XML
424
     *
425
     * @return void
426
     * @throws BuildException
427
     */
428 58
    protected function validateAttributes()
429
    {
430 58
        if ($this->file === null && count($this->dirsets) === 0 && count($this->filesets) === 0 && count($this->filelists) === 0) {
431
            throw new BuildException("CopyTask. Specify at least one source - a file, fileset or filelist.");
432
        }
433
434 58
        if ($this->destFile !== null && $this->destDir !== null) {
435
            throw new BuildException("Only one of destfile and destdir may be set.");
436
        }
437
438 58
        if ($this->destFile === null && $this->destDir === null) {
439
            throw new BuildException("One of destfile or destdir must be set.");
440
        }
441
442 58
        if ($this->file !== null && $this->file->exists() && $this->file->isDirectory()) {
443
            throw new BuildException("Use a fileset to copy directories.");
444
        }
445
446 58
        if ($this->destFile !== null && (count($this->filesets) > 0 || count($this->dirsets) > 0)) {
447
            throw new BuildException("Cannot concatenate multiple files into a single file.");
448
        }
449
450 58
        if ($this->destFile !== null) {
451 8
            $this->destDir = new File($this->destFile->getParent());
452
        }
453 58
    }
454
455
    /**
456
     * Compares source files to destination files to see if they
457
     * should be copied.
458
     *
459
     * @param $fromDir
460
     * @param $toDir
461
     * @param $files
462
     * @param $dirs
463
     *
464
     * @return void
465
     */
466 43
    private function _scan(&$fromDir, &$toDir, &$files, &$dirs)
467
    {
468
        /* mappers should be generic, so we get the mappers here and
469
        pass them on to builMap. This method is not redundan like it seems */
470 43
        $mapper = $this->getMapper();
471
472 43
        $this->buildMap($fromDir, $toDir, $files, $mapper, $this->fileCopyMap);
473
474 43
        if ($this->includeEmpty) {
475 43
            $this->buildMap($fromDir, $toDir, $dirs, $mapper, $this->dirCopyMap);
476
        }
477 43
    }
478
479 43
    private function getMapper()
480
    {
481 43
        $mapper = null;
482 43
        if ($this->mapperElement !== null) {
483 3
            $mapper = $this->mapperElement->getImplementation();
484 40
        } elseif ($this->flatten) {
485
            $mapper = new FlattenMapper();
486
        } else {
487 40
            $mapper = new IdentityMapper();
488
        }
489 43
        return $mapper;
490
    }
491
492
    /**
493
     * Builds a map of filenames (from->to) that should be copied
494
     *
495
     * @param $fromDir
496
     * @param $toDir
497
     * @param $names
498
     * @param FileNameMapper $mapper
499
     * @param $map
500
     *
501
     * @return void
502
     */
503 43
    private function buildMap(&$fromDir, &$toDir, &$names, &$mapper, &$map)
504
    {
505 43
        $toCopy = null;
506 43
        if ($this->overwrite) {
507 3
            $v = [];
508 3
            foreach ($names as $name) {
509 3
                $result = $mapper->main($name);
510 3
                if ($result !== null) {
511 3
                    $v[] = $name;
512
                }
513
            }
514 3
            $toCopy = $v;
515
        } else {
516 40
            $ds = new SourceFileScanner($this);
517 40
            $toCopy = $ds->restrict($names, $fromDir, $toDir, $mapper);
518
        }
519
520 43
        for ($i = 0, $_i = count($toCopy); $i < $_i; $i++) {
521 40
            $src = new File($fromDir, $toCopy[$i]);
522 40
            $mapped = $mapper->main($toCopy[$i]);
523 40
            if (!$this->enableMultipleMappings) {
524 38
                $dest = new File($toDir, $mapped[0]);
525 38
                $map[$src->getAbsolutePath()] = $dest->getAbsolutePath();
526
            } else {
527 2
                $mappedFiles = [];
528
529 2
                foreach ($mapped as $mappedFile) {
530 2
                    if ($mappedFile === null) {
531
                        continue;
532
                    }
533 2
                    $dest = new File($toDir, $mappedFile);
534 2
                    $mappedFiles[] = $dest->getAbsolutePath();
535
                }
536 2
                $map[$src->getAbsolutePath()] = $mappedFiles;
537
            }
538
        }
539 43
    }
540
541
    /**
542
     * Actually copies the files
543
     *
544
     * @return void
545
     * @throws BuildException
546
     */
547 49
    protected function doWork()
548
    {
549
550
        // These "slots" allow filters to retrieve information about the currently-being-process files
551 49
        $fromSlot = $this->getRegisterSlot("currentFromFile");
552 49
        $fromBasenameSlot = $this->getRegisterSlot("currentFromFile.basename");
553
554 49
        $toSlot = $this->getRegisterSlot("currentToFile");
555 49
        $toBasenameSlot = $this->getRegisterSlot("currentToFile.basename");
556
557 49
        $mapSize = count($this->fileCopyMap);
558 49
        $total = $mapSize;
559
560
        // handle empty dirs if appropriate
561 49
        if ($this->includeEmpty) {
562 49
            $count = 0;
563 49
            foreach ($this->dirCopyMap as $srcdir => $destdir) {
564 4
                $s = new File((string) $srcdir);
565 4
                $d = new File((string) $destdir);
566 4
                if (!$d->exists()) {
567
                    // Setting source directory permissions to target
568
                    // (On permissions preservation, the target directory permissions
569
                    // will be inherited from the source directory, otherwise the 'mode'
570
                    // will be used)
571 4
                    $dirMode = ($this->preservePermissions ? $s->getMode() : $this->mode);
572
573
                    // Directory creation with specific permission mode
574 4
                    if (!$d->mkdirs($dirMode)) {
575
                        $this->logError("Unable to create directory " . $d->__toString());
576
                    } else {
577 4
                        if ($this->preserveLMT) {
578
                            $d->setLastModified($s->lastModified());
579
                        }
580
581 4
                        $count++;
582
                    }
583
                }
584
            }
585 49
            if ($count > 0) {
586 4
                $this->log(
587 4
                    "Created " . $count . " empty director" . ($count == 1 ? "y" : "ies") . " in " . $this->destDir->getAbsolutePath()
588
                );
589
            }
590
        }
591
592 49
        if ($mapSize == 0) {
593 4
            return;
594
        }
595
596 45
        $this->log(
597 45
            "Copying " . $mapSize . " file" . (($mapSize) === 1 ? '' : 's') . " to " . $this->destDir->getAbsolutePath()
598
        );
599
        // walks the map and actually copies the files
600 45
        $count = 0;
601 45
        foreach ($this->fileCopyMap as $from => $toFiles) {
602 45
            if (is_array($toFiles)) {
603 2
                foreach ($toFiles as $to) {
604 2
                    $this->copyToSingleDestination(
605 2
                        $from,
606
                        $to,
607
                        $fromSlot,
608
                        $fromBasenameSlot,
609
                        $toSlot,
610
                        $toBasenameSlot,
611
                        $count,
612
                        $total
613
                    );
614
                }
615
            } else {
616 43
                $this->copyToSingleDestination(
617 43
                    $from,
618
                    $toFiles,
619
                    $fromSlot,
620
                    $fromBasenameSlot,
621
                    $toSlot,
622
                    $toBasenameSlot,
623
                    $count,
624
                    $total
625
                );
626
            }
627
        }
628 45
    }
629
630
    /**
631
     * @param $from
632
     * @param $to
633
     * @param RegisterSlot $fromSlot
634
     * @param RegisterSlot $fromBasenameSlot
635
     * @param RegisterSlot $toSlot
636
     * @param RegisterSlot $toBasenameSlot
637
     * @param $count
638
     * @param $total
639
     */
640 45
    private function copyToSingleDestination(
641
        $from,
642
        $to,
643
        $fromSlot,
644
        $fromBasenameSlot,
645
        $toSlot,
646
        $toBasenameSlot,
647
        &$count,
648
        &$total
649
    ) {
650 45
        if ($from === $to) {
651
            $this->log("Skipping self-copy of " . $from, $this->verbosity);
652
            $total--;
653
            return;
654
        }
655 45
        $this->log("From " . $from . " to " . $to, $this->verbosity);
656
        try { // try to copy file
657 45
            $fromFile = new File($from);
658 45
            $toFile = new File($to);
659
660 45
            $fromSlot->setValue($fromFile->getPath());
661 45
            $fromBasenameSlot->setValue($fromFile->getName());
662
663 45
            $toSlot->setValue($toFile->getPath());
664 45
            $toBasenameSlot->setValue($toFile->getName());
665
666 45
            $this->fileUtils->copyFile(
667 45
                $fromFile,
668
                $toFile,
669 45
                $this->getProject(),
670 45
                $this->overwrite,
671 45
                $this->preserveLMT,
672 45
                $this->filterChains,
673 45
                $this->mode,
674 45
                $this->preservePermissions,
675 45
                $this->granularity
676
            );
677
678 45
            $count++;
679
        } catch (IOException $ioe) {
680
            $this->logError("Failed to copy " . $from . " to " . $to . ": " . $ioe->getMessage());
681
        }
682 45
    }
683
684
    /**
685
     * @param string $message
686
     * @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...
687
     *
688
     * @throws BuildException
689
     */
690 1
    protected function logError($message, $location = null)
691
    {
692 1
        if ($this->haltonerror) {
693
            throw new BuildException($message, $location);
694
        }
695
696 1
        $this->log($message, Project::MSG_ERR);
697 1
    }
698
}
699