FileSyncTask::getCommand()   F
last analyzed

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