Skip to content

Commit dc39769

Browse files
committed
inital commit
1 parent 6e648df commit dc39769

29 files changed

+1208
-1
lines changed

.editorconfig

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
; This file is for unifying the coding style for different editors and IDEs.
2+
; More information at http://editorconfig.org
3+
4+
root = true
5+
6+
[*]
7+
charset = utf-8
8+
indent_size = 4
9+
indent_style = space
10+
end_of_line = lf
11+
insert_final_newline = true
12+
trim_trailing_whitespace = true
13+
14+
[*.{html,yml,yaml}]
15+
indent_size = 2
16+
17+
[*.{xlf,xml}]
18+
insert_final_newline = false
19+
20+
[*.md]
21+
trim_trailing_whitespace = false

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
node_modules
2+
Documentation/.vuepress/dist
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
<?php
2+
namespace Breadlesscode\Backups\Command;
3+
4+
use Neos\Flow\Cli\CommandController;
5+
use Neos\Flow\Annotations as Flow;
6+
7+
use Breadlesscode\Backups\Service\BackupService;
8+
9+
class BackupCommandController extends CommandController
10+
{
11+
/**
12+
* @Flow\Inject()
13+
* @var BackupService
14+
*/
15+
protected $backupService;
16+
17+
/**
18+
* creates a single backup of a specific package
19+
*/
20+
public function createCommand(): void
21+
{
22+
if ($this->backupService->noStepsConfigured()) {
23+
$this->outputError('No backup steps configured. Please configure Breadlesscode.Backups.steps');
24+
$this->quit();
25+
}
26+
27+
$this->output('Creating backup...');
28+
$this->backupService->createBackup();
29+
$this->outputLine('<success>success</success>');
30+
}
31+
32+
/**
33+
* restores a single backup of a specific package
34+
*/
35+
public function restoreCommand(string $name, bool $noConfirm = false): void
36+
{
37+
$shouldRestore = true;
38+
$backup = $this->backupService->getBackup($name);
39+
$steps = $this->backupService->getStepsInstances(
40+
$this->backupService->getTemporaryBackupPath(),
41+
$backup['meta']['steps']
42+
);
43+
// print warnings
44+
foreach ($steps as $stepClass => $step) {
45+
/** @var $step StepInterface */
46+
$message = $step->getRestoreWarning();
47+
48+
if($message !== null) {
49+
$this->outputLine('<error>'.$stepClass.'</error>');
50+
$this->outputLine('<comment>'.$message.'</comment>');
51+
$this->outputLine();
52+
}
53+
}
54+
55+
if ($noConfirm === false) {
56+
$shouldRestore = $this->output->askConfirmation('Are you sure you want to restore this Backup?', false);
57+
}
58+
59+
if(!$shouldRestore) {
60+
$this->outputLine();
61+
$this->outputLine('<error>Canceled by user</error>');
62+
$this->quit();
63+
}
64+
65+
$this->output('Restoring backup...');
66+
try {
67+
$this->backupService->restoreBackup($name);
68+
$this->outputLine('<success>success</success>');
69+
} catch (\Exception $e) {
70+
$this->outputError($e->getMessage());
71+
}
72+
}
73+
74+
/**
75+
* lists all backups
76+
*/
77+
public function listCommand($offset = 0, $limit = 60): void
78+
{
79+
$backups = $this->backupService->getBackups($offset, $limit);
80+
81+
$backups = array_map(function($backup) {
82+
unset($backup['meta']);
83+
return $backup;
84+
}, $backups);
85+
86+
$this->outputLine();
87+
$this->output->outputTable($backups, ['Name', 'Date', 'Relative date']);
88+
}
89+
90+
/**
91+
* deletes backups
92+
*/
93+
public function deleteCommand(string $name, bool $noConfirm = false): void
94+
{
95+
$confirmed = true;
96+
97+
if (!$noConfirm) {
98+
$confirmed = $this->output->askConfirmation('Are you sure you want to delete this backup?');
99+
}
100+
101+
if (!$confirmed) {
102+
$this->outputLine();
103+
$this->outputLine('<error>Canceled by user</error>');
104+
$this->quit();
105+
}
106+
107+
$this->output('Deleting backup...');
108+
109+
try {
110+
$this->backupService->deleteBackup($name);
111+
$this->outputLine('<success>success</success>');
112+
} catch (\Exception $e) {
113+
$this->outputError($e->getMessage());
114+
}
115+
}
116+
117+
protected function outputError(string $message): void
118+
{
119+
$this->outputLine('<error>failed</error>');
120+
$this->outputLine();
121+
$this->outputLine('Reason:');
122+
$this->outputLine($message);
123+
}
124+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<?php
2+
namespace Breadlesscode\Backups\Compressor;
3+
4+
abstract class AbstractCompressor implements BackupCompressorInterface
5+
{
6+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?php
2+
namespace Breadlesscode\Backups\Compressor;
3+
4+
interface BackupCompressorInterface
5+
{
6+
public function compress(string $source, string $targetFolder): ?string;
7+
public function decompress(string $source, string $targetFolder): ?string;
8+
public function generateFilename($name): string;
9+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
<?php
2+
namespace Breadlesscode\Backups\Compressor;
3+
4+
use Neos\Utility\Files;
5+
6+
class ZipCompressor extends AbstractCompressor
7+
{
8+
public function compress(string $source, string $targetFolder): ?string
9+
{
10+
$archive = new \ZipArchive();
11+
$archivePath = $targetFolder.'/'.$this->generateFilename(basename($source));
12+
$archive->open($archivePath, \ZipArchive::CREATE);
13+
14+
$filesIterator = new \RecursiveIteratorIterator(
15+
new \RecursiveDirectoryIterator($source),
16+
\RecursiveIteratorIterator::LEAVES_ONLY
17+
);
18+
19+
foreach ($filesIterator as $name => $file) {
20+
if (!$file->isDir()) {
21+
$filePath = $file->getRealPath();
22+
$relativePath = substr($filePath, strlen($source) + 1);
23+
24+
$archive->addFile($filePath, $relativePath);
25+
}
26+
}
27+
28+
return $archive->close() ? $archivePath : null;
29+
}
30+
31+
public function decompress(string $source, string $targetFolder): ?string
32+
{
33+
$zip = new \ZipArchive();
34+
$success = $zip->open($source);
35+
36+
if ($success === false) {
37+
return null;
38+
}
39+
40+
Files::createDirectoryRecursively($targetFolder);
41+
$success = $zip->extractTo($targetFolder);
42+
$zip->close();
43+
44+
return $success ? $targetFolder : null;
45+
}
46+
47+
public function generateFilename($name): string
48+
{
49+
return $name.'.zip';
50+
}
51+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?php
2+
namespace Breadlesscode\Backups\Factory;
3+
4+
abstract class AbstractAdapterFactory implements AdapterFactoryInterface
5+
{
6+
protected $config;
7+
8+
public function setConfig(array $config): void
9+
{
10+
$this->config = $config;
11+
}
12+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?php
2+
namespace Breadlesscode\Backups\Factory;
3+
4+
use League\Flysystem\AdapterInterface;
5+
6+
interface AdapterFactoryInterface
7+
{
8+
public function setConfig(array $config): void;
9+
public function get(): AdapterInterface;
10+
//public function validateConfig(): bool; maybe in the future
11+
public function getComposerDependencies(): array; // @todo validate this
12+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?php
2+
namespace Breadlesscode\Backups\Factory;
3+
4+
use League\Flysystem\AdapterInterface;
5+
use Aws\S3\S3Client;
6+
use League\Flysystem\AwsS3v3\AwsS3Adapter;
7+
8+
class AwsS3AdapterFactory extends AbstractAdapterFactory
9+
{
10+
public function get(): AdapterInterface
11+
{
12+
$client = new S3Client([
13+
'credentials' => [
14+
'key' => $this->config['credentials']['key'],
15+
'secret' => $this->config['credentials']['secret'],
16+
],
17+
'region' => $this->config['region'],
18+
'version' => 'latest',
19+
]);
20+
21+
return new AwsS3Adapter($client, $this->config['bucketName']);
22+
}
23+
24+
public function getComposerDependencies(): array
25+
{
26+
return [
27+
'league/flysystem-aws-s3-v3'
28+
];
29+
}
30+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?php
2+
namespace Breadlesscode\Backups\Factory;
3+
4+
use League\Flysystem\AdapterInterface;
5+
use League\Flysystem\AzureBlobStorage\AzureBlobStorageAdapter;
6+
use MicrosoftAzure\Storage\Blob\BlobRestProxy;
7+
8+
class AzureBlobAdapterFactory extends AbstractAdapterFactory
9+
{
10+
public function get(): AdapterInterface
11+
{
12+
$connectionStr = 'DefaultEndpointsProtocol=https;AccountName=%s;AccountKey=%s;';
13+
$client = BlobRestProxy::createBlobService(vsprintf($connectionStr, [
14+
$this->config['account']['name'],
15+
$this->config['account']['key'],
16+
]));
17+
18+
return new AzureBlobStorageAdapter($client, $this->config['containerName']);
19+
}
20+
21+
public function getComposerDependencies(): array
22+
{
23+
return [
24+
'league/flysystem-azure-blob-storage'
25+
];
26+
}
27+
}

0 commit comments

Comments
 (0)