aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorKate <26026535+provokateurin@users.noreply.github.com>2024-10-14 08:57:00 +0200
committerGitHub <noreply@github.com>2024-10-14 08:57:00 +0200
commit10d1c3c29ffe3af905ddaa16011f268efc5fe6bb (patch)
treed42be1301afe26c1dba7b023830eb6c6df8f5bb1
parentec7f3605ac7356b2f5ecd1c8dbabc62ccccd8e21 (diff)
parentfe0f89c237f9923630ac59ffdef22f80d097e3f5 (diff)
downloadnextcloud-server-10d1c3c29ffe3af905ddaa16011f268efc5fe6bb.tar.gz
nextcloud-server-10d1c3c29ffe3af905ddaa16011f268efc5fe6bb.zip
Merge pull request #48537 from nextcloud/add-command-to-cleanup-preview
-rw-r--r--core/Command/Preview/Cleanup.php88
-rw-r--r--core/register_command.php1
-rw-r--r--lib/composer/composer/autoload_classmap.php1
-rw-r--r--lib/composer/composer/autoload_static.php1
-rw-r--r--tests/Core/Command/Preview/CleanupTest.php176
5 files changed, 267 insertions, 0 deletions
diff --git a/core/Command/Preview/Cleanup.php b/core/Command/Preview/Cleanup.php
new file mode 100644
index 00000000000..2049bb98bc4
--- /dev/null
+++ b/core/Command/Preview/Cleanup.php
@@ -0,0 +1,88 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace OC\Core\Command\Preview;
+
+use OC\Core\Command\Base;
+use OCP\Files\Folder;
+use OCP\Files\IRootFolder;
+use OCP\Files\NotFoundException;
+use OCP\Files\NotPermittedException;
+use Psr\Log\LoggerInterface;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+
+class Cleanup extends Base {
+
+ public function __construct(
+ private IRootFolder $rootFolder,
+ private LoggerInterface $logger,
+ ) {
+ parent::__construct();
+ }
+
+ protected function configure(): void {
+ $this
+ ->setName('preview:cleanup')
+ ->setDescription('Removes existing preview files');
+ }
+
+ protected function execute(InputInterface $input, OutputInterface $output): int {
+ try {
+ $appDataFolder = $this->rootFolder->get($this->rootFolder->getAppDataDirectoryName());
+
+ if (!$appDataFolder instanceof Folder) {
+ $this->logger->error("Previews can't be removed: appdata is not a folder");
+ $output->writeln("Previews can't be removed: appdata is not a folder");
+ return 1;
+ }
+
+ /** @var Folder $previewFolder */
+ $previewFolder = $appDataFolder->get('preview');
+
+ } catch (NotFoundException $e) {
+ $this->logger->error("Previews can't be removed: appdata folder can't be found", ['exception' => $e]);
+ $output->writeln("Previews can't be removed: preview folder isn't deletable");
+ return 1;
+ }
+
+ if (!$previewFolder->isDeletable()) {
+ $this->logger->error("Previews can't be removed: preview folder isn't deletable");
+ $output->writeln("Previews can't be removed: preview folder isn't deletable");
+ return 1;
+ }
+
+ try {
+ $previewFolder->delete();
+ $this->logger->debug('Preview folder deleted');
+ $output->writeln('Preview folder deleted', OutputInterface::VERBOSITY_VERBOSE);
+ } catch (NotFoundException $e) {
+ $output->writeln("Previews weren't deleted: preview folder was not found while deleting it");
+ $this->logger->error("Previews weren't deleted: preview folder was not found while deleting it", ['exception' => $e]);
+ return 1;
+ } catch (NotPermittedException $e) {
+ $output->writeln("Previews weren't deleted: you don't have the permission to delete preview folder");
+ $this->logger->error("Previews weren't deleted: you don't have the permission to delete preview folder", ['exception' => $e]);
+ return 1;
+ }
+
+ try {
+ $appDataFolder->newFolder('preview');
+ $this->logger->debug('Preview folder recreated');
+ $output->writeln('Preview folder recreated', OutputInterface::VERBOSITY_VERBOSE);
+ } catch (NotPermittedException $e) {
+ $output->writeln("Preview folder was deleted, but you don't have the permission to create preview folder");
+ $this->logger->error("Preview folder was deleted, but you don't have the permission to create preview folder", ['exception' => $e]);
+ return 1;
+ }
+
+ $output->writeln('Previews removed');
+ return 0;
+ }
+}
diff --git a/core/register_command.php b/core/register_command.php
index faa2c3021a7..08a01d7ff6e 100644
--- a/core/register_command.php
+++ b/core/register_command.php
@@ -102,6 +102,7 @@ if ($config->getSystemValueBool('installed', false)) {
$application->add(Server::get(Command\Maintenance\Repair::class));
$application->add(Server::get(Command\Maintenance\RepairShareOwnership::class));
+ $application->add(Server::get(Command\Preview\Cleanup::class));
$application->add(Server::get(Command\Preview\Generate::class));
$application->add(Server::get(Command\Preview\Repair::class));
$application->add(Server::get(Command\Preview\ResetRenderedTexts::class));
diff --git a/lib/composer/composer/autoload_classmap.php b/lib/composer/composer/autoload_classmap.php
index 35416cc1303..1219c0f5aa4 100644
--- a/lib/composer/composer/autoload_classmap.php
+++ b/lib/composer/composer/autoload_classmap.php
@@ -1213,6 +1213,7 @@ return array(
'OC\\Core\\Command\\Maintenance\\UpdateHtaccess' => $baseDir . '/core/Command/Maintenance/UpdateHtaccess.php',
'OC\\Core\\Command\\Maintenance\\UpdateTheme' => $baseDir . '/core/Command/Maintenance/UpdateTheme.php',
'OC\\Core\\Command\\Memcache\\RedisCommand' => $baseDir . '/core/Command/Memcache/RedisCommand.php',
+ 'OC\\Core\\Command\\Preview\\Cleanup' => $baseDir . '/core/Command/Preview/Cleanup.php',
'OC\\Core\\Command\\Preview\\Generate' => $baseDir . '/core/Command/Preview/Generate.php',
'OC\\Core\\Command\\Preview\\Repair' => $baseDir . '/core/Command/Preview/Repair.php',
'OC\\Core\\Command\\Preview\\ResetRenderedTexts' => $baseDir . '/core/Command/Preview/ResetRenderedTexts.php',
diff --git a/lib/composer/composer/autoload_static.php b/lib/composer/composer/autoload_static.php
index 71eb43cddf8..0e93ee8addd 100644
--- a/lib/composer/composer/autoload_static.php
+++ b/lib/composer/composer/autoload_static.php
@@ -1246,6 +1246,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
'OC\\Core\\Command\\Maintenance\\UpdateHtaccess' => __DIR__ . '/../../..' . '/core/Command/Maintenance/UpdateHtaccess.php',
'OC\\Core\\Command\\Maintenance\\UpdateTheme' => __DIR__ . '/../../..' . '/core/Command/Maintenance/UpdateTheme.php',
'OC\\Core\\Command\\Memcache\\RedisCommand' => __DIR__ . '/../../..' . '/core/Command/Memcache/RedisCommand.php',
+ 'OC\\Core\\Command\\Preview\\Cleanup' => __DIR__ . '/../../..' . '/core/Command/Preview/Cleanup.php',
'OC\\Core\\Command\\Preview\\Generate' => __DIR__ . '/../../..' . '/core/Command/Preview/Generate.php',
'OC\\Core\\Command\\Preview\\Repair' => __DIR__ . '/../../..' . '/core/Command/Preview/Repair.php',
'OC\\Core\\Command\\Preview\\ResetRenderedTexts' => __DIR__ . '/../../..' . '/core/Command/Preview/ResetRenderedTexts.php',
diff --git a/tests/Core/Command/Preview/CleanupTest.php b/tests/Core/Command/Preview/CleanupTest.php
new file mode 100644
index 00000000000..fcbb7582ca3
--- /dev/null
+++ b/tests/Core/Command/Preview/CleanupTest.php
@@ -0,0 +1,176 @@
+<?php
+/**
+ * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+namespace Core\Command\Preview;
+
+use OC\Core\Command\Preview\Cleanup;
+use OCP\Files\Folder;
+use OCP\Files\IRootFolder;
+use OCP\Files\NotFoundException;
+use OCP\Files\NotPermittedException;
+use PHPUnit\Framework\MockObject\MockObject;
+use Psr\Log\LoggerInterface;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+use Test\TestCase;
+
+class CleanupTest extends TestCase {
+ private IRootFolder&MockObject $rootFolder;
+ private LoggerInterface&MockObject $logger;
+ private InputInterface&MockObject $input;
+ private OutputInterface&MockObject $output;
+ private Cleanup $repair;
+
+ protected function setUp(): void {
+ parent::setUp();
+ $this->rootFolder = $this->createMock(IRootFolder::class);
+ $this->logger = $this->createMock(LoggerInterface::class);
+ $this->repair = new Cleanup(
+ $this->rootFolder,
+ $this->logger,
+ );
+
+ $this->input = $this->createMock(InputInterface::class);
+ $this->output = $this->createMock(OutputInterface::class);
+ }
+
+ public function testCleanup(): void {
+ $previewFolder = $this->createMock(Folder::class);
+ $previewFolder->expects($this->once())
+ ->method('isDeletable')
+ ->willReturn(true);
+
+ $previewFolder->expects($this->once())
+ ->method('delete');
+
+ $appDataFolder = $this->createMock(Folder::class);
+ $appDataFolder->expects($this->once())->method('get')->with('preview')->willReturn($previewFolder);
+ $appDataFolder->expects($this->once())->method('newFolder')->with('preview');
+
+ $this->rootFolder->expects($this->once())
+ ->method('getAppDataDirectoryName')
+ ->willReturn('appdata_some_id');
+
+ $this->rootFolder->expects($this->once())
+ ->method('get')
+ ->with('appdata_some_id')
+ ->willReturn($appDataFolder);
+
+ $this->output->expects($this->exactly(3))->method('writeln')
+ ->with(self::callback(function (string $message): bool {
+ static $i = 0;
+ return match (++$i) {
+ 1 => $message === 'Preview folder deleted',
+ 2 => $message === 'Preview folder recreated',
+ 3 => $message === 'Previews removed'
+ };
+ }));
+
+ $this->assertEquals(0, $this->repair->run($this->input, $this->output));
+ }
+
+ public function testCleanupWhenNotDeletable(): void {
+ $previewFolder = $this->createMock(Folder::class);
+ $previewFolder->expects($this->once())
+ ->method('isDeletable')
+ ->willReturn(false);
+
+ $previewFolder->expects($this->never())
+ ->method('delete');
+
+ $appDataFolder = $this->createMock(Folder::class);
+ $appDataFolder->expects($this->once())->method('get')->with('preview')->willReturn($previewFolder);
+ $appDataFolder->expects($this->never())->method('newFolder')->with('preview');
+
+ $this->rootFolder->expects($this->once())
+ ->method('getAppDataDirectoryName')
+ ->willReturn('appdata_some_id');
+
+ $this->rootFolder->expects($this->once())
+ ->method('get')
+ ->with('appdata_some_id')
+ ->willReturn($appDataFolder);
+
+ $this->logger->expects($this->once())->method('error')->with("Previews can't be removed: preview folder isn't deletable");
+ $this->output->expects($this->once())->method('writeln')->with("Previews can't be removed: preview folder isn't deletable");
+
+ $this->assertEquals(1, $this->repair->run($this->input, $this->output));
+ }
+
+ /**
+ * @dataProvider dataForTestCleanupWithDeleteException
+ */
+ public function testCleanupWithDeleteException(string $exceptionClass, string $errorMessage): void {
+ $previewFolder = $this->createMock(Folder::class);
+ $previewFolder->expects($this->once())
+ ->method('isDeletable')
+ ->willReturn(true);
+
+ $previewFolder->expects($this->once())
+ ->method('delete')
+ ->willThrowException(new $exceptionClass());
+
+ $appDataFolder = $this->createMock(Folder::class);
+ $appDataFolder->expects($this->once())->method('get')->with('preview')->willReturn($previewFolder);
+ $appDataFolder->expects($this->never())->method('newFolder')->with('preview');
+
+ $this->rootFolder->expects($this->once())
+ ->method('getAppDataDirectoryName')
+ ->willReturn('appdata_some_id');
+
+ $this->rootFolder->expects($this->once())
+ ->method('get')
+ ->with('appdata_some_id')
+ ->willReturn($appDataFolder);
+
+ $this->logger->expects($this->once())->method('error')->with($errorMessage);
+ $this->output->expects($this->once())->method('writeln')->with($errorMessage);
+
+ $this->assertEquals(1, $this->repair->run($this->input, $this->output));
+ }
+
+ public static function dataForTestCleanupWithDeleteException(): array {
+ return [
+ [NotFoundException::class, "Previews weren't deleted: preview folder was not found while deleting it"],
+ [NotPermittedException::class, "Previews weren't deleted: you don't have the permission to delete preview folder"],
+ ];
+ }
+
+ public function testCleanupWithCreateException(): void {
+ $previewFolder = $this->createMock(Folder::class);
+ $previewFolder->expects($this->once())
+ ->method('isDeletable')
+ ->willReturn(true);
+
+ $previewFolder->expects($this->once())
+ ->method('delete');
+
+ $appDataFolder = $this->createMock(Folder::class);
+ $appDataFolder->expects($this->once())->method('get')->with('preview')->willReturn($previewFolder);
+ $appDataFolder->expects($this->once())->method('newFolder')->with('preview')->willThrowException(new NotPermittedException());
+
+ $this->rootFolder->expects($this->once())
+ ->method('getAppDataDirectoryName')
+ ->willReturn('appdata_some_id');
+
+ $this->rootFolder->expects($this->once())
+ ->method('get')
+ ->with('appdata_some_id')
+ ->willReturn($appDataFolder);
+
+ $this->output->expects($this->exactly(2))->method('writeln')
+ ->with(self::callback(function (string $message): bool {
+ static $i = 0;
+ return match (++$i) {
+ 1 => $message === 'Preview folder deleted',
+ 2 => $message === "Preview folder was deleted, but you don't have the permission to create preview folder",
+ };
+ }));
+
+ $this->logger->expects($this->once())->method('error')->with("Preview folder was deleted, but you don't have the permission to create preview folder");
+
+ $this->assertEquals(1, $this->repair->run($this->input, $this->output));
+ }
+}