mirror of
https://github.com/nextcloud/server.git
synced 2026-05-28 04:32:30 -04:00
fix(files_trashbin): Expire trashbin items when space is needed
Signed-off-by: Kent Delante <kent.delante@proton.me>
This commit is contained in:
parent
26210e205d
commit
1ccf491a9e
3 changed files with 173 additions and 4 deletions
|
|
@ -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
|
||||
*/
|
||||
|
|
|
|||
156
apps/files_trashbin/tests/Command/ExpireTrashTest.php
Normal file
156
apps/files_trashbin/tests/Command/ExpireTrashTest.php
Normal file
|
|
@ -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…
Reference in a new issue