Skip to content

Commit 647b73c

Browse files
Activate incremental backup support
The tar source can now handle incremental backups if supplied with a metadata file to cache the current state. You can use only the 'incrementalFile' option. That leaves you in charge of handling the backup level and snar file handling. If you do not want to handle it yourself you can supply the 'forceLevelZeroOn' option. This will make sure at configured points in time a level zero backup is executed This fixes issue #105
1 parent 4393db1 commit 647b73c

File tree

5 files changed

+260
-15
lines changed

5 files changed

+260
-15
lines changed

doc/config/source/tar.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,12 @@
1212
<!-- optional, default false -->
1313
<option name="ignoreFailedRead" value="false" />
1414

15+
<!-- optional, default null -->
16+
<option name="incrementalFile" value="path/to/metadata.snar" />
17+
18+
<!-- optional, default null -->
19+
<option name="forceLevelZeroOn" value="%D@Sun" />
20+
1521
<!-- optional, default null -->
1622
<option name="compressProgram" value="lbzip2" />
1723

src/Backup/Cli.php

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33

44
use phpbu\App\Cli\Executable;
55
use phpbu\App\Cli\Result;
6+
use phpbu\App\Configuration;
7+
use phpbu\App\Util;
68
use SebastianFeldmann\Cli\Command\Runner;
79

810
/**
@@ -25,6 +27,13 @@ abstract class Cli
2527
*/
2628
protected $runner;
2729

30+
/**
31+
* Current timestamp
32+
*
33+
* @var int
34+
*/
35+
protected $time;
36+
2837
/**
2938
* Executable command.
3039
*
@@ -36,10 +45,12 @@ abstract class Cli
3645
* Cli constructor.
3746
*
3847
* @param \SebastianFeldmann\Cli\Command\Runner $runner
48+
* @param int $time
3949
*/
40-
public function __construct(Runner $runner = null)
50+
public function __construct(Runner $runner = null, $time = null)
4151
{
4252
$this->runner = $runner ? : new Runner\Simple();
53+
$this->time = $time ? : time();
4354
}
4455

4556
/**
@@ -98,6 +109,18 @@ public function getExecutable(Target $target) : Executable
98109
return $this->executable;
99110
}
100111

112+
/**
113+
* Return an absolute path relative to the used file.
114+
*
115+
* @param string $path
116+
* @param string $default
117+
* @return string
118+
*/
119+
protected function toAbsolutePath(string $path, string $default = '')
120+
{
121+
return !empty($path) ? Util\Path::toAbsolutePath($path, Configuration::getWorkingDirectory()) : $default;
122+
}
123+
101124
/**
102125
* Creates the executable for this action.
103126
*

src/Backup/Crypter/Abstraction.php

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,7 @@
33

44
use phpbu\App\Backup\Cli;
55
use phpbu\App\Backup\Target;
6-
use phpbu\App\Configuration;
76
use phpbu\App\Result;
8-
use phpbu\App\Util;
97

108
/**
119
* Abstract crypter class.
@@ -53,16 +51,4 @@ public function simulate(Target $target, Result $result)
5351
$this->getExecutable($target)->getCommandPrintable()
5452
);
5553
}
56-
57-
/**
58-
* Return an absolute path relative to the used file.
59-
*
60-
* @param string $path
61-
* @param string $default
62-
* @return string
63-
*/
64-
protected function toAbsolutePath(string $path, string $default = '')
65-
{
66-
return !empty($path) ? Util\Path::toAbsolutePath($path, Configuration::getWorkingDirectory()) : $default;
67-
}
6854
}

src/Backup/Source/Tar.php

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,32 @@ class Tar extends SimulatorExecutable implements Simulator, Restorable
111111
*/
112112
private $dereference;
113113

