Tar::validateDirectory()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
cc 2
eloc 2
nc 2
nop 1
dl 0
loc 4
ccs 0
cts 0
cp 0
crap 6
rs 10
c 0
b 0
f 0
1
<?php
2
namespace phpbu\App\Cli\Executable;
3
4
use phpbu\App\Cli\Executable;
5
use phpbu\App\Exception;
6
use SebastianFeldmann\Cli\CommandLine;
7
use SebastianFeldmann\Cli\Command\Executable as Cmd;
8
9
/**
10
 * Tar Executable class.
11
 *
12
 * @package    phpbu
13
 * @subpackage Backup
14
 * @author     Sebastian Feldmann <[email protected]>
15
 * @copyright  Sebastian Feldmann <[email protected]>
16
 * @license    https://opensource.org/licenses/MIT The MIT License (MIT)
17
 * @link       http://phpbu.de/
18
 * @since      Class available since Release 1.0.0
19
 */
20
class Tar extends Abstraction implements Executable
21
{
22
    /**
23
     * Path to compress
24
     *
25
     * @var string
26
     */
27
    private $path;
28
29
    /**
30
     * Compression to use
31
     *
32
     * @var string
33
     */
34
    private $compression;
35
36
    /**
37
     * Compress program to use.
38
     * --use-compress-program
39
     *
40
     * @var string
41
     */
42
    private $compressProgram;
43
44
    /**
45
     * Path to dump file
46
     *
47
     * @var string
48
     */
49
    private $tarPathname;
50
51
    /**
52
     * List of excluded path.
53
     * --exclude='foo'
54
     *
55
     * @var array
56
     */
57
    private $excludes = [];
58
59
    /**
60
     * Force local file resolution
61
     * --force-local
62
     *
63
     * @var bool
64
     */
65
    private $local = false;
66
67
    /**
68
     * Ignore failed reads
69
     * --ignore-failed-read
70
     *
71
     * @var bool
72
     */
73
    private $ignoreFailedRead;
74
75
    /**
76
     * Should the source directory be removed.
77
     *
78
     * @var boolean
79
     */
80
    private $removeSourceDir = false;
81
82
    /**
83
     * Limit data throughput
84
     * | pv -L ${limit}
85
     *
86
     * @var string
87
     */
88
    private $pvLimit = '';
89
90
    /**
91
     * List of available compressors
92
     *
93
     * @var array
94
     */
95
    private static $availableCompressions = [
96
        'bzip2' => 'j',
97
        'gzip'  => 'z',
98
        'xz'    => 'J'
99
    ];
100
101
    /**
102
     * Instead of archiving symbolic links, archive the files they link to
103
     *
104
     * @var bool
105
     */
106
    private $dereference = false;
107
108
    /**
109
     * File to store the incremental metadata in 'archive.snar'
110
     *
111
     * @var string
112
     */
113 36
    private $metadataFile = '';
114
115 36
    /**
116 36
     * Defines the incremental backup level
117
     *
118
     * This is used during incremental backup only.
119
     *
120
     *  0 => backup all
121
     *  1 => backup incremental
122
     *
123
     * @var int
124 14
     */
125
    private $level = 1;
126 14
127
    /**
128
     * Constructor.
129
     *
130
     * @param string $path
131
     */
132
    public function __construct(string $path = '')
133
    {
134
        $this->setup('tar', $path);
135 25
    }
136
137 25
    /**
138 14
     * Return 'tar' compressor option e.g. 'j' for bzip2.
139
     *
140 25
     * @param  string $compressor
141
     * @return string
142
     */
143
    protected function getCompressionOption(string $compressor) : string
144
    {
145
        return $this->isCompressionValid($compressor) ? self::$availableCompressions[$compressor] : '';
146
    }
147
148
    /**
149 17
     * Compress tar.
150
     *
151 17
     * @param  string $compression
152 17
     * @return \phpbu\App\Cli\Executable\Tar
153
     */
154
    public function useCompression(string $compression) : Tar
155
    {
156
        if ($this->isCompressionValid($compression)) {
157
            $this->compression = $this->getCompressionOption($compression);
158
        }
159
        return $this;
160
    }
161 2
162
    /**
163 2
     * Set compress program.
164 2
     *
165
     * @param  string $program
166
     * @return \phpbu\App\Cli\Executable\Tar
167
     */
168
    public function useCompressProgram(string $program) : Tar
169
    {
170
        $this->compressProgram = $program;
171
        return $this;
172
    }
173 17
174
    /**
175 17
     * Add an path to exclude.
176 17
     *
177
     * @param  string $path
178
     * @return \phpbu\App\Cli\Executable\Tar
179
     */
180
    public function addExclude(string $path) : Tar
181
    {
182
        $this->excludes[] = $path;
183
        return $this;
184
    }
185 17
186
    /**
187 17
     * Force local file resolution.
188 17
     *
189
     * @param  bool $bool
190
     * @return \phpbu\App\Cli\Executable\Tar
191
     */
192
    public function forceLocal(bool $bool) : Tar
193
    {
194
        $this->local = $bool;
195
        return $this;
196
    }
197 18
198
    /**
199 18
     * Ignore failed reads setter.
200 18
     *
201
     * @param  bool $bool
202
     * @return \phpbu\App\Cli\Executable\Tar
203
     */
204
    public function ignoreFailedRead(bool $bool) : Tar
205
    {
206
        $this->ignoreFailedRead = $bool;
207
        return $this;
208 4
    }
209
210 4
    /**
211
     * Limit the data throughput.
212
     *
213
     * @param string $limit
214
     * @return \phpbu\App\Cli\Executable\Tar
215
     */
216
    public function throttle(string $limit) : Tar
217
    {
218
        $this->pvLimit = $limit;
219
        return $this;
220 34
    }
221
222 34
    /**
223 33
     * Does the tar handle the compression.
224 33
     *
225
     * @return bool
226
     */
227
    public function handlesCompression() : bool
228
    {
229
        return !empty($this->compression);
230
    }
231
232
    /**
233 32
     * Set folder to compress.
234
     *
235 32
     * @param  string $path
236 32
     * @return \phpbu\App\Cli\Executable\Tar
237
     * @throws \phpbu\App\Exception
238
     */
239
    public function archiveDirectory(string $path) : Tar
240
    {
241
        $this->validateDirectory($path);
242
        $this->path = $path;
243
        return $this;
244
    }
245 22
246
    /**
247 22
     * Set target filename.
248 22
     *
249
     * @param  string $path
250
     * @return \phpbu\App\Cli\Executable\Tar
251
     */
252
    public function archiveTo(string $path) : Tar
253
    {
254
        $this->tarPathname = $path;
255
        return $this;
256
    }
257 17
258
    /**
259 17
     * Delete the source directory.
260 17
     *
261
     * @param  boolean $bool
262
     * @return \phpbu\App\Cli\Executable\Tar
263
     */
264
    public function removeSourceDirectory(bool $bool) : Tar
265
    {
266
        $this->removeSourceDir = $bool;
267
        return $this;
268
    }
269 34
270
    /**
271 34
     * Instead of archiving symbolic links, archive the files they link
272
     *
273 32
     * @param bool $bool
274 32
     * @return \phpbu\App\Cli\Executable\Tar
275 32
     */
276 32
    public function dereference(bool $bool) : Tar
277
    {
278 32
        $this->dereference = $bool;
279 32
        return $this;
280
    }
281 32
282 32
    /**
283 32
     * Set incremental backup metadata file
284 32
     *
285
     * @param  string $pathToMetadataFile
286 32
     * @return $this
287 3
     */
288 3
    public function incrementalMetadata(string $pathToMetadataFile): Tar
289 3
    {
290 3
        $this->metadataFile = $pathToMetadataFile;
291
        return $this;
292 29
    }
293
294
    /**
295 32
     * Force a level 0 backup even if backup is done incrementally
296 32
     *
297
     * @param bool $levelZero
298
     */
299 32
    public function forceLevelZero(bool $levelZero)
300
    {
301 32
        $this->level = $levelZero ? 0 : 1;
302
    }
303
304
    /**
305
     * Tar CommandLine generator.
306
     *
307
     * @return \SebastianFeldmann\Cli\CommandLine
308
     * @throws \phpbu\App\Exception
309 32
     */
310
    protected function createCommandLine() : CommandLine
311 32
    {
312 2
        $this->validateSetup();
313
314 32
        $process = new CommandLine();
315
        $tar     = new Cmd($this->binary);
316
        $create  = $this->isThrottled() ? 'c' : 'cf';
317
        $process->addCommand($tar);
318
319
        $this->setExcludeOptions($tar);
320
        $this->handleWarnings($tar);
321
        $this->handleIncremental($tar);
322 32
323
        $tar->addOptionIfNotEmpty('-h', $this->dereference, false);
324 32
        $tar->addOptionIfNotEmpty('--force-local', $this->local, false);
325 3
        $tar->addOptionIfNotEmpty('--use-compress-program', $this->compressProgram);
326 3
        $tar->addOption('-' . (empty($this->compressProgram) ? $this->compression : '') . $create);
327
328 32
        if ($this->isThrottled()) {
329
            $pv = new Cmd('pv');
330
            $pv->addOption('-qL', $this->pvLimit, ' ');
331
            $process->pipeOutputTo($pv);
332
            $process->redirectOutputTo($this->tarPathname);
333
        } else {
334
            $tar->addArgument($this->tarPathname);
335 32
        }
336
337 32
        $tar->addOption('-C', dirname($this->path), ' ');
338 7
        $tar->addArgument(basename($this->path));
339
340 32
        // delete the source data if requested
341
        $this->addRemoveCommand($process);
342
343
        return $process;
344
    }
345
346
    /**
347 7
     * Adds necessary exclude options to tat command.
348
     *
349 7
     * @param \SebastianFeldmann\Cli\Command\Executable $tar
350 7
     */
351 7
    protected function setExcludeOptions(Cmd $tar)
352
    {
353
        foreach ($this->excludes as $path) {
354
            $tar->addOption('--exclude', $path);
355
        }
356
    }
357
358
    /**
359
     * Configure warning handling.
360 34
     * With the 'ignoreFailedRead' option set, exit code '1' is also accepted since it only indicates a warning.
361
     *
362 34
     * @param \SebastianFeldmann\Cli\Command\Executable $tar
363 1
     */
364
    protected function handleWarnings(Cmd $tar)
365 33
    {
366
        if ($this->ignoreFailedRead) {
367
            $tar->addOption('--ignore-failed-read');
368
            $this->acceptableExitCodes = [0, 1];
369
        }
370
    }
371
372 34
    /**
373
     * Will set the incremental backup options
374 34
     *
375 1
     * - --listed-incremental=PATH_TO_METADATA_FILE
376
     * - --level=0|1
377 33
     *
378 1
     * @param \SebastianFeldmann\Cli\Command\Executable $tar
379
     */
380 32
    private function handleIncremental(Cmd $tar)
381
    {
382
        // if no incremental metadata file is set we can skip this part
383
        if (empty($this->metadataFile)) {
384
            return;
385
        }
386
387
        $tar->addOption('--listed-incremental', $this->metadataFile);
388 27
389
        // only set the level if we want to force a level 0 backup since 1 is the default value
390 27
        if ($this->level !== 1) {
391
            $tar->addOption('--level', $this->level);
392
        }
393
    }
394
395
    /**
396
     * Add a remove command if requested.
397
     *
398 32
     * @param \SebastianFeldmann\Cli\CommandLine $process
399
     */
400 32
    protected function addRemoveCommand(CommandLine $process)
401
    {
402
        if ($this->removeSourceDir) {
403
            $process->addCommand($this->getRmCommand());
404
        }
405
    }
406
407
    /**
408
     * Return 'rm' command.
409
     *
410
     * @return \SebastianFeldmann\Cli\Command\Executable
411
     */
412
    protected function getRmCommand() : Cmd
413
    {
414
        $rm = new Cmd('rm');
415
        $rm->addOption('-rf', $this->path, ' ');
416
        return $rm;
417
    }
418
419
    /**
420
     * Check directory to compress.
421
     *
422
     * @param  string $path
423
     * @throws \phpbu\App\Exception
424
     */
425
    private function validateDirectory(string $path)
426
    {
427
        if ($path === '.') {
428
            throw new Exception('unable to tar current working directory');
429
        }
430
    }
431
432
    /**
433
     * Check if source and target values are set.
434
     *
435
     * @throws \phpbu\App\Exception
436
     */
437
    private function validateSetup()
438
    {
439
        if (empty($this->path)) {
440
            throw new Exception('no directory to compress');
441
        }
442
        if (empty($this->tarPathname)) {
443
            throw new Exception('no target filename set');
444
        }
445
    }
446
447
    /**
448
     * Return true if a given compression is valid false otherwise.
449
     *
450
     * @param  string $compression
451
     * @return bool
452
     */
453
    public static function isCompressionValid(string  $compression) : bool
454
    {
455
        return isset(self::$availableCompressions[$compression]);
456
    }
457
458
    /**
459
     * Should output be throttled through pv.
460
     *
461
     * @return bool
462
     */
463
    public function isThrottled() : bool
464
    {
465
        return !empty($this->pvLimit);
466
    }
467
}
468