Skip to content

Commit 10d1c3c

Browse files
Merge pull request #48537 from nextcloud/add-command-to-cleanup-preview
2 parents ec7f360 + fe0f89c commit 10d1c3c

File tree

5 files changed

+267
-0
lines changed

5 files changed

+267
-0
lines changed

core/Command/Preview/Cleanup.php

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
7+
* SPDX-License-Identifier: AGPL-3.0-or-later
8+
*/
9+
10+
namespace OC\Core\Command\Preview;
11+
12+
use OC\Core\Command\Base;
13+
use OCP\Files\Folder;
14+
use OCP\Files\IRootFolder;
15+
use OCP\Files\NotFoundException;
16+
use OCP\Files\NotPermittedException;
17+
use Psr\Log\LoggerInterface;
18+
use Symfony\Component\Console\Input\InputInterface;
19+
use Symfony\Component\Console\Output\OutputInterface;
20+
21+
class Cleanup extends Base {
22+
23+
public function __construct(
24+
private IRootFolder $rootFolder,
25+
private LoggerInterface $logger,
26+
) {
27+
parent::__construct();
28+
}
29+
30+
protected function configure(): void {
31+
$this
32+
->setName('preview:cleanup')
33+
->setDescription('Removes existing preview files');
34+
}
35+
36+
protected function execute(InputInterface $input, OutputInterface $output): int {
37+
try {
38+
$appDataFolder = $this->rootFolder->get($this->rootFolder->getAppDataDirectoryName());
39+
40+
if (!$appDataFolder instanceof Folder) {
41+
$this->logger->error("Previews can't be removed: appdata is not a folder");
42+
$output->writeln("Previews can't be removed: appdata is not a folder");
43+
return 1;
44+
}
45+
46+
/** @var Folder $previewFolder */
47+
$previewFolder = $appDataFolder->get('preview');
48+
49+
} catch (NotFoundException $e) {
50+
$this->logger->error("Previews can't be removed: appdata folder can't be found", ['exception' => $e]);
51+
$output->writeln("Previews can't be removed: preview folder isn't deletable");
52+
return 1;
53+
}
54+
55+
if (!$previewFolder->isDeletable()) {
56+
$this->logger->error("Previews can't be removed: preview folder isn't deletable");
57+
$output->writeln("Previews can't be removed: preview folder isn't deletable");
58+
return 1;
59+
}
60+
61+
try {
62+
$previewFolder->delete();
63+
$this->logger->debug('Preview folder deleted');
64+
$output->writeln('Preview folder deleted', OutputInterface::VERBOSITY_VERBOSE);
65+
} catch (NotFoundException $e) {
66+
$output->writeln("Previews weren't deleted: preview folder was not found while deleting it");
67+
$this->logger->error("Previews weren't deleted: preview folder was not found while deleting it", ['exception' => $e]);
68+
return 1;
69+
} catch (NotPermittedException $e) {
70+
$output->writeln("Previews weren't deleted: you don't have the permission to delete preview folder");
71+
$this->logger->error("Previews weren't deleted: you don't have the permission to delete preview folder", ['exception' => $e]);
72+
return 1;
73+
}
74+
75+
try {
76+
$appDataFolder->newFolder('preview');
77+
$this->logger->debug('Preview folder recreated');
78+
$output->writeln('Preview folder recreated', OutputInterface::VERBOSITY_VERBOSE);
79+
} catch (NotPermittedException $e) {
80+
$output->writeln("Preview folder was deleted, but you don't have the permission to create preview folder");
81+
$this->logger->error("Preview folder was deleted, but you don't have the permission to create preview folder", ['exception' => $e]);
82+
return 1;
83+
}
84+
85+
$output->writeln('Previews removed');
86+
return 0;
87+
}
88+
}

core/register_command.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@
102102
$application->add(Server::get(Command\Maintenance\Repair::class));
103103
$application->add(Server::get(Command\Maintenance\RepairShareOwnership::class));
104104

105+
$application->add(Server::get(Command\Preview\Cleanup::class));
105106
$application->add(Server::get(Command\Preview\Generate::class));
106107
$application->add(Server::get(Command\Preview\Repair::class));
107108
$application->add(Server::get(Command\Preview\ResetRenderedTexts::class));