114+
/**
115+
* Path to the incremental metadata file
116+
*
117+
* @var string
118+
*/
119+
private $incrementalFile;
120+
121+
/**
122+
* Force level 0 backup on
123+
*
124+
* - DATE@VLAUE
125+
* - %h@3 => every 3am backup
126+
* - %d@1 => first each month
127+
* - %D@Mon => every Monday
128+
*
129+
* @var string
130+
*/
131+
private $forceLevelZeroOn;
132+
133+
/**
134+
* Should a level zero backup be forced
135+
*
136+
* @var bool
137+
*/
138+
private $forceLevelZero = false;
139+
114140
/**
115141
* Setup.
116142
*
@@ -123,12 +149,65 @@ public function setup(array $conf = [])
123149
$this->setupPath($conf);
124150
$this->pathToTar = Util\Arr::getValue($conf, 'pathToTar', '');
125151
$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', '');
126154
$this->compressProgram = Util\Arr::getValue($conf, 'compressProgram', '');
127155
$this->throttle = Util\Arr::getValue($conf, 'throttle', '');
128156
$this->forceLocal = Util\Str::toBoolean(Util\Arr::getValue($conf, 'forceLocal', ''), false);
129157
$this->ignoreFailedRead = Util\Str::toBoolean(Util\Arr::getValue($conf, 'ignoreFailedRead', ''), false);
130158
$this->removeSourceDir = Util\Str::toBoolean(Util\Arr::getValue($conf, 'removeSourceDir', ''), false);
131159
$this->dereference = Util\Str::toBoolean(Util\Arr::getValue($conf, 'dereference', ''), false);
160+
$this->setupIncrementalSettings();
161+
}
162+
163+
/**
164+
* Setup incremental backup settings
165+
*
166+
* @throws \phpbu\App\Exception
167+
*/
168+
private function setupIncrementalSettings()
169+
{
170+
// no incremental backup just bail
171+
if (empty($this->incrementalFile)) {
172+
return;
173+
}
174+
$this->incrementalFile = $this->toAbsolutePath($this->incrementalFile);
175+
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+
$date = $dateAndValues[0] ?? '';
187+
$values = $dateAndValues[1] ?? '';
188+
189+
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+
* @param string $date
200+
* @param array $values
201+
* @return bool
202+
*/
203+
private function isLevelZeroTime(string $date, array $values): bool
204+
{
205+
foreach ($values as $value) {
206+
if (Util\Path::replaceDatePlaceholders($date, $this->time) === $value) {
207+
return true;
208+
}
209+
}
210+
return false;
132211
}
133212

134213
/**
@@ -217,6 +296,9 @@ protected function createExecutable(Target $target) : Executable
217296
->throttle($this->throttle)
218297
->archiveTo($this->pathToArchive)
219298
->dereference($this->dereference);
299+
300+
$this->handleIncrementalBackup($executable);
301+
220302
// add paths to exclude
221303
foreach ($this->excludes as $path) {
222304
$executable->addExclude($path);
@@ -225,6 +307,22 @@ protected function createExecutable(Target $target) : Executable
225307
return $executable;
226308
}
227309

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+
228326
/**
229327
* Check the source to compress.
230328
*

tests/phpbu/Backup/Source/TarTest.php

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
use phpbu\App\Backup\CliMockery;
55
use phpbu\App\BaseMockery;
6+
use phpbu\App\Configuration;
67
use SebastianFeldmann\Cli\Command\Result as CommandResult;
78
use SebastianFeldmann\Cli\Command\Runner\Simple;
89
use SebastianFeldmann\Cli\Processor\ProcOpen;
@@ -36,6 +37,18 @@ public function testSetupPathMissing()
3637
$this->assertFalse(true, 'exception should be thrown');
3738
}
3839

40+
/**
41+
* Tests Tar::setUp
42+
*/
43+
public function testSetupPathDoesNotExist()
44+
{
45+
$this->expectException('phpbu\App\Exception');
46+
$tar = new Tar();
47+
$tar->setup(['path' => getcwd() . '/foo']);
48+
49+
$this->assertFalse(true, 'exception should be thrown');
50+
}
51+
3952
/**
4053
* Tests Tar::getExecutable
4154
*/
@@ -102,6 +115,125 @@ public function testExcludes()
102115
);
103116
}
104117

