Completed
Push — master ( 273890...647b73 )
by Sebastian
03:36
created

Tar::isLevelZeroTime()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 3

Importance

Changes 0
Metric Value
cc 3
eloc 4
nc 3
nop 2
dl 0
loc 8
ccs 5
cts 5
cp 1
crap 3
rs 10
c 0
b 0
f 0
1
<?php
2
namespace phpbu\App\Backup\Source;
3
4
use phpbu\App\Backup\Restore\Plan;
5
use phpbu\App\Backup\Target;
6
use phpbu\App\Cli\Executable;
7
use phpbu\App\Configuration;
8
use phpbu\App\Exception;
9
use phpbu\App\Result;
10
use phpbu\App\Util;
11
12
/**
13
 * Tar source class.
14
 *
15
 * @package    phpbu
16
 * @subpackage Backup
17
 * @author     Sebastian Feldmann <[email protected]>
18
 * @copyright  Sebastian Feldmann <[email protected]>
19
 * @license    https://opensource.org/licenses/MIT The MIT License (MIT)
20
 * @link       http://phpbu.de/
21
 * @since      Class available since Release 1.0.0
22
 */
23
class Tar extends SimulatorExecutable implements Simulator, Restorable
24
{
25
    /**
26
     * Tar Executable
27
     *
28
     * @var \phpbu\App\Cli\Executable\Tar
29
     */
30
    protected $executable;
31
32
    /**
33
     * Path to executable.
34
     *
35
     * @var string
36
     */
37
    private $pathToTar;
38
39
    /**
40
     * Path to backup
41
     *
42
     * @var string
43
     */
44
    private $path;
45
46
    /**
47
     * List of paths to exclude
48
     * --exclude
49
     *
50
     * @var array
51
     */
52
    private $excludes;
53
54
    /**
55
     * Special compression program
56
     * --use-compress-program
57
     *
58
     * @var string
59
     */
60
    private $compressProgram;
61
62
    /**
63
     * Force local file resolution
64
     *
65
     * --force-local
66
     *
67
     * @var bool
68
     */
69
    private $forceLocal;
70
71
    /**
72
     * Tar should ignore failed reads
73
     * --ignore-failed-read
74
     *
75
     * @var bool
76
     */
77
    private $ignoreFailedRead;
78
79
    /**
80
     * Remove the packed data
81
     *
82
     * @var bool
83
     */
84
    private $removeSourceDir;
85
86
    /**
87
     * Compression to use.
88
     *
89
     * @var string
90
     */
91
    private $compression = '';
92
93
    /**
94
     * Throttle cpu usage.
95
     *
96
     * @var string
97
     */
98
    private $throttle = '';
99
100
    /**
101
     * Path where to store the archive.
102
     *
103
     * @var string
104
     */
105
    private $pathToArchive;
106
107
    /**
108
     * Instead of archiving symbolic links, archive the files they link to
109
     *
110
     * @var bool
111
     */
112
    private $dereference;
113
114
    /**
115
     * Path to the incremental metadata file
116
     *
117
     * @var string
118
     */
119
    private $incrementalFile;
120
121 19
    /**
122
     * Force level 0 backup on
123 19
     *
124 18
     * - DATE@VLAUE
125 18
     * - %h@3   => every 3am backup
126 18
     * - %d@1   => first each month
127 18
     * - %D@Mon => every Monday
128 18
     *
129 18
     * @var string
130 18
     */
131 18
    private $forceLevelZeroOn;
132 18
133
    /**
134
     * Should a level zero backup be forced
135
     *
136
     * @var bool
137
     */
138
    private $forceLevelZero = false;
139
140 19
    /**
141
     * Setup.
142 19
     *
143 19
     * @see    \phpbu\App\Backup\Source
144 1
     * @param  array $conf
145
     * @throws \phpbu\App\Exception
146 18
     */
147 18
    public function setup(array $conf = [])
148
    {
149
        $this->setupPath($conf);
150 18
        $this->pathToTar        = Util\Arr::getValue($conf, 'pathToTar', '');
151
        $this->excludes         = Util\Str::toList(Util\Arr::getValue($conf, 'exclude', ''));
152
        $this->incrementalFile  = Util\Arr::getValue($conf, 'incrementalFile', '');
153
        $this->forceLevelZeroOn = Util\Arr::getValue($conf, 'forceLevelZeroOn', '');
154
        $this->compressProgram  = Util\Arr::getValue($conf, 'compressProgram', '');
155
        $this->throttle         = Util\Arr::getValue($conf, 'throttle', '');
156
        $this->forceLocal       = Util\Str::toBoolean(Util\Arr::getValue($conf, 'forceLocal', ''), false);
157
        $this->ignoreFailedRead = Util\Str::toBoolean(Util\Arr::getValue($conf, 'ignoreFailedRead', ''), false);
158
        $this->removeSourceDir  = Util\Str::toBoolean(Util\Arr::getValue($conf, 'removeSourceDir', ''), false);
159
        $this->dereference      = Util\Str::toBoolean(Util\Arr::getValue($conf, 'dereference', ''), false);
160
        $this->setupIncrementalSettings();
161 7
    }
162
163
    /**
164 7
     * Setup incremental backup settings
165
     *
166 6
     * @throws \phpbu\App\Exception
167 6
     */
168
    private function setupIncrementalSettings()
169 5
    {
170
        // no incremental backup just bail
171 5
        if (empty($this->incrementalFile)) {
172 2
            return;
173
        }
174
        $this->incrementalFile = $this->toAbsolutePath($this->incrementalFile);
175 3
176
        // no zero level forcing, bail again
177
        if (empty($this->forceLevelZeroOn)) {
178
            return;
179
        }
180
        // extract the date placeholder %D and the values a|b|c from the configuration
181
        // %DATE@VALUE[|VALUE]
182
        // - %D@Mon
183
        // - %d@1|11|22
184
        // - %D@Sun|Thu
185
        $dateAndValues = explode('@', $this->forceLevelZeroOn);
186 1
        $date          = $dateAndValues[0] ?? '';
187
        $values        = $dateAndValues[1] ?? '';
188 1
189 1
        if (empty($date) || empty($values)) {
190
            throw new Exception('invalid \'forceLevelZeroOn\' configuration - \'%DATE@VALUE[|VALUE]\'');
191
        }
192
        // check if the given date format is happening right now
193
        $this->forceLevelZero = $this->isLevelZeroTime($date, explode('|', $values));
194
    }
195
196
    /**
197
     * Checks if the configured zero level force applies to the current time
198
     *
199 16
     * @param  string $date
200
     * @param  array  $values
201 16
     * @return bool
202
     */
203
    private function isLevelZeroTime(string $date, array $values): bool
204 16
    {
205 16
        foreach ($values as $value) {
206 5
            if (Util\Path::replaceDatePlaceholders($date, $this->time) === $value) {
207 5
                return true;
208
            }
209
        }
210 16
        return false;
211 16
    }
212 16
213 16
    /**
214 16
     * Setup the path to the directory that should be compressed.
215 16
     *
216 16
     * @param  array $conf
217 16
     * @throws \phpbu\App\Exception
218 16
     */
219 16
    protected function setupPath(array $conf)
220
    {
221 16
        $path = Util\Arr::getValue($conf, 'path', '');
222 1
        if (empty($path)) {
223
            throw new Exception('path option is mandatory');
224
        }
225 16
        $this->path = Util\Path::toAbsolutePath($path, Configuration::getWorkingDirectory());
226
        if (!file_exists($this->path)) {
227
            throw new Exception('could not find directory to compress');
228
        }
229
    }
230
231
    /**
232
     * Execute the backup.
233 7
     *
234
     * @see    \phpbu\App\Backup\Source
235 7
     * @param  \phpbu\App\Backup\Target $target
236 1
     * @param  \phpbu\App\Result        $result
237
     * @return \phpbu\App\Backup\Source\Status
238 6
     * @throws \phpbu\App\Exception
239
     */
240
    public function backup(Target $target, Result $result) : Status
241
    {
242
        // make sure source path is a directory
243
        $this->validatePath();
244
        // set uncompressed default MIME type
245
        $target->setMimeType('application/x-tar');
246 3
        $tar = $this->execute($target);
247
248 3
        $result->debug($tar->getCmdPrintable());
249
250
        if (!$tar->isSuccessful()) {
251 3
            throw new Exception('tar failed: ' . $tar->getStdErr());
252 1
        }
253
254 3
        return $this->createStatus($target);
255
    }
256
257
    /**
258
     * Restore the backup
259
     *
260
     * @param  \phpbu\App\Backup\Target       $target
261
     * @param  \phpbu\App\Backup\Restore\Plan $plan
262
     * @return \phpbu\App\Backup\Source\Status
263
     * @throws \phpbu\App\Exception
264
     */
265
    public function restore(Target $target, Plan $plan): Status
266
    {
267
        $plan->addRestoreCommand('tar -xvf ' . $target->getFilename(true));
268
        return Status::create()->uncompressedFile($target->getPathname());
269
    }
270
271
    /**
272
     * Setup the Executable to run the 'tar' command.
273
     *
274
     * @param  \phpbu\App\Backup\Target $target
275
     * @return \phpbu\App\Cli\Executable
276
     * @throws \phpbu\App\Exception
277
     */
278
    protected function createExecutable(Target $target) : Executable
279
    {
280
        $this->pathToArchive = $target->getPathnamePlain();
281
282
        // check if archive should be compressed and tar supports requested compression
283
        if ($target->shouldBeCompressed()
284
            && Executable\Tar::isCompressionValid($target->getCompression()->getCommand())) {
285
            $this->pathToArchive = $target->getPathname();
286
            $this->compression   = $target->getCompression()->getCommand();
287
        }
288
289
        $executable = new Executable\Tar($this->pathToTar);
290
        $executable->archiveDirectory($this->path)
291
                   ->useCompression($this->compression)
292
                   ->useCompressProgram($this->compressProgram)
293
                   ->forceLocal($this->forceLocal)
294
                   ->ignoreFailedRead($this->ignoreFailedRead)
295
                   ->removeSourceDirectory($this->removeSourceDir)
296
                   ->throttle($this->throttle)
297
                   ->archiveTo($this->pathToArchive)
298
                   ->dereference($this->dereference);
299
300
        $this->handleIncrementalBackup($executable);
301
302
        // add paths to exclude
303
        foreach ($this->excludes as $path) {
304
            $executable->addExclude($path);
305
        }
306
307
        return $executable;
308
    }
309
310
    /**
311
     * Setup the incremental backup options
312
     *
313
     * @param  \phpbu\App\Cli\Executable\Tar $executable
314
     * @throws \phpbu\App\Exception
315
     * @return void
316
     */
317
    private function handleIncrementalBackup(Executable\Tar $executable): void
318
    {
319
        if (empty($this->incrementalFile)) {
320
            return;
321
        }
322
        $executable->incrementalMetadata($this->incrementalFile);
323
        $executable->forceLevelZero($this->forceLevelZero);
324
    }
325
326
    /**
327
     * Check the source to compress.
328
     *
329
     * @throws \phpbu\App\Exception
330
     */
331
    private function validatePath()
332
    {
333
        if (!is_dir($this->path)) {
334
            throw new Exception('path to compress has to be a directory');
335
        }
336
    }
337
338
    /**
339
     * Create backup status.
340
     *
341
     * @param  \phpbu\App\Backup\Target $target
342
     * @return \phpbu\App\Backup\Source\Status
343
     */
344
    protected function createStatus(Target $target) : Status
345
    {
346
        $status = Status::create();
347
        // if tar doesn't handle the compression mark status uncompressed
348
        // so the app can take care of compression
349
        if (!$this->executable->handlesCompression()) {
350
            $status->uncompressedFile($target->getPathnamePlain());
351
        }
352
        return $status;
353
    }
354
}
355