CopyTask::copyToSingleDestination()   A
last analyzed

Complexity

Conditions 3
Paths 9

Size

Total Lines 43
Code Lines 25

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 21
CRAP Score 3.064

Importance

Changes 0
Metric Value
eloc 25
c 0
b 0
f 0
dl 0
loc 43
ccs 21
cts 26
cp 0.8077
rs 9.52
cc 3
nc 9
nop 8
crap 3.064

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
/**
4
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
5
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
6
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
7
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
8
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
9
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
10
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
11
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
12
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
13
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
14
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
15
 *
16
 * This software consists of voluntary contributions made by many individuals
17
 * and is licensed under the LGPL. For more information please see
18
 * <http://phing.info>.
19
 */
20
21
namespace Phing\Task\System;
22
23
use Phing\Exception\BuildException;
24
use Phing\Io\File;
25
use Phing\Io\FileUtils;
26
use Phing\Io\IOException;
27
use Phing\Io\SourceFileScanner;
28
use Phing\Mapper\FileNameMapper;
29
use Phing\Mapper\FlattenMapper;
30
use Phing\Mapper\IdentityMapper;
31
use Phing\Project;
32
use Phing\Task;
33
use Phing\Type\Element\FilterChainAware;
34
use Phing\Type\Element\ResourceAware;
35
use Phing\Type\Mapper;
36
use Phing\Util\RegisterSlot;
37
38
/**
39
 * A phing copy task.  Copies a file or directory to a new file
40
 * or directory.  Files are only copied if the source file is newer
41
 * than the destination file, or when the destination file does not
42
 * exist. It is possible to explicitly overwrite existing files.
43
 *
44
 * @author Andreas Aderhold, [email protected]
45
 */