118+
/**
119+
* Tests Tar::getExecutable
120+
*
121+
* We have to create a configuration here to actually calculate a relative path from the configuration location.
122+
*/
123+
public function testIncremental()
124+
{
125+
$incFile = getcwd() . '/foo.snar';
126+
$conf = new Configuration();
127+
$conf->setFilename(getcwd() . '/config.xml');
128+
129+
$target = $this->createTargetMock('/tmp/backup.tar');
130+
$target->method('shouldBeCompressed')->willReturn(false);
131+
$target->method('getPathname')->willReturn('/tmp/backup.tar');
132+
133+
$tar = new Tar();
134+
$tar->setup(['pathToTar' => PHPBU_TEST_BIN, 'path' => __DIR__, 'incrementalFile' => 'foo.snar']);
135+
136+
$exec = $tar->getExecutable($target);
137+
138+
$this->assertEquals(
139+
PHPBU_TEST_BIN . '/tar --listed-incremental=\'' . $incFile . '\' '
140+
. '-cf \'/tmp/backup.tar\' -C \''
141+
. dirname(__DIR__) . '\' \''
142+
. basename(__DIR__) . '\'',
143+
$exec->getCommand()
144+
);
145+
}
146+
147+
/**
148+
* Tests Tar::setUp
149+
*/
150+
public function testIncrementalInvalidZeroLevelFormat()
151+
{
152+
$this->expectException('phpbu\App\Exception');
153+
154+
$tar = new Tar();
155+
$tar->setup(
156+
[
157+
'pathToTar' => PHPBU_TEST_BIN,
158+
'path' => __DIR__,
159+
'incrementalFile' => 'foo.snar',
160+
'forceLevelZeroOn' => 'foo'
161+
]
162+
);
163+
164+
$this->assertFalse(true, 'exception should be thrown');
165+
}
166+
167+
/**
168+
* Tests Tar::getExecutable
169+
*/
170+
public function testIncrementalForceZeroTrue()
171+
{
172+
$incFile = getcwd() . '/foo.snar';
173+
$time = time();
174+
$conf = new Configuration();
175+
$conf->setFilename(getcwd() . '/config.xml');
176+
177+
$target = $this->createTargetMock('/tmp/backup.tar');
178+
$target->method('shouldBeCompressed')->willReturn(false);
179+
$target->method('getPathname')->willReturn('/tmp/backup.tar');
180+
181+
$tar = new Tar(null, $time);
182+
$tar->setup(
183+
[
184+
'pathToTar' => PHPBU_TEST_BIN,
185+
'path' => __DIR__,
186+
'incrementalFile' => 'foo.snar',
187+
'forceLevelZeroOn' => '%D@Mon|Tue|Wed|Thu|Fri|Sat|Sun'
188+
]
189+
);
190+
191+
$exec = $tar->getExecutable($target);
192+
193+
$this->assertEquals(
194+
PHPBU_TEST_BIN . '/tar --listed-incremental=\'' . $incFile . '\' --level=\'0\' '
195+
. '-cf \'/tmp/backup.tar\' -C \''
196+
. dirname(__DIR__) . '\' \''
197+
. basename(__DIR__) . '\'',
198+
$exec->getCommand()
199+
);
200+
}
201+
202+
/**
203+
* Tests Tar::getExecutable
204+
*/
205+
public function testIncrementalForceZeroFalse()
206+
{
207+
$incFile = getcwd() . '/foo.snar';
208+
$time = time() + (3600 * 48);
209+
$conf = new Configuration();
210+
$conf->setFilename(getcwd() . '/config.xml');
211+
212+
$target = $this->createTargetMock('/tmp/backup.tar');
213+
$target->method('shouldBeCompressed')->willReturn(false);
214+
$target->method('getPathname')->willReturn('/tmp/backup.tar');
215+
216+
$tar = new Tar(null, $time);
217+
$tar->setup(
218+
[
219+
'pathToTar' => PHPBU_TEST_BIN,
220+
'path' => __DIR__,
221+
'incrementalFile' => 'foo.snar',
222+
'forceLevelZeroOn' => '%d@' . date('d')
223+
]
224+
);
225+
226+
$exec = $tar->getExecutable($target);
227+
228+
$this->assertEquals(
229+
PHPBU_TEST_BIN . '/tar --listed-incremental=\'' . $incFile . '\' '
230+
. '-cf \'/tmp/backup.tar\' -C \''
231+
. dirname(__DIR__) . '\' \''
232+
. basename(__DIR__) . '\'',
233+
$exec->getCommand()
234+
);
235+
}
236+
105237
/**
106238
* Tests Tar::getExecutable
107239
*/

0 commit comments

Comments
 (0)