diff --git a/core/Command/Preview/Cleanup.php b/core/Command/Preview/Cleanup.php new file mode 100644 index 0000000000000..1b98a4b8add3a --- /dev/null +++ b/core/Command/Preview/Cleanup.php @@ -0,0 +1,91 @@ +setName('preview:cleanup') + ->setDescription('Removes existing preview files'); + } + + protected function execute(InputInterface $input, OutputInterface $output): int { + $instanceId = $this->config->getSystemValueString('instanceid'); + try { + $appDataFolder = $this->rootFolder->get("appdata_$instanceId"); + + 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 faa2c3021a7b3..08a01d7ff6e08 100644 --- a/core/register_command.php +++ b/core/register_command.php @@ -102,6 +102,7 @@ $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/tests/Core/Command/Preview/CleanupTest.php b/tests/Core/Command/Preview/CleanupTest.php new file mode 100644 index 0000000000000..05a83ed680644 --- /dev/null +++ b/tests/Core/Command/Preview/CleanupTest.php @@ -0,0 +1,172 @@ +config = $this->createMock(IConfig::class); + $this->rootFolder = $this->createMock(IRootFolder::class); + $this->logger = $this->createMock(LoggerInterface::class); + $this->repair = new Cleanup( + $this->config, + $this->rootFolder, + $this->logger, + ); + + $this->input = $this->createMock(InputInterface::class); + $this->output = $this->createMock(OutputInterface::class); + } + + public function testCleanup(): void { + $this->config->expects($this->once())->method('getSystemValueString')->with('instanceid')->willReturn('some_id'); + + $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('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 { + $this->config->expects($this->once())->method('getSystemValueString')->with('instanceid')->willReturn('some_id'); + + $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('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 { + $this->config->expects($this->once())->method('getSystemValueString')->with('instanceid')->willReturn('some_id'); + + $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('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 { + $this->config->expects($this->once())->method('getSystemValueString')->with('instanceid')->willReturn('some_id'); + + $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('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)); + } +}