Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions core/Command/Preview/Cleanup.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
namespace OC\Core\Command\Preview;

use OC\Core\Command\Base;
use OC\Preview\PreviewService;
use OCP\DB\Exception;
use OCP\Files\Folder;
use OCP\Files\IRootFolder;
use OCP\Files\NotFoundException;
Expand All @@ -23,6 +25,7 @@ class Cleanup extends Base {
public function __construct(
private IRootFolder $rootFolder,
private LoggerInterface $logger,
private PreviewService $previewService,
) {
parent::__construct();
}
Expand All @@ -34,6 +37,31 @@ protected function configure(): void {
}

protected function execute(InputInterface $input, OutputInterface $output): int {
if ($this->deletePreviewFromPreviewTable($output) !== 0) {
return 1;
}

return $this->deletePreviewFromFileCacheTable($output);
}

/**
* Delete from the new oc_previews table.
*/
private function deletePreviewFromPreviewTable(OutputInterface $output): int {
try {
$this->previewService->deleteAll();
return 0;
} catch (NotPermittedException|Exception $e) {
$this->logger->error("Previews can't be removed: exception occurred: " . $e->getMessage(), ['exception' => $e]);
$output->writeln("Previews can't be removed: " . $e->getMessage() . '. See the logs for more details.');
return 1;
}
}

/**
* Legacy in case there are still previews stored there.
*/
private function deletePreviewFromFileCacheTable(OutputInterface $output): int {
try {
$appDataFolder = $this->rootFolder->get($this->rootFolder->getAppDataDirectoryName());

Expand Down
14 changes: 14 additions & 0 deletions lib/private/Preview/PreviewService.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
use OC\Preview\Db\Preview;
use OC\Preview\Db\PreviewMapper;
use OC\Preview\Storage\StorageFactory;
use OCP\DB\Exception;
use OCP\Files\NotPermittedException;
use OCP\IDBConnection;

class PreviewService {
Expand All @@ -23,6 +25,10 @@ public function __construct(
) {
}

/**
* @throws NotPermittedException
* @throws Exception
*/
public function deletePreview(Preview $preview): void {
$this->storageFactory->deletePreview($preview);
$this->previewMapper->delete($preview);
Expand Down Expand Up @@ -99,11 +105,19 @@ public function getPreviewsForMimeTypes(array $mimeTypes): \Generator {
return $this->previewMapper->getPreviewsForMimeTypes($mimeTypes);
}

/**
* @throws NotPermittedException
* @throws Exception
*/
public function deleteAll(): void {
$lastId = 0;
while (true) {
$previews = $this->previewMapper->getPreviews($lastId, 1000);
$i = 0;

// FIXME: Should we use transaction here? Du to the I/O created when
// deleting the previews from the storage, which might be on a network
// This might take a non trivial amount of time where the DB is locked.
foreach ($previews as $preview) {
$this->deletePreview($preview);
$i++;
Expand Down
24 changes: 24 additions & 0 deletions tests/Core/Command/Preview/CleanupTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
namespace Core\Command\Preview;

use OC\Core\Command\Preview\Cleanup;
use OC\Preview\PreviewService;
use OCP\Files\Folder;
use OCP\Files\IRootFolder;
use OCP\Files\NotFoundException;
Expand All @@ -22,22 +23,27 @@ class CleanupTest extends TestCase {
private LoggerInterface&MockObject $logger;
private InputInterface&MockObject $input;
private OutputInterface&MockObject $output;
private PreviewService&MockObject $previewService;
private Cleanup $repair;

protected function setUp(): void {
parent::setUp();
$this->rootFolder = $this->createMock(IRootFolder::class);
$this->logger = $this->createMock(LoggerInterface::class);
$this->previewService = $this->createMock(PreviewService::class);
$this->repair = new Cleanup(
$this->rootFolder,
$this->logger,
$this->previewService,
);

$this->input = $this->createMock(InputInterface::class);
$this->output = $this->createMock(OutputInterface::class);
}

public function testCleanup(): void {
$this->previewService->expects($this->once())->method('deleteAll');

$previewFolder = $this->createMock(Folder::class);
$previewFolder->expects($this->once())
->method('isDeletable')
Expand Down Expand Up @@ -73,6 +79,8 @@ public function testCleanup(): void {
}

public function testCleanupWhenNotDeletable(): void {
$this->previewService->expects($this->once())->method('deleteAll');

$previewFolder = $this->createMock(Folder::class);
$previewFolder->expects($this->once())
->method('isDeletable')
Expand Down Expand Up @@ -102,6 +110,8 @@ public function testCleanupWhenNotDeletable(): void {

#[\PHPUnit\Framework\Attributes\DataProvider('dataForTestCleanupWithDeleteException')]
public function testCleanupWithDeleteException(string $exceptionClass, string $errorMessage): void {
$this->previewService->expects($this->once())->method('deleteAll');

$previewFolder = $this->createMock(Folder::class);
$previewFolder->expects($this->once())
->method('isDeletable')
Expand Down Expand Up @@ -138,6 +148,8 @@ public static function dataForTestCleanupWithDeleteException(): array {
}

public function testCleanupWithCreateException(): void {
$this->previewService->expects($this->once())->method('deleteAll');

$previewFolder = $this->createMock(Folder::class);
$previewFolder->expects($this->once())
->method('isDeletable')
Expand Down Expand Up @@ -172,4 +184,16 @@ public function testCleanupWithCreateException(): void {

$this->assertEquals(1, $this->repair->run($this->input, $this->output));
}

public function testCleanupWithPreviewServiceException(): void {
$this->previewService->expects($this->once())->method('deleteAll')
->willThrowException(new NotPermittedException('abc'));

$this->logger->expects($this->once())->method('error')->with("Previews can't be removed: exception occurred: abc");

$this->rootFolder->expects($this->never())
->method('get');

$this->assertEquals(1, $this->repair->run($this->input, $this->output));
}
}
Loading