Passed
Push — main ( b7649a...398f47 )
by Michiel
06:32
created

FileSyncTask::getCommand()   F

Complexity

Conditions 12
Paths 1536

Size

Total Lines 57
Code Lines 30

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 156

Importance

Changes 0
Metric Value
eloc 30
dl 0
loc 57
ccs 0
cts 30
cp 0
rs 2.8
c 0
b 0
f 0
cc 12
nc 1536
nop 0
crap 156

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
namespace Phing\Task\Optional;
21
22
use Phing\Exception\BuildException;
23
use Phing\Project;
24
use Phing\Task;
25
26
/**
27
 * The FileSyncTask class copies files either to or from a remote host, or locally
28
 * on the current host. It allows rsync to transfer the differences between two
29
 * sets of files across the network connection, using an efficient checksum-search
30
 * algorithm.
31
 *
32
 * There are 4 different ways of using FileSyncTask:
33
 *
34
 *   1. For copying local files.
35
 *   2. For copying from the local machine to a remote machine using a remote
36
 *      shell program as the transport (ssh).
37
 *   3. For copying from a remote machine to the local machine using a remote
38
 *      shell program.
39
 *   4. For listing files on a remote machine.
40
 *
41
 * This is extended from Federico's original code, all his docs are kept in here below.
42
 *
43
 * @author  Federico Cargnelutti <[email protected]>
44
 * @author  Anton Stöckl <[email protected]>
45
 * @version $Revision$
46
 * @package phing.tasks.ext
47
 * @see     http://svn.fedecarg.com/repo/Phing/tasks/ext/FileSyncTask.php
48
 * @example http://fedecarg.com/wiki/FileSyncTask
49
 */