lib/composer/composer/autoload_classmap.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1213,6 +1213,7 @@
12131213
'OC\\Core\\Command\\Maintenance\\UpdateHtaccess' => $baseDir . '/core/Command/Maintenance/UpdateHtaccess.php',
12141214
'OC\\Core\\Command\\Maintenance\\UpdateTheme' => $baseDir . '/core/Command/Maintenance/UpdateTheme.php',
12151215
'OC\\Core\\Command\\Memcache\\RedisCommand' => $baseDir . '/core/Command/Memcache/RedisCommand.php',
1216+
'OC\\Core\\Command\\Preview\\Cleanup' => $baseDir . '/core/Command/Preview/Cleanup.php',
12161217
'OC\\Core\\Command\\Preview\\Generate' => $baseDir . '/core/Command/Preview/Generate.php',
12171218
'OC\\Core\\Command\\Preview\\Repair' => $baseDir . '/core/Command/Preview/Repair.php',
12181219
'OC\\Core\\Command\\Preview\\ResetRenderedTexts' => $baseDir . '/core/Command/Preview/ResetRenderedTexts.php',

lib/composer/composer/autoload_static.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1246,6 +1246,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
12461246
'OC\\Core\\Command\\Maintenance\\UpdateHtaccess' => __DIR__ . '/../../..' . '/core/Command/Maintenance/UpdateHtaccess.php',
12471247
'OC\\Core\\Command\\Maintenance\\UpdateTheme' => __DIR__ . '/../../..' . '/core/Command/Maintenance/UpdateTheme.php',
12481248
'OC\\Core\\Command\\Memcache\\RedisCommand' => __DIR__ . '/../../..' . '/core/Command/Memcache/RedisCommand.php',
1249+
'OC\\Core\\Command\\Preview\\Cleanup' => __DIR__ . '/../../..' . '/core/Command/Preview/Cleanup.php',
12491250
'OC\\Core\\Command\\Preview\\Generate' => __DIR__ . '/../../..' . '/core/Command/Preview/Generate.php',
12501251
'OC\\Core\\Command\\Preview\\Repair' => __DIR__ . '/../../..' . '/core/Command/Preview/Repair.php',
12511252
'OC\\Core\\Command\\Preview\\ResetRenderedTexts' => __DIR__ . '/../../..' . '/core/Command/Preview/ResetRenderedTexts.php',
Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
<?php
2+
/**
3+
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
4+
* SPDX-License-Identifier: AGPL-3.0-or-later
5+
*/
6+
namespace Core\Command\Preview;
7+
8+
use OC\Core\Command\Preview\Cleanup;
9+
use OCP\Files\Folder;
10+
use OCP\Files\IRootFolder;
11+
use OCP\Files\NotFoundException;
12+
use OCP\Files\NotPermittedException;
13+
use PHPUnit\Framework\MockObject\MockObject;
14+
use Psr\Log\LoggerInterface;
15+
use Symfony\Component\Console\Input\InputInterface;
16+
use Symfony\Component\Console\Output\OutputInterface;
17+
use Test\TestCase;
18+
19+
class CleanupTest extends TestCase {
20+
private IRootFolder&MockObject $rootFolder;
21+
private LoggerInterface&MockObject $logger;
22+
private InputInterface&MockObject $input;
23+
private OutputInterface&MockObject $output;
24+
private Cleanup $repair;
25+
26+
protected function setUp(): void {
27+
parent::setUp();
28+
$this->rootFolder = $this->createMock(IRootFolder::class);
29+
$this->logger = $this->createMock(LoggerInterface::class);
30+
$this->repair = new Cleanup(
31+
$this->rootFolder,
32+
$this->logger,
33+
);
34+
35+
$this->input = $this->createMock(InputInterface::class);
36+
$this->output = $this->createMock(OutputInterface::class);
37+
}
38+
39+
public function testCleanup(): void {
40+
$previewFolder = $this->createMock(Folder::class);
41+
$previewFolder->expects($this->once())
42+
->method('isDeletable')
43+
->willReturn(true);
44+
45+
$previewFolder->expects($this->once())
46+
->method('delete');
47+
48+
$appDataFolder = $this->createMock(Folder::class);
49+
$appDataFolder->expects($this->once())->method('get')->with('preview')->willReturn($previewFolder);
50+
$appDataFolder->expects($this->once())->method('newFolder')->with('preview');
51+
52+
$this->rootFolder->expects($this->once())
53+
->method('getAppDataDirectoryName')
54+
->willReturn('appdata_some_id');
55+
56+
$this->rootFolder->expects($this->once())
57+
->method('get')
58+
->with('appdata_some_id')
59+
->willReturn($appDataFolder);
60+
61+
$this->output->expects($this->exactly(3))->method('writeln')
62+
->with(self::callback(function (string $message): bool {
63+
static $i = 0;
64+
return match (++$i) {
65+
1 => $message === 'Preview folder deleted',
66+
2 => $message === 'Preview folder recreated',
67+
3 => $message === 'Previews removed'
68+
};
69+
}));
70+
71+
$this->assertEquals(0, $this->repair->run($this->input, $this->output));
72+
}
73+
74+
public function testCleanupWhenNotDeletable(): void {
75+
$previewFolder = $this->createMock(Folder::class);
76+
$previewFolder->expects($this->once())
77+
->method('isDeletable')
78+
->willReturn(false);
79+
80+
$previewFolder->expects($this->never())
81+
->method('delete');
82+
83+
$appDataFolder = $this->createMock(Folder::class);
84+
$appDataFolder->expects($this->once())->method('get')->with('preview')->willReturn($previewFolder);
85+
$appDataFolder->expects($this->never())->method('newFolder')->with('preview');
86+
87+
$this->rootFolder->expects($this->once())
88+
->method('getAppDataDirectoryName')
89+
->willReturn('appdata_some_id');
90+
91+
$this->rootFolder->expects($this->once())
92+
->method('get')
93+
->with('appdata_some_id')
94+
->willReturn($appDataFolder);
95+
96+
$this->logger->expects($this->once())->method('error')->with("Previews can't be removed: preview folder isn't deletable");
97+
$this->output->expects($this->once())->method('writeln')->with("Previews can't be removed: preview folder isn't deletable");
98+
99+
$this->assertEquals(1, $this->repair->run($this->input, $this->output));
100+
}
101+
102+
/**
103+
* @dataProvider dataForTestCleanupWithDeleteException
104+
*/
105+
public function testCleanupWithDeleteException(string $exceptionClass, string $errorMessage): void {
106+
$previewFolder = $this->createMock(Folder::class);
107+
$previewFolder->expects($this->once())
108+
->method('isDeletable')
109+
->willReturn(true);
110+
111+
$previewFolder->expects($this->once())
112+
->method('delete')
113+
->willThrowException(new $exceptionClass());
114+
115+
$appDataFolder = $this->createMock(Folder::class);
116+
$appDataFolder->expects($this->once())->method('get')->with('preview')->willReturn($previewFolder);
117+
$appDataFolder->expects($this->never())->method('newFolder')->with('preview');
118+
119+
$this->rootFolder->expects($this->once())
120+
->method('getAppDataDirectoryName')
121+
->willReturn('appdata_some_id');
122+
123+
$this->rootFolder->expects($this->once())
124+
->method('get')
125+
->with('appdata_some_id')
126+
->willReturn($appDataFolder);
127+
128+
$this->logger->expects($this->once())->method('error')->with($errorMessage);
129+
$this->output->expects($this->once())->method('writeln')->with($errorMessage);
130+
131+
$this->assertEquals(1, $this->repair->run($this->input, $this->output));
132+
}
133+
134+
public static function dataForTestCleanupWithDeleteException(): array {
135+
return [
136+
[NotFoundException::class, "Previews weren't deleted: preview folder was not found while deleting it"],
137+
[NotPermittedException::class, "Previews weren't deleted: you don't have the permission to delete preview folder"],
138+
];
139+
}
140+
141+
public function testCleanupWithCreateException(): void {
142+
$previewFolder = $this->createMock(Folder::class);
143+
$previewFolder->expects($this->once())
144+
->method('isDeletable')
145+
->willReturn(true);
146+
147+
$previewFolder->expects($this->once())
148+
->method('delete');
149+
150+
$appDataFolder = $this->createMock(Folder::class);
151+
$appDataFolder->expects($this->once())->method('get')->with('preview')->willReturn($previewFolder);
152+
$appDataFolder->expects($this->once())->method('newFolder')->with('preview')->willThrowException(new NotPermittedException());
153+
154+
$this->rootFolder->expects($this->once())
155+
->method('getAppDataDirectoryName')
156+
->willReturn('appdata_some_id');
157+
158+
$this->rootFolder->expects($this->once())
159+
->method('get')
160+
->with('appdata_some_id')
161+
->willReturn($appDataFolder);
162+
163+
$this->output->expects($this->exactly(2))->method('writeln')
164+
->with(self::callback(function (string $message): bool {
165+
static $i = 0;
166+
return match (++$i) {
167+
1 => $message === 'Preview folder deleted',
168+
2 => $message === "Preview folder was deleted, but you don't have the permission to create preview folder",
169+
};
170+
}));
171+
172+
$this->logger->expects($this->once())->method('error')->with("Preview folder was deleted, but you don't have the permission to create preview folder");
173+
174+
$this->assertEquals(1, $this->repair->run($this->input, $this->output));
175+
}
176+
}

0 commit comments

Comments
 (0)