Passed
Push — main ( c551fb...edb315 )
by Siad
06:32
created

CopyTask::copyToSingleDestination()   A

Complexity

Conditions 3
Paths 9

Size

Total Lines 43
Code Lines 25

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 19
CRAP Score 3.0813

Importance

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

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 60
    public function __construct()
108
    {
109 60
        parent::__construct();
110 60
        $this->fileUtils = new FileUtils();
111 60
        $this->mode = 0777 - umask();
112 60
    }
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 2
    }
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 11
    public function setOverwrite($bool)
133
    {
134 11
        $this->overwrite = (bool) $bool;
135 11
    }
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 1
    }
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 1
    }
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 16
    public function setFile(File $file)
217
    {
218 16
        $this->file = $file;
219 16
    }
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 9
    }
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 51
    public function setTodir(File $dir)
252
    {
253 51
        $this->destDir = $dir;
254 51
    }
255
256 2
    public function setEnableMultipleMappings($enableMultipleMappings)
257
    {
258 2
        $this->enableMultipleMappings = (bool) $enableMultipleMappings;
259 2
    }
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 1
    public function setHaltonerror($haltonerror)
273
    {
274 1
        $this->haltonerror = (bool) $haltonerror;
275 1
    }
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 3
    public function createMapper()
285
    {
286 3
        if (null !== $this->mapperElement) {
287
            throw new BuildException('Cannot define more than one mapper', $this->getLocation());
288
        }
289 3
        $this->mapperElement = new Mapper($this->project);
290
291 3
        return $this->mapperElement;
292
    }
293
294
    /**
295
     * The main entry point where everything gets in motion.
296
     *
297
     * @throws BuildException
298
     */
299 59
    public function main()
