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

CopyTask::copyToSingleDestination()   A

Complexity

Conditions 3
Paths 9

Size

Total Lines 41
Code Lines 25

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 19
CRAP Score 3.0813

Importance

Changes 0
Metric Value
cc 3
eloc 25
nc 9
nop 8
dl 0
loc 41
ccs 19
cts 24
cp 0.7917
crap 3.0813
rs 9.52
c 0
b 0
f 0

How to fix   Many Parameters   

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
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