Skip to content

Commit

Permalink
Merge pull request #48537 from nextcloud/add-command-to-cleanup-preview
Browse files Browse the repository at this point in the history
  • Loading branch information
provokateurin authored Oct 14, 2024
2 parents ec7f360 + fe0f89c commit 10d1c3c
Show file tree
Hide file tree
Showing 5 changed files with 267 additions and 0 deletions.
88 changes: 88 additions & 0 deletions core/Command/Preview/Cleanup.php
Original file line number Diff line number Diff line change
@@ -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;
}
}
1 change: 1 addition & 0 deletions core/register_command.php
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down
1 change: 1 addition & 0 deletions lib/composer/composer/autoload_classmap.php
Original file line number Diff line number Diff line change
Expand Up @@ -1213,6 +1213,7 @@
'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',
Expand Down
1 change: 1 addition & 0 deletions lib/composer/composer/autoload_static.php
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
176 changes: 176 additions & 0 deletions tests/Core/Command/Preview/CleanupTest.php
Original file line number Diff line number Diff line change
@@ -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));
}
}

0 comments on commit 10d1c3c

Please sign in to comment.