46
class CopyTask extends Task
47
{
48
    use ResourceAware;
49
    use FilterChainAware;
50
51
    /**
52
     * @var File
53
     */
54
    protected $file; // the source file (from xml attribute)
55
56
    /**
57
     * @var File
58
     */
59
    protected $destFile; // the destiantion file (from xml attribute)
60
61
    /**
62
     * @var File
63
     */
64
    protected $destDir; // the destination dir (from xml attribute)
65
66
    protected $overwrite = false; // overwrite destination (from xml attribute)
67
    protected $preserveLMT = false; // sync timestamps (from xml attribute)
68
    protected $preservePermissions = true; // sync permissions (from xml attribute)
69
    protected $includeEmpty = true; // include empty dirs? (from XML)
70
    protected $flatten = false; // apply the FlattenMapper right way (from XML)
71
72
    /**
73
     * @var Mapper
74
     */
75
    protected $mapperElement;
76
77
    protected $fileCopyMap = []; // asoc array containing mapped file names
78
    protected $dirCopyMap = []; // asoc array containing mapped file names
79
    protected $completeDirMap = []; // asoc array containing complete dir names
80
81
    /**
82
     * @var FileUtils
83
     */
84
    protected $fileUtils; // a instance of fileutils
85
86
    protected $verbosity = Project::MSG_VERBOSE;
87
88
    /**
89
     * @var int
90
     */
91
    protected $mode = 0; // mode to create directories with
92
93
    /**
94
     * @var bool
95
     */
96
    protected $haltonerror = true; // stop build on errors
97
98
    protected $enableMultipleMappings = false;
99
100
    /** @var int */
101
    protected $granularity = 0;
102
103
    /**
104
     * Sets up this object internal stuff.
105
     * i.e. the Fileutils instance and default mode.
106
     */
107 66
    public function __construct()
108
    {
109 66
        parent::__construct();
110 66
        $this->fileUtils = new FileUtils();
111 66
        $this->mode = 0777 - umask();
112
    }
113
114
    /**
115
     * Set the number of seconds leeway to give before deciding a
116
     * target is out of date.
117
     *
118
     * @param int $granularity the granularity used to decide if a target is out of date
119
     */
120 2
    public function setGranularity(int $granularity): void
121
    {
122 2
        $this->granularity = $granularity;
123
    }
124
125
    /**
126
     * Set the overwrite flag. IntrospectionHelper takes care of
127
     * booleans in set* methods so we can assume that the right
128
     * value (bool primitive) is coming in here.
129
     *
130
     * @param bool $bool Overwrite the destination file(s) if it/they already exist
131
     */
132 12
    public function setOverwrite($bool)
133
    {
134 12
        $this->overwrite = (bool) $bool;
135
    }
136
137
    /**
138
     * Set whether files copied from directory trees will be "flattened"
139
     * into a single directory.  If there are multiple files with
140
     * the same name in the source directory tree, only the first
141
     * file will be copied into the "flattened" directory, unless
142
     * the forceoverwrite attribute is true.
143
     *
144
     * @param bool $flatten if true flatten the destination directory. Default
145
     *                      is false.
146
     */
147
    public function setFlatten($flatten)
148
    {
149
        $this->flatten = $flatten;
150
    }
151
152
    /**
153
     * Used to force listing of all names of copied files.
154
     *
155
     * @param bool $verbosity
156
     */
157 1
    public function setVerbose($verbosity)
158
    {
159 1
        if ($verbosity) {
160 1
            $this->verbosity = Project::MSG_INFO;
161
        } else {
162
            $this->verbosity = Project::MSG_VERBOSE;
163
        }
164
    }
165
166
    /**
167
     * @see CopyTask::setPreserveLastModified
168
     */
169
    public function setTstamp(bool $preserveLastModified)
170
    {
171
        $this->setPreserveLastModified($preserveLastModified);
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 (bool primitive) is coming in here.
178
     */
179 1
    public function setPreserveLastModified(bool $preserveLastModified)
180
    {
181 1
        $this->preserveLMT = $preserveLastModified;
182
    }
183
184
    /**
185
     * Set the preserve permissions flag. IntrospectionHelper takes care of
186
     * booleans in set* methods so we can assume that the right
187
     * value (bool primitive) is coming in here.
188
     */
189
    public function setPreservepermissions(bool $preservePermissions)
190
    {
191
        $this->preservePermissions = $preservePermissions;
192
    }
193
194
    public function setPreservemode(bool $preserveMode)
195
    {
196
        $this->setPreservepermissions($preserveMode);
197
    }
198
199
    /**
200
     * Set the include empty dirs flag. IntrospectionHelper takes care of
201
     * booleans in set* methods so we can assume that the right
202
     * value (bool primitive) is coming in here.
203
     */
204
    public function setIncludeEmptyDirs(bool $includeEmptyDirs)
205
    {
206
        $this->includeEmpty = $includeEmptyDirs;
207
    }
208
209
    /**
210
     * Set the file. We have to manually take care of the
211
     * type that is coming due to limited type support in php
212
     * in and convert it manually if necessary.
213
     *
214
     * @param File $file The source file. Either a string or an PhingFile object
215
     */
216 18
    public function setFile(File $file)
217
    {
218 18
        $this->file = $file;
219
    }
220
221
    /**
222
     * Set the toFile. 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 dest file. Either a string or an PhingFile object
227
     */
228 9
    public function setTofile(File $file)
229
    {
230 9
        $this->destFile = $file;
231
    }
232
233
    /**
234
     * Sets the mode to create destination directories with (ignored on Windows).
235
     * Default mode is taken from umask().
236
     *
237
     * @param int $mode Octal mode
238
     */
239
    public function setMode($mode)
240
    {
241
        $this->mode = (int) base_convert($mode, 8, 10);
242
    }
243
244
    /**
245
     * Set the toDir. We have to manually take care of the
246
     * type that is coming due to limited type support in php
247
     * in and convert it manually if necessary.
248
     *
249
     * @param File $dir The directory, either a string or an PhingFile object
250
     */
251 57
    public function setTodir(File $dir)
252
    {
253 57
        $this->destDir = $dir;
254
    }
255
256 2
    public function setEnableMultipleMappings($enableMultipleMappings)
257
    {
258 2
        $this->enableMultipleMappings = (bool) $enableMultipleMappings;
259
    }
260
261
    public function isEnabledMultipleMappings()
262
    {
263
        return $this->enableMultipleMappings;
264
    }
265
266
    /**
267
     * Set the haltonerror attribute - when true, will
268
     * make the build fail when errors are detected.
269
     *
270
     * @param bool $haltonerror Flag if the build should be stopped on errors
271
     */
272 2
    public function setHaltonerror($haltonerror)
273
    {
274 2
        $this->haltonerror = (bool) $haltonerror;
275
    }
276
277
    /**
278
     * Nested creator, creates one Mapper for this task.
279
     *
280
     * @throws BuildException
281
     *
282
     * @return Mapper The created Mapper type object
283
     */
284 6
    public function createMapper()
285
    {
286 6
        if (null !== $this->mapperElement) {
287
            throw new BuildException('Cannot define more than one mapper', $this->getLocation());
288
        }
289 6
        $this->mapperElement = new Mapper($this->project);
290
291 6
        return $this->mapperElement;
292
    }
293
294
    /**
295
     * The main entry point where everything gets in motion.
296
     *
297
     * @throws BuildException
298
     */
299 65
    public function main()
300
    {
301 65
        $this->validateAttributes();
302
303 65
        if (null !== $this->file) {
304 17
            if ($this->file->exists()) {
305 16
                if (null === $this->destFile) {
306 9
                    $this->destFile = new File($this->destDir, (string) $this->file->getName());
307
                }
308
                if (
309 16
                    true === $this->overwrite
310 16
                    || ($this->file->lastModified() - $this->granularity > $this->destFile->lastModified())
311
                ) {
312 14
                    $this->fileCopyMap[$this->file->getAbsolutePath()] = $this->destFile->getAbsolutePath();
313
                } else {
314 2
                    $this->log($this->file->getName() . ' omitted, ' . $this->destFile->getName() . ' is up to date');
315
                }
316
            } else {
317
                // terminate build
318 1
                $this->logError('Could not find file ' . $this->file->__toString() . ' to copy.');
319
            }
320
        }
321
322 65
        $project = $this->getProject();
323
324
        // process filelists
325 65
        foreach ($this->filelists as $fl) {
326 2
            $fromDir = $fl->getDir($project);
327 2
            $srcFiles = $fl->getFiles($project);
328 2
            $srcDirs = [$fl->getDir($project)];
329
330 2
            if (!$this->flatten && null === $this->mapperElement) {
331 2
                $this->completeDirMap[$fromDir->getAbsolutePath()] = $this->destDir->getAbsolutePath();
332
            }
333
334 2
            $this->scan($fromDir, $this->destDir, $srcFiles, $srcDirs);
335
        }
336
337 65
        foreach ($this->dirsets as $dirset) {
338
            try {
339 1
                $ds = $dirset->getDirectoryScanner($project);
340 1
                $fromDir = $dirset->getDir($project);
341 1
                $srcDirs = $ds->getIncludedDirectories();
342
343 1
                $srcFiles = [];
344 1
                foreach ($srcDirs as $srcDir) {
345 1
                    $srcFiles[] = $srcDir;
346
                }
347
348
                if (
349 1
                    !$this->flatten
350 1
                    && null === $this->mapperElement
351 1
                    && $ds->isEverythingIncluded()
352
                ) {
353
                    $this->completeDirMap[$fromDir->getAbsolutePath()] = $this->destDir->getAbsolutePath();
354
                }
355
356 1
                $this->scan($fromDir, $this->destDir, $srcFiles, $srcDirs);
357
            } catch (BuildException $e) {
358
                if (true === $this->haltonerror) {
359
                    throw $e;
360
                }
361
362
                $this->logError($e->getMessage());
363
            }
364
        }
365
366
        // process filesets
367 65
        foreach ($this->filesets as $fs) {
368
            try {
369 48
                $ds = $fs->getDirectoryScanner($project);
370 44
                $fromDir = $fs->getDir($project);
371 44
                $srcFiles = $ds->getIncludedFiles();
372 44
                $srcDirs = $ds->getIncludedDirectories();
373
374
                if (
375 44
                    !$this->flatten
376 44
                    && null === $this->mapperElement
377 44
                    && $ds->isEverythingIncluded()
378
                ) {
379 18
                    $this->completeDirMap[$fromDir->getAbsolutePath()] = $this->destDir->getAbsolutePath();
380
                }
381
382 44
                $this->scan($fromDir, $this->destDir, $srcFiles, $srcDirs);
383 4
            } catch (BuildException $e) {
384 4
                if (true == $this->haltonerror) {
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...
385 4
                    throw $e;
386
                }
387
388
                $this->logError($e->getMessage());
389
            }
390
        }
391
392
        // go and copy the stuff
393 61
        $this->doWork();
394
395 61
        if (null !== $this->destFile) {
396 18
            $this->destDir = null;
397
        }
398
    }
399
400
    /**
401
     * Validates attributes coming in from XML.
402
     *
403
     * @throws BuildException
404
     */
405 64
    protected function validateAttributes()
406
    {
407 64
        if (null === $this->file && 0 === count($this->dirsets) && 0 === count($this->filesets) && 0 === count($this->filelists)) {
408
            throw new BuildException('CopyTask. Specify at least one source - a file, fileset or filelist.');
409
        }
410
411 64
        if (null !== $this->destFile && null !== $this->destDir) {
412
            throw new BuildException('Only one of destfile and destdir may be set.');
413
        }
414
415 64
        if (null === $this->destFile && null === $this->destDir) {
416
            throw new BuildException('One of destfile or destdir must be set.');
417
        }
418
419 64
        if (null !== $this->file && $this->file->exists() && $this->file->isDirectory()) {
420
            throw new BuildException('Use a fileset to copy directories.');
421
        }
422
423 64
        if (null !== $this->destFile && (count($this->filesets) > 0 || count($this->dirsets) > 0)) {
424
            throw new BuildException('Cannot concatenate multiple files into a single file.');
425
        }
426
427 64
        if (null !== $this->destFile) {
428 8
            $this->destDir = new File($this->destFile->getParent());
429
        }
430
    }
431
432
    /**
433
     * Actually copies the files.
434
     *
435
     * @throws BuildException
436
     */
437 55
    protected function doWork()
438
    {
439
        // These "slots" allow filters to retrieve information about the currently-being-process files
440 55
        $fromSlot = $this->getRegisterSlot('currentFromFile');
441 55
        $fromBasenameSlot = $this->getRegisterSlot('currentFromFile.basename');
442
443 55
        $toSlot = $this->getRegisterSlot('currentToFile');
444 55
        $toBasenameSlot = $this->getRegisterSlot('currentToFile.basename');
445
446 55
        $mapSize = count($this->fileCopyMap);
447 55
        $total = $mapSize;
448
449
        // handle empty dirs if appropriate
450 55
        if ($this->includeEmpty) {
451 55
            $count = 0;
452 55
            foreach ($this->dirCopyMap as $srcdir => $destdir) {
453 4
                $s = new File((string) $srcdir);
454 4
                $d = new File((string) $destdir);
455 4
                if (!$d->exists()) {
456
                    // Setting source directory permissions to target
457
                    // (On permissions preservation, the target directory permissions
458
                    // will be inherited from the source directory, otherwise the 'mode'
459
                    // will be used)
460 4
                    $dirMode = ($this->preservePermissions ? $s->getMode() : $this->mode);
461
462
                    // Directory creation with specific permission mode
463 4
                    if (!$d->mkdirs($dirMode)) {
464
                        $this->logError('Unable to create directory ' . $d->__toString());
465
                    } else {
466 4
                        if ($this->preserveLMT) {
467
                            $d->setLastModified($s->lastModified());
468
                        }
469
470 4
                        ++$count;
471
                    }
472
                }
473
            }
474 55
            if ($count > 0) {
475 4
                $this->log(
476 4
                    'Created ' . $count . ' empty director' . (1 == $count ? 'y' : 'ies') . ' in ' . $this->destDir->getAbsolutePath()
477 4
                );
478
            }
479
        }
480
481 55
        if (0 == $mapSize) {
482 6
            return;
483
        }
484
485 49
        $this->log(
486 49
            'Copying ' . $mapSize . ' file' . (($mapSize) === 1 ? '' : 's') . ' to ' . $this->destDir->getAbsolutePath()
487 49
        );
488
        // walks the map and actually copies the files
489 49
        $count = 0;
490 49
        foreach ($this->fileCopyMap as $from => $toFiles) {
491 49
            if (is_array($toFiles)) {
492 2
                foreach ($toFiles as $to) {
493 2
                    $this->copyToSingleDestination(
494 2
                        $from,
495 2
                        $to,
496 2
                        $fromSlot,
497 2
                        $fromBasenameSlot,
498 2
                        $toSlot,
499 2
                        $toBasenameSlot,
500 2
                        $count,
501 2
                        $total
502 2
                    );
503
                }
504
            } else {
505 47
                $this->copyToSingleDestination(
506 47
                    $from,
507 47
                    $toFiles,
508 47
                    $fromSlot,
509 47
                    $fromBasenameSlot,
510 47
                    $toSlot,
511 47
                    $toBasenameSlot,
512 47
                    $count,
513 47
                    $total
514 47
                );
515
            }
516
        }
517
    }
518
519
    /**
520
     * @param string $message
521
     * @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...
522
     *
523
     * @throws BuildException
524
     */
525 1
    protected function logError($message, $location = null)
526
    {
527 1
        if ($this->haltonerror) {
528
            throw new BuildException($message, $location);
529
        }
530
531 1
        $this->log($message, Project::MSG_ERR);
532
    }
533
534
    /**
535
     * Compares source files to destination files to see if they
536
     * should be copied.
537
     *
538
     * @param string $fromDir
539
     * @param string $toDir
540
     * @param array  $files
541
     * @param array  $dirs
542
     */
543 47
    private function scan(&$fromDir, &$toDir, &$files, &$dirs)
544
    {
545
        /* mappers should be generic, so we get the mappers here and
546
        pass them on to builMap. This method is not redundan like it seems */
547 47
        $mapper = $this->getMapper();
548
549 47
        $this->buildMap($fromDir, $toDir, $files, $mapper, $this->fileCopyMap);
550
551 47
        if ($this->includeEmpty) {
552 47
            $this->buildMap($fromDir, $toDir, $dirs, $mapper, $this->dirCopyMap);
553
        }
554
    }
555
556 47
    private function getMapper()
557
    {
558 47
        $mapper = null;
559 47
        if (null !== $this->mapperElement) {
560 6
            $mapper = $this->mapperElement->getImplementation();
561 41
        } elseif ($this->flatten) {
562
            $mapper = new FlattenMapper();
563
        } else {
564 41
            $mapper = new IdentityMapper();
565
        }
566
567 47
        return $mapper;
568
    }
569
570
    /**
571
     * Builds a map of filenames (from->to) that should be copied.
572
     *
573
     * @param string         $fromDir
574
     * @param string         $toDir
575
     * @param array          $names
576
     * @param FileNameMapper $mapper
577
     * @param array          $map
578
     */
579 47
    private function buildMap($fromDir, $toDir, &$names, $mapper, &$map)
580
    {
581 47
        $toCopy = null;
582 47
        if ($this->overwrite) {
583 3
            $v = [];
584 3
            foreach ($names as $name) {
585 3
                $result = $mapper->main($name);
586 3
                if (null !== $result) {
587 3
                    $v[] = $name;
588
                }
589
            }
590 3
            $toCopy = $v;
591
        } else {
592 44
            $ds = new SourceFileScanner($this);
593 44
            $toCopy = $ds->restrict($names, $fromDir, $toDir, $mapper);
0 ignored issues
show
Bug introduced by
$toDir of type string is incompatible with the type Phing\Io\File expected by parameter $destDir of Phing\Io\SourceFileScanner::restrict(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

593
            $toCopy = $ds->restrict($names, $fromDir, /** @scrutinizer ignore-type */ $toDir, $mapper);
Loading history...
Bug introduced by
$fromDir of type string is incompatible with the type Phing\Io\File expected by parameter $srcDir of Phing\Io\SourceFileScanner::restrict(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

593
            $toCopy = $ds->restrict($names, /** @scrutinizer ignore-type */ $fromDir, $toDir, $mapper);
Loading history...
594
        }
595
596 47
        for ($i = 0, $_i = count($toCopy); $i < $_i; ++$i) {
597 42
            $src = new File($fromDir, $toCopy[$i]);
598 42
            $mapped = $mapper->main($toCopy[$i]);
599 42
            if (!$this->enableMultipleMappings) {
600 40
                $dest = new File($toDir, $mapped[0]);
601 40
                $map[$src->getAbsolutePath()] = $dest->getAbsolutePath();
602
            } else {
603 2
                $mappedFiles = [];
604
605 2
                foreach ($mapped as $mappedFile) {
606 2
                    if (null === $mappedFile) {
607
                        continue;
608
                    }
609 2
                    $dest = new File($toDir, $mappedFile);
610 2
                    $mappedFiles[] = $dest->getAbsolutePath();
611
                }
612 2
                $map[$src->getAbsolutePath()] = $mappedFiles;
613
            }
614
        }
615
    }
616
617
    /**
618
     * @param string       $from
619
     * @param string       $to
620
     * @param RegisterSlot $fromSlot
621
     * @param RegisterSlot $fromBasenameSlot
622
     * @param RegisterSlot $toSlot
623
     * @param RegisterSlot $toBasenameSlot
624
     * @param int          $count
625
     * @param int          $total
626
     */
627 49
    private function copyToSingleDestination(
628
        $from,
629
        $to,
630
        $fromSlot,
631
        $fromBasenameSlot,
632
        $toSlot,
633
        $toBasenameSlot,
634
        &$count,
635
        &$total
636
    ) {
637 49
        if ($from === $to) {
638
            $this->log('Skipping self-copy of ' . $from, $this->verbosity);
639
            --$total;
640
641
            return;
642
        }
643 49
        $this->log('From ' . $from . ' to ' . $to, $this->verbosity);
644
645
        try { // try to copy file
646 49
            $fromFile = new File($from);
647 49
            $toFile = new File($to);
648
649 49
            $fromSlot->setValue($fromFile->getPath());
650 49
            $fromBasenameSlot->setValue($fromFile->getName());
651
652 49
            $toSlot->setValue($toFile->getPath());
653 49
            $toBasenameSlot->setValue($toFile->getName());
654
655 49
            $this->fileUtils->copyFile(
656 49
                $fromFile,
657 49
                $toFile,
658 49
                $this->getProject(),
659 49
                $this->overwrite,
660 49
                $this->preserveLMT,
661 49
                $this->filterChains,
662 49
                $this->mode,
663 49
                $this->preservePermissions,
664 49
                $this->granularity
665 49
            );
666
667 49
            ++$count;
668
        } catch (IOException $ioe) {
669
            $this->logError('Failed to copy ' . $from . ' to ' . $to . ': ' . $ioe->getMessage());
670
        }
671
    }
672
}
673