fix(files_trashbin): Expire trashbin items when space is needed

Signed-off-by: Kent Delante <kent.delante@proton.me>
pull/53112/head
Kent Delante 5 months ago
parent 26210e205d
commit 1ccf491a9e
  1. 7
      apps/files_trashbin/lib/Command/ExpireTrash.php
  2. 14
      apps/files_trashbin/lib/Expiration.php
  3. 156
      apps/files_trashbin/tests/Command/ExpireTrashTest.php

@ -9,7 +9,6 @@ namespace OCA\Files_Trashbin\Command;
use OC\Files\View;
use OCA\Files_Trashbin\Expiration;
use OCA\Files_Trashbin\Helper;
use OCA\Files_Trashbin\Trashbin;
use OCP\IUser;
use OCP\IUserManager;
@ -46,8 +45,9 @@ class ExpireTrash extends Command {
}
protected function execute(InputInterface $input, OutputInterface $output): int {
$minAge = $this->expiration->getMinAgeAsTimestamp();
$maxAge = $this->expiration->getMaxAgeAsTimestamp();
if (!$maxAge) {
if ($minAge === false && $maxAge === false) {
$output->writeln('Auto expiration is configured - keeps files and folders in the trash bin for 30 days and automatically deletes anytime after that if space is needed (note: files may not be deleted if space is not needed)');
return 1;
}
@ -85,8 +85,7 @@ class ExpireTrash extends Command {
if (!$this->setupFS($uid)) {
return;
}
$dirContent = Helper::getTrashFiles('/', $uid, 'mtime');
Trashbin::deleteExpiredFiles($dirContent, $uid);
Trashbin::expire($uid);
} catch (\Throwable $e) {
$this->logger->error('Error while expiring trashbin for user ' . $user->getUID(), ['exception' => $e]);
}

@ -94,6 +94,20 @@ class Expiration {
return $isOlderThanMax || $isMinReached;
}
/**
* Get minimal retention obligation as a timestamp
*
* @return int|false
*/
public function getMinAgeAsTimestamp() {
$minAge = false;
if ($this->isEnabled() && $this->minAge !== self::NO_OBLIGATION) {
$time = $this->timeFactory->getTime();
$minAge = $time - ($this->minAge * 86400);
}
return $minAge;
}
/**
* @return bool|int
*/

@ -0,0 +1,156 @@
<?php
/**
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
namespace OCA\Files_Trashbin\Tests\Command;
use OCA\Files_Trashbin\Command\ExpireTrash;
use OCA\Files_Trashbin\Expiration;
use OCA\Files_Trashbin\Helper;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\Files\IRootFolder;
use OCP\Files\Node;
use OCP\IConfig;
use OCP\IUser;
use OCP\IUserManager;
use OCP\Server;
use Psr\Log\LoggerInterface;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Test\TestCase;
/**
* Class ExpireTrashTest
*
* @group DB
*
* @package OCA\Files_Trashbin\Tests\Command
*/
class ExpireTrashTest extends TestCase {
private Expiration $expiration;
private Node $userFolder;
private IConfig $config;
private IUserManager $userManager;
private IUser $user;
private ITimeFactory $timeFactory;
protected function setUp(): void {
parent::setUp();
$this->config = Server::get(IConfig::class);
$this->timeFactory = $this->createMock(ITimeFactory::class);
$this->expiration = Server::get(Expiration::class);
$this->invokePrivate($this->expiration, 'timeFactory', [$this->timeFactory]);
$userId = self::getUniqueID('user');
$this->userManager = Server::get(IUserManager::class);
$this->user = $this->userManager->createUser($userId, $userId);
$this->loginAsUser($userId);
$this->userFolder = Server::get(IRootFolder::class)->getUserFolder($userId);
}
protected function tearDown(): void {
$this->logout();
if (isset($this->user)) {
$this->user->delete();
}
$this->invokePrivate($this->expiration, 'timeFactory', [Server::get(ITimeFactory::class)]);
parent::tearDown();
}
/**
* @dataProvider retentionObligationProvider
*/
public function testRetentionObligation(string $obligation, string $quota, int $elapsed, int $fileSize, bool $shouldExpire): void {
$this->config->setSystemValues(['trashbin_retention_obligation' => $obligation]);
$this->expiration->setRetentionObligation($obligation);
$this->user->setQuota($quota);
$bytes = 'ABCDEFGHIKLMNOPQRSTUVWXYZ';
$file = 'foo.txt';
$this->userFolder->newFile($file, substr($bytes, 0, $fileSize));
$filemtime = $this->userFolder->get($file)->getMTime();
$this->timeFactory->expects($this->any())
->method('getTime')
->willReturn($filemtime + $elapsed);
$this->userFolder->get($file)->delete();
$this->userFolder->getStorage()
->getCache()
->put('files_trashbin', ['size' => $fileSize, 'unencrypted_size' => $fileSize]);
$userId = $this->user->getUID();
$trashFiles = Helper::getTrashFiles('/', $userId);
$this->assertEquals(1, count($trashFiles));
$outputInterface = $this->createMock(OutputInterface::class);
$inputInterface = $this->createMock(InputInterface::class);
$inputInterface->expects($this->any())
->method('getArgument')
->with('user_id')
->willReturn([$userId]);
$command = new ExpireTrash(
Server::get(LoggerInterface::class),
Server::get(IUserManager::class),
$this->expiration
);
$this->invokePrivate($command, 'execute', [$inputInterface, $outputInterface]);
$trashFiles = Helper::getTrashFiles('/', $userId);
$this->assertEquals($shouldExpire ? 0 : 1, count($trashFiles));
}
public function retentionObligationProvider(): array {
$hour = 3600; // 60 * 60
$oneDay = 24 * $hour;
$fiveDays = 24 * 5 * $hour;
$tenDays = 24 * 10 * $hour;
$elevenDays = 24 * 11 * $hour;
return [
['disabled', '20 B', 0, 1, false],
['auto', '20 B', 0, 5, false],
['auto', '20 B', 0, 21, true],
['0, auto', '20 B', 0, 21, true],
['0, auto', '20 B', $oneDay, 5, false],
['0, auto', '20 B', $oneDay, 19, true],
['0, auto', '20 B', 0, 19, true],
['auto, 0', '20 B', $oneDay, 19, true],
['auto, 0', '20 B', $oneDay, 21, true],
['auto, 0', '20 B', 0, 5, false],
['auto, 0', '20 B', 0, 19, true],
['1, auto', '20 B', 0, 5, false],
['1, auto', '20 B', $fiveDays, 5, false],
['1, auto', '20 B', $fiveDays, 21, true],
['auto, 1', '20 B', 0, 21, true],
['auto, 1', '20 B', 0, 5, false],
['auto, 1', '20 B', $fiveDays, 5, true],
['auto, 1', '20 B', $oneDay, 5, false],
['2, 10', '20 B', $fiveDays, 5, false],
['2, 10', '20 B', $fiveDays, 20, true],
['2, 10', '20 B', $elevenDays, 5, true],
['10, 2', '20 B', $fiveDays, 5, false],
['10, 2', '20 B', $fiveDays, 21, false],
['10, 2', '20 B', $tenDays, 5, false],
['10, 2', '20 B', $elevenDays, 5, true]
];
}
}
Loading…
Cancel
Save