300
    {
301 59
        $this->validateAttributes();
302
303 59
        if (null !== $this->file) {
304 15
            if ($this->file->exists()) {
305 14
                if (null === $this->destFile) {
306 7
                    $this->destFile = new File($this->destDir, (string) $this->file->getName());
307
                }
308
                if (
309 14
                    true === $this->overwrite
310 14
                    || ($this->file->lastModified() - $this->granularity > $this->destFile->lastModified())
311
                ) {
312 12
                    $this->fileCopyMap[$this->file->getAbsolutePath()] = $this->destFile->getAbsolutePath();
313
                } else {
314 14
                    $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 59
        $project = $this->getProject();
323
324
        // process filelists
325 59
        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 59
        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 59
        foreach ($this->filesets as $fs) {
368
            try {
369 44
                $ds = $fs->getDirectoryScanner($project);
370 40
                $fromDir = $fs->getDir($project);
371 40
                $srcFiles = $ds->getIncludedFiles();
372 40
                $srcDirs = $ds->getIncludedDirectories();
373
374
                if (
375 40
                    !$this->flatten
376 40
                    && null === $this->mapperElement
377 40
                    && $ds->isEverythingIncluded()
378
                ) {
379 18
                    $this->completeDirMap[$fromDir->getAbsolutePath()] = $this->destDir->getAbsolutePath();
380
                }
381
382 40
                $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 55
        $this->doWork();
394
395 55
        if (null !== $this->destFile) {
396 16
            $this->destDir = null;
397
        }
398 55
    }
399
400
    /**
401
     * Validates attributes coming in from XML.
402
     *
403
     * @throws BuildException
404
     */
405 58
    protected function validateAttributes()
406
    {
407 58
        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 58
        if (null !== $this->destFile && null !== $this->destDir) {
412
            throw new BuildException('Only one of destfile and destdir may be set.');
413
        }
414
415 58
        if (null === $this->destFile && null === $this->destDir) {
416
            throw new BuildException('One of destfile or destdir must be set.');
417
        }
418
419 58
        if (null !== $this->file && $this->file->exists() && $this->file->isDirectory()) {
420
            throw new BuildException('Use a fileset to copy directories.');
421
        }
422
423 58
        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 58
        if (null !== $this->destFile) {
428 8
            $this->destDir = new File($this->destFile->getParent());
429
        }
430 58
    }
431
432
    /**
433
     * Actually copies the files.
434
     *
435
     * @throws BuildException
436
     */
437 49
    protected function doWork()
438
    {
439
        // These "slots" allow filters to retrieve information about the currently-being-process files
440 49
        $fromSlot = $this->getRegisterSlot('currentFromFile');
441 49
        $fromBasenameSlot = $this->getRegisterSlot('currentFromFile.basename');
442
443 49
        $toSlot = $this->getRegisterSlot('currentToFile');
444 49
        $toBasenameSlot = $this->getRegisterSlot('currentToFile.basename');
445
446 49
        $mapSize = count($this->fileCopyMap);
447 49
        $total = $mapSize;
448
449
        // handle empty dirs if appropriate
450 49
        if ($this->includeEmpty) {
451 49
            $count = 0;
452 49
            foreach ($this->dirCopyMap as $srcdir => $destdir) {
453 5
                $s = new File((string) $srcdir);
454 5
                $d = new File((string) $destdir);
455 5
                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 49
            if ($count > 0) {
475 4
                $this->log(
476 4
                    'Created ' . $count . ' empty director' . (1 == $count ? 'y' : 'ies') . ' in ' . $this->destDir->getAbsolutePath()
477
                );
478
            }
479
        }
480
481 49
        if (0 == $mapSize) {
482 4
            return;
483
        }
484
485 45
        $this->log(
486 45
            'Copying ' . $mapSize . ' file' . (($mapSize) === 1 ? '' : 's') . ' to ' . $this->destDir->getAbsolutePath()
487
        );
488
        // walks the map and actually copies the files
489 45
        $count = 0;
490 45
        foreach ($this->fileCopyMap as $from => $toFiles) {
491 45
            if (is_array($toFiles)) {
492 2
                foreach ($toFiles as $to) {
493 2
                    $this->copyToSingleDestination(
494 2
                        $from,
495
                        $to,
496
                        $fromSlot,
497
                        $fromBasenameSlot,
498
                        $toSlot,
499
                        $toBasenameSlot,
500
                        $count,
501
                        $total
502
                    );
503
                }
504
            } else {
505 43
                $this->copyToSingleDestination(
506 43
                    $from,
507
                    $toFiles,
508
                    $fromSlot,
509
                    $fromBasenameSlot,
510
                    $toSlot,
511
                    $toBasenameSlot,
512
                    $count,
513
                    $total
514
                );
515
            }
516
        }
517 45
    }
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 1
    }
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 43
    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 43
        $mapper = $this->getMapper();
548
549 43
        $this->buildMap($fromDir, $toDir, $files, $mapper, $this->fileCopyMap);
550
551 43
        if ($this->includeEmpty) {
552 43
            $this->buildMap($fromDir, $toDir, $dirs, $mapper, $this->dirCopyMap);
553
        }
554 43
    }
555
556 43
    private function getMapper()
557
    {
558 43
        $mapper = null;
559 43
        if (null !== $this->mapperElement) {
560 3
            $mapper = $this->mapperElement->getImplementation();
561 40
        } elseif ($this->flatten) {
562
            $mapper = new FlattenMapper();
563
        } else {
564 40
            $mapper = new IdentityMapper();
565
        }
566
567 43
        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 43
    private function buildMap($fromDir, $toDir, &$names, $mapper, &$map)
580
    {
581 43
        $toCopy = null;
582 43
        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 40
            $ds = new SourceFileScanner($this);
593 40
            $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 43
        for ($i = 0, $_i = count($toCopy); $i < $_i; ++$i) {
597 41
            $src = new File($fromDir, $toCopy[$i]);
598 41
            $mapped = $mapper->main($toCopy[$i]);
599 41
            if (!$this->enableMultipleMappings) {
600 39
                $dest = new File($toDir, $mapped[0]);
601 39
                $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 43
    }
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 45
    private function copyToSingleDestination(
628
        $from,
629
        $to,
630
        $fromSlot,
631
        $fromBasenameSlot,
632
        $toSlot,
633
        $toBasenameSlot,
634
        &$count,
635
        &$total
636
    ) {
637 45
        if ($from === $to) {
638
            $this->log('Skipping self-copy of ' . $from, $this->verbosity);
639
            --$total;
640
641
            return;
642
        }
643 45
        $this->log('From ' . $from . ' to ' . $to, $this->verbosity);
644
645
        try { // try to copy file
646 45
            $fromFile = new File($from);
647 45
            $toFile = new File($to);
648
649 45
            $fromSlot->setValue($fromFile->getPath());
650 45
            $fromBasenameSlot->setValue($fromFile->getName());
651
652 45
            $toSlot->setValue($toFile->getPath());
653 45
            $toBasenameSlot->setValue($toFile->getName());
654
655 45
            $this->fileUtils->copyFile(
656 45
                $fromFile,
657
                $toFile,
658 45
                $this->getProject(),
659 45
                $this->overwrite,
660 45
                $this->preserveLMT,
661 45
                $this->filterChains,
662 45
                $this->mode,
663 45
                $this->preservePermissions,
664 45
                $this->granularity
665
            );
666
667 45
            ++$count;
668
        } catch (IOException $ioe) {
669
            $this->logError('Failed to copy ' . $from . ' to ' . $to . ': ' . $ioe->getMessage());
670
        }
671 45
    }
672
}
673