Skip to content

Commit 82b38ab

Browse files
committed
use external storage as appdata
Signed-off-by: Maxence Lange <maxence@artificial-owl.com>
1 parent 8b750c2 commit 82b38ab

13 files changed

+597
-80
lines changed

appinfo/info.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
<command>OCA\Backup\Command\RemoteRemove</command>
4242

4343
<command>OCA\Backup\Command\ExternalAdd</command>
44+
<command>OCA\Backup\Command\ExternalAppData</command>
4445
<command>OCA\Backup\Command\ExternalList</command>
4546
<command>OCA\Backup\Command\ExternalRemove</command>
4647

lib/Command/ExternalAdd.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,6 @@ protected function execute(InputInterface $input, OutputInterface $output): int
9494
}
9595

9696
$root = $this->requestingRoot($input, $output);
97-
echo '>> ' . json_encode($root);
9897
$output->writeln('');
9998
if ($root === '') {
10099
$output->writeln('Operation cancelled');

lib/Command/ExternalAppData.php

Lines changed: 235 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,235 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
6+
/**
7+
* Nextcloud - Backup now. Restore later.
8+
*
9+
* This file is licensed under the Affero General Public License version 3 or
10+
* later. See the COPYING file.
11+
*
12+
* @author Maxence Lange <maxence@artificial-owl.com>
13+
* @copyright 2021, Maxence Lange <maxence@artificial-owl.com>
14+
* @license GNU AGPL version 3 or any later version
15+
*
16+
* This program is free software: you can redistribute it and/or modify
17+
* it under the terms of the GNU Affero General Public License as
18+
* published by the Free Software Foundation, either version 3 of the
19+
* License, or (at your option) any later version.
20+
*
21+
* This program is distributed in the hope that it will be useful,
22+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
23+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
24+
* GNU Affero General Public License for more details.
25+
*
26+
* You should have received a copy of the GNU Affero General Public License
27+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
28+
*
29+
*/
30+
31+
32+
namespace OCA\Backup\Command;
33+
34+
use ArtificialOwl\MySmallPhpTools\Traits\Nextcloud\nc23\TNC23Deserialize;
35+
use OC\Core\Command\Base;
36+
use OCA\Backup\Exceptions\ExternalFolderNotFoundException;
37+
use OCA\Backup\Service\ConfigService;
38+
use OCA\Backup\Service\ExternalFolderService;
39+
use OCA\Backup\Service\PointService;
40+
use OCA\Files_External\Lib\InsufficientDataForMeaningfulAnswerException;
41+
use OCP\Files\StorageNotAvailableException;
42+
use Symfony\Component\Console\Input\InputInterface;
43+
use Symfony\Component\Console\Output\OutputInterface;
44+
use Symfony\Component\Console\Question\ChoiceQuestion;
45+
use Symfony\Component\Console\Question\ConfirmationQuestion;
46+
use Symfony\Component\Console\Question\Question;
47+
48+
/**
49+
* Class ExternalAppData
50+
*
51+
* @package OCA\Backup\Command
52+
*/
53+
class ExternalAppData extends Base {
54+
use TNC23Deserialize;
55+
56+
57+
/** @var PointService */
58+
private $pointService;
59+
60+
/** @var ExternalFolderService */
61+
private $externalFolderService;
62+
63+
/** @var ConfigService */
64+
private $configService;
65+
66+
67+
/**
68+
* ExternalAppData constructor.
69+
*
70+
* @param PointService $pointService
71+
* @param ExternalFolderService $externalFolderService
72+
* @param ConfigService $configService
73+
*/
74+
public function __construct(
75+
PointService $pointService,
76+
ExternalFolderService $externalFolderService,
77+
ConfigService $configService
78+
) {
79+
parent::__construct();
80+
81+
$this->pointService = $pointService;
82+
$this->externalFolderService = $externalFolderService;
83+
$this->configService = $configService;
84+
}
85+
86+
87+
/**
88+
*
89+
*/
90+
protected function configure() {
91+
$this->setName('backup:external:appdata')
92+
->setDescription('Add external filesystem to store the app\'s data');
93+
}
94+
95+
96+
/**
97+
* @param InputInterface $input
98+
* @param OutputInterface $output
99+
*
100+
* @return int
101+
* @throws InsufficientDataForMeaningfulAnswerException
102+
* @throws StorageNotAvailableException
103+
* @throws ExternalFolderNotFoundException
104+
*/
105+
protected function execute(InputInterface $input, OutputInterface $output): int {
106+
$output->writeln(
107+
'This configuration tool will help you set the <info>Appdata</info> folder of the Backup App on an <info>external storage</info>'
108+
);
109+
$output->writeln('...');
110+
$output->writeln('');
111+
$output->writeln('<error>All previous Restoring Point will be lost during this process</error>');
112+
$output->writeln('');
113+
$output->writeln('');
114+
$storageId = $this->selectStorage($input, $output);
115+
$output->writeln('');
116+
if ($storageId === 0) {
117+
$output->writeln('Operation cancelled');
118+
119+
return 0;
120+
}
121+
122+
$root = $this->requestingRoot($input, $output);
123+
$output->writeln('');
124+
if ($root === '') {
125+
$output->writeln('Operation cancelled');
126+
127+
return 0;
128+
}
129+
130+
$external = $this->externalFolderService->getStorageById($storageId);
131+
$output->writeln('');
132+
if ($external->getRoot() !== '') {
133+
$output->writeln('This external filesystem is already used by the Backup App');
134+
135+
return 0;
136+
}
137+
138+
$external->setRoot($root);
139+
140+
$output->writeln('Please confirm the creation of a new External Folder, based on this setup:');
141+
$output->writeln('');
142+
$output->writeln('Storage Id: <info>' . $external->getStorageId() . '</info>');
143+
$output->writeln('Storage Path: <info>' . $external->getStorage() . '</info>');
144+
$output->writeln('Localisation of backup files: <info>' . $external->getRoot() . '</info>');
145+
$output->writeln('');
146+
147+
$question = new ConfirmationQuestion(
148+
'<comment>Do you really want to create and use this External Folder as appdata ?</comment> (y/N) ',
149+
false,
150+
'/^(y|Y)/i'
151+
);
152+
153+
$helper = $this->getHelper('question');
154+
if (!$helper->ask($input, $output, $question)) {
155+
$output->writeln('Operation cancelled');
156+
157+
return 0;
158+
}
159+
160+
161+
$this->pointService->destroyBackupFS();
162+
$this->configService->setAppValueArray(ConfigService::EXTERNAL_APPDATA, $this->serialize($external));
163+
164+
$output->writeln('done');
165+
166+
return 0;
167+
}
168+
169+
170+
/**
171+
* @param InputInterface $input
172+
* @param OutputInterface $output
173+
*
174+
* @return int|mixed|string|null
175+
* @throws InsufficientDataForMeaningfulAnswerException
176+
* @throws StorageNotAvailableException
177+
*/
178+
private function selectStorage(InputInterface $input, OutputInterface $output): int {
179+
$availableStorage = [];
180+
foreach ($this->externalFolderService->getStorages() as $storage) {
181+
if ($storage->getRoot() !== '') {
182+
continue;
183+
}
184+
$availableStorage[$storage->getStorageId()] =
185+
$storage->getStorage() . ' (id:' . $storage->getStorageId() . ')';
186+
}
187+
188+
if (empty($availableStorage)) {
189+
$output->writeln('There is no available external filesystem.');
190+
$output->writeln('You can use the <info>Files External</info> to add a new external filesystem');
191+
$output->writeln('');
192+
193+
return 0;
194+
}
195+
196+
$availableStorage[0] = 'exit';
197+
198+
$output->writeln('');
199+
$helper = $this->getHelper('question');
200+
$question = new ChoiceQuestion(
201+
'Which external storage you want to use to store your backups ?',
202+
$availableStorage,
203+
0
204+
);
205+
$question->setErrorMessage('Select a valid filesystem');
206+
207+
$result = $helper->ask($input, $output, $question);
208+
foreach ($availableStorage as $k => $v) {
209+
if ($v === $result) {
210+
return $k;
211+
}
212+
}
213+
214+
return 0;
215+
}
216+
217+
218+
/**
219+
* @param InputInterface $input
220+
* @param OutputInterface $output
221+
*
222+
* @return string
223+
*/
224+
private function requestingRoot(InputInterface $input, OutputInterface $output): string {
225+
$helper = $this->getHelper('question');
226+
$default = 'backups/';
227+
228+
$question = new Question(
229+
'Path to the right folder to store your backups (default="<info>' . $default . '</info>"): ',
230+
$default
231+
);
232+
233+
return trim($helper->ask($input, $output, $question));
234+
}
235+
}

lib/Command/PointDetails.php

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -99,11 +99,11 @@ class PointDetails extends Base {
9999
* @param PackService $packService
100100
*/
101101
public function __construct(
102-
RemoteService $remoteService,
103-
PointService $pointService,
102+
RemoteService $remoteService,
103+
PointService $pointService,
104104
ExternalFolderService $externalFolderService,
105-
ChunkService $chunkService,
106-
PackService $packService
105+
ChunkService $chunkService,
106+
PackService $packService
107107
) {
108108
parent::__construct();
109109

@@ -206,8 +206,8 @@ protected function execute(InputInterface $input, OutputInterface $output): int
206206
$checked = '<error>missing chunk</error>';
207207
}
208208

209-
$color = ($checked === $chunk->getChecksum()) ? 'info' : 'error';
210-
$checked = '<' . $color . '>ok</' . $color . '>';
209+
$checked =
210+
($checked === $chunk->getChecksum()) ? '<info>ok</info>' : '<error>checksum</error>';
211211

212212
$table->appendRow(
213213
[
@@ -253,7 +253,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int
253253
* @param RestoringChunk $chunk
254254
*/
255255
private function displayDetailsPacked(
256-
Table $table,
256+
Table $table,
257257
RestoringPoint $point,
258258
RestoringChunk $chunk
259259
): void {
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
6+
/**
7+
* Nextcloud - Backup now. Restore later.
8+
*
9+
* This file is licensed under the Affero General Public License version 3 or
10+
* later. See the COPYING file.
11+
*
12+
* @author Maxence Lange <maxence@artificial-owl.com>
13+
* @copyright 2021, Maxence Lange <maxence@artificial-owl.com>
14+
* @license GNU AGPL version 3 or any later version
15+
*
16+
* This program is free software: you can redistribute it and/or modify
17+
* it under the terms of the GNU Affero General Public License as
18+
* published by the Free Software Foundation, either version 3 of the
19+
* License, or (at your option) any later version.
20+
*
21+
* This program is distributed in the hope that it will be useful,
22+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
23+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
24+
* GNU Affero General Public License for more details.
25+
*
26+
* You should have received a copy of the GNU Affero General Public License
27+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
28+
*
29+
*/
30+
31+
32+
namespace OCA\Backup\Exceptions;
33+
34+
use Exception;
35+
36+
/**
37+
* Class ExternalAppdataException
38+
*
39+
* @package OCA\Backup\Exceptions
40+
*/
41+
class ExternalAppdataException extends Exception {
42+
}

lib/Model/ExternalFolder.php

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
namespace OCA\Backup\Model;
3333

3434
use ArtificialOwl\MySmallPhpTools\Db\Nextcloud\nc23\INC23QueryRow;
35+
use ArtificialOwl\MySmallPhpTools\Exceptions\InvalidItemException;
3536
use ArtificialOwl\MySmallPhpTools\IDeserializable;
3637
use ArtificialOwl\MySmallPhpTools\Traits\TArrayTools;
3738
use ArtificialOwl\MySmallPhpTools\Traits\TStringTools;
@@ -49,7 +50,7 @@ class ExternalFolder implements JsonSerializable, INC23QueryRow, IDeserializable
4950

5051

5152
/** @var int */
52-
private $storageId;
53+
private $storageId = 0;
5354

5455
/** @var string */
5556
private $storage;
@@ -170,12 +171,17 @@ public function importFromDatabase(array $data): INC23QueryRow {
170171
* @param array $data
171172
*
172173
* @return ExternalFolder
174+
* @throws InvalidItemException
173175
*/
174176
public function import(array $data): IDeserializable {
175177
$this->setStorageId($this->getInt('storageId', $data))
176178
->setStorage($this->get('storage', $data))
177179
->setRoot($this->get('root', $data));
178180

181+
if ($this->getStorageId() === 0) {
182+
throw new InvalidItemException();
183+
}
184+
179185
return $this;
180186
}
181187

0 commit comments

Comments
 (0)