Completed
Push — master ( 20b0ec...0fa80a )
by Siad
15:26
created

CopyTask::main()   F

Complexity

Conditions 23
Paths 9936

Size

Total Lines 95
Code Lines 56

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 41
CRAP Score 26.9892

Importance

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