50
class FileSyncTask extends Task
51
{
52
    /**
53
     * Path to rsync command.
54
     *
55
     * @var string
56
     */
57
    protected $rsyncPath = '/usr/bin/rsync';
58
59
    /**
60
     * Source directory.
61
     * For remote sources this must contain user and host, e.g.: user@host:/my/source/dir
62
     *
63
     * @var string
64
     */
65
    protected $sourceDir;
66
67
    /**
68
     * Destination directory.
69
     * For remote targets this must contain user and host, e.g.: user@host:/my/target/dir
70
     *
71
     * @var string
72
     */
73
    protected $destinationDir;
74
75
    /**
76
     * Remote host.
77
     *
78
     * @var string
79
     */
80
    protected $remoteHost;
81
82
    /**
83
     * Rsync auth username.
84
     *
85
     * @var string
86
     */
87
    protected $remoteUser;
88
89
    /**
90
     * Rsync auth password.
91
     *
92
     * @var string
93
     */
94
    protected $remotePass;
95
96
    /**
97
     * Remote shell.
98
     *
99
     * @var string
100
     */
101
    protected $remoteShell;
102
103
    /**
104
     * Exclude file matching pattern.
105
     * Use comma seperated values to exclude multiple files/directories, e.g.: a,b
106
     *
107
     * @var string
108
     */
109
    protected $exclude;
110
111
    /**
112
     * Excluded patterns file.
113
     *
114
     * @var string
115
     */
116
    protected $excludeFile;
117
118
    /**
119
     * This option creates a backup so users can rollback to an existing restore
120
     * point. The remote directory is copied to a new directory specified by the
121
     * user.
122
     *
123
     * @var string
124
     */
125
    protected $backupDir;
126
127
    /**
128
     * Default command options.
129
     * r - recursive
130
     * p - preserve permissions
131
     * K - treat symlinked dir on receiver as dir
132
     * z - compress
133
     * l - copy symlinks as symlinks
134
     *
135
     * @var string
136
     */
137
    protected $defaultOptions = '-rpKzl';
138
139
    /**
140
     * Command options.
141
     *
142
     * @var string
143
     */
144
    protected $options;
145
146
    /**
147
     * Connection type.
148
     *
149
     * @var boolean
150
     */
151
    protected $isRemoteConnection = false;
152
153
    /**
154
     * This option increases the amount of information you are given during the
155
     * transfer. The verbose option set to true will give you information about
156
     * what files are being transferred and a brief summary at the end.
157
     *
158
     * @var boolean
159
     */
160
    protected $verbose = true;
161
162
    /**
163
     * This option makes rsync perform a trial run that doesn’t make any changes
164
     * (and produces mostly the same output as a real run).
165
     *
166
     * @var boolean
167
     */
168
    protected $dryRun = false;
169
170
    /**
171
     * This option makes requests a simple itemized list of the changes that are
172
     * being made to each file, including attribute changes.
173
     *
174
     * @var boolean
175
     */
176
    protected $itemizeChanges = false;
177
178
    /**
179
     * This option will cause rsync to skip files based on checksum, not mod-time & size.
180
     *
181
     * @var boolean
182
     */
183
    protected $checksum = false;
184
185
    /**
186
     * This option deletes files that don't exist on sender.
187
     *
188
     * @var boolean
189
     */
190
    protected $delete = false;
191
192
    /**
193
     * Identity file.
194
     *
195
     * @var string
196
     */
197
    protected $identityFile;
198
199
    /**
200
     * Remote port for syncing via SSH.
201
     *
202
     * @var int
203
     */
204
    protected $remotePort = 22;
205
206
    /**
207
     * Phing's main method. Wraps the executeCommand() method.
208
     *
209
     * @return void
210
     */
211 4
    public function main()
212
    {
213 4
        $this->executeCommand();
214
    }
215
216
    /**
217
     * Executes the rsync command and returns the exit code.
218
     *
219
     * @return int            Return code from execution.
220
     * @throws BuildException
221
     */
222 4
    public function executeCommand()
223
    {
224 4
        if ($this->rsyncPath === null) {
225
            throw new BuildException('The "rsyncPath" attribute is missing or undefined.');
226
        }
227
228 4
        if ($this->sourceDir === null) {
229 1
            throw new BuildException('The "sourcedir" attribute is missing or undefined.');
230
        }
231
232 3
        if ($this->destinationDir === null) {
233 1
            throw new BuildException('The "destinationdir" attribute is missing or undefined.');
234
        }
235
236 2
        if (strpos($this->destinationDir, ':')) {
237 1
            $this->setIsRemoteConnection(true);
238
        }
239
240 2
        if (strpos($this->sourceDir, ':')) {
241 1
            if ($this->isRemoteConnection) {
242 1
                throw new BuildException('The source and destination cannot both be remote.');
243
            }
244
            $this->setIsRemoteConnection(true);
245
        } else {
246 1
            if (!(is_dir($this->sourceDir) && is_readable($this->sourceDir))) {
247 1
                throw new BuildException('No such file or directory: ' . $this->sourceDir);
248
            }
249
        }
250
251
        if ($this->backupDir !== null && $this->backupDir == $this->destinationDir) {
252
            throw new BuildException("Invalid backup directory: " . $this->backupDir);
253
        }
254
255
        $command = $this->getCommand();
256
257
        $output = [];
258
        $return = null;
259
        exec($command, $output, $return);
260
261
        $lines = '';
262
        foreach ($output as $line) {
263
            if (!empty($line)) {
264
                $lines .= "\r\n\t\t\t" . $line;
265
            }
266
        }
267
268
        $this->log($command);
269
270
        if ($return != 0) {
271
            $this->log('Task exited with code: ' . $return, Project::MSG_ERR);
272
            $this->log(
273
                'Task exited with message: (' . $return . ') ' . $this->getErrorMessage($return),
274
                Project::MSG_ERR
275
            );
276
            throw new BuildException($return . ': ' . $this->getErrorMessage($return));
277
        }
278
279
        $this->log($lines, Project::MSG_INFO);
280
281
        return $return;
282
    }
283
284
    /**
285
     * Returns the rsync command line options.
286
     *
287
     * @return string
288
     */
289
    public function getCommand()
290
    {
291
        $options = $this->defaultOptions;
292
293
        if ($this->options !== null) {
294
            $options = $this->options;
295
        }
296
297
        if ($this->verbose === true) {
298
            $options .= ' --verbose';
299
        }
300
301
        if ($this->checksum === true) {
302
            $options .= ' --checksum';
303
        }
304
305
        if ($this->identityFile !== null) {
306
            $options .= ' -e "ssh -i ' . $this->identityFile . ' -p' . $this->remotePort . '"';
307
        } else {
308
            if ($this->remoteShell !== null) {
309
                $options .= ' -e "' . $this->remoteShell . '"';
310
            }
311
        }
312
313
        if ($this->dryRun === true) {
314
            $options .= ' --dry-run';
315
        }
316
317
        if ($this->delete === true) {
318
            $options .= ' --delete-after --ignore-errors --force';
319
        }
320
321
        if ($this->itemizeChanges === true) {
322
            $options .= ' --itemize-changes';
323
        }
324
        if ($this->backupDir !== null) {
325
            $options .= ' -b --backup-dir="' . $this->backupDir . '"';
326
        }
327
328
        if ($this->exclude !== null) {
329
            //remove trailing comma if any
330
            $this->exclude = trim($this->exclude, ',');
331
            $options .= ' --exclude="' . str_replace(',', '" --exclude="', $this->exclude) . '"';
332
        }
333
334
        if ($this->excludeFile !== null) {
335
            $options .= ' --exclude-from="' . $this->excludeFile . '"';
336
        }
337
338
        $this->setOptions($options);
339
340
        $options .= ' "' . $this->sourceDir . '" "' . $this->destinationDir . '"';
341
342
        escapeshellcmd($options);
343
        $options .= ' 2>&1';
344
345
        return $this->rsyncPath . ' ' . $options;
346
    }
347
348
    /**
349
     * Returns an error message based on a given error code.
350
     *
351
     * @param int $code Error code
352
     * @return null|string
353
     */
354
    public function getErrorMessage($code)
355
    {
356
        $error[0] = 'Success';
0 ignored issues
show
Comprehensibility Best Practice introduced by
$error was never initialized. Although not strictly required by PHP, it is generally a good practice to add $error = array(); before regardless.
Loading history...
357
        $error[1] = 'Syntax or usage error';
358
        $error[2] = 'Protocol incompatibility';
359
        $error[3] = 'Errors selecting input/output files, dirs';
360
        $error[4] = 'Requested action not supported: an attempt was made to manipulate '
361
            . '64-bit files on a platform that cannot support them; or an option '
362
            . 'was specified that is supported by the client and not by the server';
363
        $error[5] = 'Error starting client-server protocol';
364
        $error[10] = 'Error in socket I/O';
365
        $error[11] = 'Error in file I/O';
366
        $error[12] = 'Error in rsync protocol data stream';
367
        $error[13] = 'Errors with program diagnostics';
368
        $error[14] = 'Error in IPC code';
369
        $error[20] = 'Received SIGUSR1 or SIGINT';
370
        $error[21] = 'Some error returned by waitpid()';
371
        $error[22] = 'Error allocating core memory buffers';
372
        $error[23] = 'Partial transfer due to error';
373
        $error[24] = 'Partial transfer due to vanished source files';
374
        $error[30] = 'Timeout in data send/receive';
375
376
        if (array_key_exists($code, $error)) {
377
            return $error[$code];
378
        }
379
380
        return null;
381
    }
382
383
    /**
384
     * Sets the path to the rsync command.
385
     *
386
     * @param string $path
387
     * @return void
388
     */
389
    public function setRsyncPath($path)
390
    {
391
        $this->rsyncPath = $path;
392
    }
393
394
    /**
395
     * Sets the isRemoteConnection property.
396
     *
397
     * @param boolean $isRemote
398
     * @return void
399
     */
400 1
    protected function setIsRemoteConnection($isRemote)
401
    {
402 1
        $this->isRemoteConnection = $isRemote;
403 1
    }
404
405
    /**
406
     * Sets the source directory.
407
     *
408
     * @param string $dir
409
     * @return void
410
     */
411 3
    public function setSourceDir($dir)
412
    {
413 3
        $this->sourceDir = $dir;
414 3
    }
415
416
    /**
417
     * Sets the command options.
418
     *
419
     * @param string $options
420
     * @return void
421
     */
422
    public function setOptions($options)
423
    {
424
        $this->options = $options;
425
    }
426
427
    /**
428
     * Sets the destination directory. If the option remotehost is not included
429
     * in the build.xml file, rsync will point to a local directory instead.
430
     *
431
     * @param string $dir
432
     * @return void
433
     */
434 3
    public function setDestinationDir($dir)
435
    {
436 3
        $this->destinationDir = $dir;
437 3
    }
438
439
    /**
440
     * Sets the remote host.
441
     *
442
     * @param string $host
443
     * @return void
444
     */
445
    public function setRemoteHost($host)
446
    {
447
        $this->remoteHost = $host;
448
    }
449
450
    /**
451
     * Specifies the user to log in as on the remote machine. This also may be
452
     * specified in the properties file.
453
     *
454
     * @param string $user
455
     * @return void
456
     */
457
    public function setRemoteUser($user)
458
    {
459
        $this->remoteUser = $user;
460
    }
461
462
    /**
463
     * This option allows you to provide a password for accessing a remote rsync
464
     * daemon. Note that this option is only useful when accessing an rsync daemon
465
     * using the built in transport, not when using a remote shell as the transport.
466
     *
467
     * @param string $pass
468
     * @return void
469
     */
470
    public function setRemotePass($pass)
471
    {
472
        $this->remotePass = $pass;
473
    }
474
475
    /**
476
     * Allows the user to choose an alternative remote shell program to use for
477
     * communication between the local and remote copies of rsync. Typically,
478
     * rsync is configured to use ssh by default, but you may prefer to use rsh
479
     * on a local network.
480
     *
481
     * @param string $shell
482
     * @return void
483
     */
484
    public function setRemoteShell($shell)
485
    {
486
        $this->remoteShell = $shell;
487
    }
488
489
    /**
490
     * Increases the amount of information you are given during the
491
     * transfer. By default, rsync works silently. A single -v will give you
492
     * information about what files are being transferred and a brief summary at
493
     * the end.
494
     *
495
     * @param boolean $verbose
496
     * @return void
497
     */
498
    public function setVerbose(bool $verbose)
499
    {
500
        $this->verbose = $verbose;
501
    }
502
503
    /**
504
     * This changes the way rsync checks if the files have been changed and are in need of a transfer.
505
     * Without this option, rsync  uses  a "quick  check"  that  (by  default)  checks if each file’s
506
     * size and time of last modification match between the sender and receiver.
507
     * This option changes this to compare a 128-bit checksum for each file that has a matching size.
508
     *
509
     * @param boolean $checksum
510
     * @return void
511
     */
512
    public function setChecksum(bool $checksum)
513
    {
514
        $this->checksum = $checksum;
515
    }
516
517
    /**
518
     * This makes rsync perform a trial run that doesn’t make any changes (and produces mostly the same
519
     * output as a real run).  It is  most commonly used in combination with the -v, --verbose and/or
520
     * -i, --itemize-changes options to see what an rsync command is going to do before one actually runs it.
521
     *
522
     * @param boolean $dryRun
523
     * @return void
524
     */
525
    public function setDryRun(bool $dryRun)
526
    {
527
        $this->dryRun = $dryRun;
528
    }
529
530
    /**
531
     * Requests a simple itemized list of the changes that are being made to each file, including attribute changes.
532
     *
533
     * @param boolean $itemizeChanges
534
     * @return void
535
     */
536
    public function setItemizeChanges(bool $itemizeChanges)
537
    {
538
        $this->itemizeChanges = $itemizeChanges;
539
    }
540
541
    /**
542
     * Tells rsync to delete extraneous files from the receiving side, but only
543
     * for the directories that are being synchronized. Files that are excluded
544
     * from transfer are also excluded from being deleted.
545
     *
546
     * @param boolean $delete
547
     * @return void
548
     */
549
    public function setDelete(bool $delete)
550
    {
551
        $this->delete = $delete;
552
    }
553
554
    /**
555
     * Exclude files matching patterns from $file, Blank lines in $file and
556
     * lines starting with ';' or '#' are ignored.
557
     *
558
     * @param string $file
559
     * @return void
560
     */
561
    public function setExcludeFile($file)
562
    {
563
        $this->excludeFile = $file;
564
    }
565
566
    /**
567
     * Makes backups into hierarchy based in $dir.
568
     *
569
     * @param string dir
0 ignored issues
show
Bug introduced by
The type Phing\Task\Optional\dir was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
570
     * @return void
571
     */
572
    public function setBackupDir($dir)
573
    {
574
        $this->backupDir = $dir;
575
    }
576
577
    /**
578
     * Sets the identity file for public key transfers.
579
     *
580
     * @param string location of ssh identity file
0 ignored issues
show
Bug introduced by
The type Phing\Task\Optional\location was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
581
     * @return void
582
     */
583
    public function setIdentityFile($identity)
584
    {
585
        $this->identityFile = $identity;
586
    }
587
588
    /**
589
     * Sets the port of the remote computer.
590
     *
591
     * @param int $remotePort
592
     */
593
    public function setRemotePort($remotePort)
594
    {
595
        $this->remotePort = $remotePort;
596
    }
597
598
    /**
599
     * Sets exclude matching pattern.
600
     *
601
     * @param string $exclude
602
     * @return void
603
     */
604
    public function setExclude($exclude)
605
    {
606
        $this->exclude = $exclude;
607
    }
608
}
609