Merge pull request #54876 from nextcloud/carl/cleanup-commands-trash

refactor: Commands and background jobs for the trashbin
This commit is contained in:
Andy Scherzinger 2026-01-29 13:44:17 +01:00 committed by GitHub
commit b1a114ded5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 233 additions and 229 deletions

View file

@ -7,18 +7,20 @@
*/
namespace OCA\Files_Trashbin\BackgroundJob;
use OC\Files\View;
use OCA\Files_Trashbin\AppInfo\Application;
use OCA\Files_Trashbin\Expiration;
use OCA\Files_Trashbin\Trashbin;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\BackgroundJob\TimedJob;
use OCP\Files\Folder;
use OCP\Files\IRootFolder;
use OCP\Files\ISetupManager;
use OCP\IAppConfig;
use OCP\IUser;
use OCP\IUserManager;
use OCP\Lock\ILockingProvider;
use OCP\Lock\LockedException;
use Override;
use Psr\Log\LoggerInterface;
class ExpireTrash extends TimedJob {
@ -28,19 +30,21 @@ class ExpireTrash extends TimedJob {
private const USER_BATCH_SIZE = 10;
public function __construct(
private IAppConfig $appConfig,
private IUserManager $userManager,
private Expiration $expiration,
private LoggerInterface $logger,
private ISetupManager $setupManager,
private ILockingProvider $lockingProvider,
private readonly IAppConfig $appConfig,
private readonly IUserManager $userManager,
private readonly Expiration $expiration,
private readonly LoggerInterface $logger,
private readonly ISetupManager $setupManager,
private readonly ILockingProvider $lockingProvider,
private readonly IRootFolder $rootFolder,
ITimeFactory $time,
) {
parent::__construct($time);
$this->setInterval(self::THIRTY_MINUTES);
}
protected function run($argument) {
#[Override]
protected function run($argument): void {
$backgroundJob = $this->appConfig->getValueBool(Application::APP_ID, self::TOGGLE_CONFIG_KEY_NAME, true);
if (!$backgroundJob) {
return;
@ -64,9 +68,8 @@ class ExpireTrash extends TimedJob {
$count++;
try {
if ($this->setupFS($user)) {
Trashbin::expire($uid);
}
$folder = $this->getTrashRoot($user);
Trashbin::expire($folder, $user);
} catch (\Throwable $e) {
$this->logger->error('Error while expiring trashbin for user ' . $uid, ['exception' => $e]);
} finally {
@ -82,23 +85,19 @@ class ExpireTrash extends TimedJob {
}
}
/**
* Act on behalf on trash item owner
*/
protected function setupFS(IUser $user): bool {
private function getTrashRoot(IUser $user): Folder {
$this->setupManager->tearDown();
$this->setupManager->setupForUser($user);
// Check if this user has a trashbin directory
$view = new View('/' . $user->getUID());
if (!$view->is_dir('/files_trashbin/files')) {
return false;
$folder = $this->rootFolder->getUserFolder($user->getUID())->getParent()->get('files_trashbin');
if (!$folder instanceof Folder) {
throw new \LogicException("Didn't expect files_trashbin to be a file instead of a folder");
}
return true;
return $folder;
}
private function getNextOffset(): int {
return $this->runMutexOperation(function () {
return $this->runMutexOperation(function (): int {
$this->appConfig->clearCache();
$offset = $this->appConfig->getValueInt(Application::APP_ID, self::OFFSET_CONFIG_KEY_NAME, 0);
@ -109,13 +108,18 @@ class ExpireTrash extends TimedJob {
}
private function resetOffset() {
private function resetOffset(): void {
$this->runMutexOperation(function (): void {
$this->appConfig->setValueInt(Application::APP_ID, self::OFFSET_CONFIG_KEY_NAME, 0);
});
}
private function runMutexOperation($operation): mixed {
/**
* @template T
* @param callable(): T $operation
* @return T
*/
private function runMutexOperation(callable $operation): mixed {
$acquired = false;
while ($acquired === false) {

View file

@ -7,29 +7,36 @@
*/
namespace OCA\Files_Trashbin\Command;
use OC\Core\Command\Base;
use OC\Files\SetupManager;
use OC\User\LazyUser;
use OCP\Files\IRootFolder;
use OCP\Files\NotFoundException;
use OCP\Files\NotPermittedException;
use OCP\IDBConnection;
use OCP\IUser;
use OCP\IUserBackend;
use OCP\IUserManager;
use OCP\Util;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Exception\InvalidOptionException;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
class CleanUp extends Command {
class CleanUp extends Base {
public function __construct(
protected IRootFolder $rootFolder,
protected IUserManager $userManager,
protected IDBConnection $dbConnection,
protected SetupManager $setupManager,
) {
parent::__construct();
}
protected function configure() {
protected function configure(): void {
parent::configure();
$this
->setName('trashbin:cleanup')
->setDescription('Remove deleted files')
@ -47,17 +54,18 @@ class CleanUp extends Command {
}
protected function execute(InputInterface $input, OutputInterface $output): int {
$users = $input->getArgument('user_id');
$userIds = $input->getArgument('user_id');
$verbose = $input->getOption('verbose');
if (!empty($users) && $input->getOption('all-users')) {
if (!empty($userIds) && $input->getOption('all-users')) {
throw new InvalidOptionException('Either specify a user_id or --all-users');
} elseif (!empty($users)) {
foreach ($users as $user) {
if ($this->userManager->userExists($user)) {
$output->writeln("Remove deleted files of <info>$user</info>");
} elseif (!empty($userIds)) {
foreach ($userIds as $userId) {
$user = $this->userManager->get($userId);
if ($user) {
$output->writeln("Remove deleted files of <info>$userId</info>");
$this->removeDeletedFiles($user, $output, $verbose);
} else {
$output->writeln("<error>Unknown user $user</error>");
$output->writeln("<error>Unknown user $userId</error>");
return 1;
}
}
@ -72,13 +80,14 @@ class CleanUp extends Command {
$limit = 500;
$offset = 0;
do {
$users = $backend->getUsers('', $limit, $offset);
foreach ($users as $user) {
$output->writeln(" <info>$user</info>");
$userIds = $backend->getUsers('', $limit, $offset);
foreach ($userIds as $userId) {
$output->writeln(" <info>$userId</info>");
$user = new LazyUser($userId, $this->userManager, null, $backend);
$this->removeDeletedFiles($user, $output, $verbose);
}
$offset += $limit;
} while (count($users) >= $limit);
} while (count($userIds) >= $limit);
}
} else {
throw new InvalidOptionException('Either specify a user_id or --all-users');
@ -87,32 +96,33 @@ class CleanUp extends Command {
}
/**
* remove deleted files for the given user
* Remove deleted files for the given user.
*/
protected function removeDeletedFiles(string $uid, OutputInterface $output, bool $verbose): void {
\OC_Util::tearDownFS();
\OC_Util::setupFS($uid);
$path = '/' . $uid . '/files_trashbin';
if ($this->rootFolder->nodeExists($path)) {
protected function removeDeletedFiles(IUser $user, OutputInterface $output, bool $verbose): void {
$this->setupManager->tearDown();
$this->setupManager->setupForUser($user);
$path = '/' . $user->getUID() . '/files_trashbin';
try {
$node = $this->rootFolder->get($path);
} catch (NotFoundException|NotPermittedException) {
if ($verbose) {
$output->writeln('Deleting <info>' . Util::humanFileSize($node->getSize()) . "</info> in trash for <info>$uid</info>.");
}
$node->delete();
if ($this->rootFolder->nodeExists($path)) {
$output->writeln('<error>Trash folder sill exists after attempting to delete it</error>');
return;
}
$query = $this->dbConnection->getQueryBuilder();
$query->delete('files_trash')
->where($query->expr()->eq('user', $query->createParameter('uid')))
->setParameter('uid', $uid);
$query->executeStatement();
} else {
if ($verbose) {
$output->writeln("No trash found for <info>$uid</info>");
$output->writeln("No trash found for <info>{$user->getUID()}</info>");
}
return;
}
if ($verbose) {
$output->writeln('Deleting <info>' . Util::humanFileSize($node->getSize()) . "</info> in trash for <info>{$user->getUID()}</info>.");
}
$node->delete();
if ($this->rootFolder->nodeExists($path)) {
$output->writeln('<error>Trash folder sill exists after attempting to delete it</error>');
return;
}
$query = $this->dbConnection->getQueryBuilder();
$query->delete('files_trash')
->where($query->expr()->eq('user', $query->createParameter('uid')))
->setParameter('uid', $user->getUID());
$query->executeStatement();
}
}

View file

@ -8,32 +8,48 @@
namespace OCA\Files_Trashbin\Command;
use OC\Command\FileAccess;
use OC\Files\SetupManager;
use OCA\Files_Trashbin\Trashbin;
use OCP\Command\ICommand;
use OCP\Files\Folder;
use OCP\Files\IRootFolder;
use OCP\IUserManager;
use OCP\Server;
use Override;
use Psr\Log\LoggerInterface;
class Expire implements ICommand {
use FileAccess;
/**
* @param string $user
*/
public function __construct(
private $user,
private readonly string $userId,
) {
}
public function handle() {
#[Override]
public function handle(): void {
// can't use DI because Expire needs to be serializable
$userManager = Server::get(IUserManager::class);
if (!$userManager->userExists($this->user)) {
$user = $userManager->get($this->userId);
if (!$user) {
// User has been deleted already
return;
}
\OC_Util::tearDownFS();
\OC_Util::setupFS($this->user);
Trashbin::expire($this->user);
\OC_Util::tearDownFS();
try {
$setupManager = Server::get(SetupManager::class);
$setupManager->tearDown();
$setupManager->setupForUser($user);
$trashRoot = Server::get(IRootFolder::class)->getUserFolder($user->getUID())->getParent()->get('files_trashbin');
if (!$trashRoot instanceof Folder) {
throw new \LogicException("Didn't expect files_trashbin to be a file instead of a folder");
}
Trashbin::expire($trashRoot, $user);
} catch (\Exception $e) {
Server::get(LoggerInterface::class)->error('Error while expiring trashbin for user ' . $user->getUID(), ['exception' => $e]);
} finally {
$setupManager->tearDown();
}
}
}

View file

@ -7,33 +7,32 @@
*/
namespace OCA\Files_Trashbin\Command;
use OC\Files\View;
use OC\Core\Command\Base;
use OC\Files\SetupManager;
use OCA\Files_Trashbin\Expiration;
use OCA\Files_Trashbin\Trashbin;
use OCP\Files\Folder;
use OCP\Files\IRootFolder;
use OCP\IUser;
use OCP\IUserManager;
use Psr\Log\LoggerInterface;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Helper\ProgressBar;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class ExpireTrash extends Command {
class ExpireTrash extends Base {
/**
* @param IUserManager|null $userManager
* @param Expiration|null $expiration
*/
public function __construct(
private LoggerInterface $logger,
private ?IUserManager $userManager = null,
private ?Expiration $expiration = null,
private readonly ?IUserManager $userManager,
private readonly ?Expiration $expiration,
private readonly SetupManager $setupManager,
private readonly IRootFolder $rootFolder,
) {
parent::__construct();
}
protected function configure() {
protected function configure(): void {
parent::configure();
$this
->setName('trashbin:expire')
->setDescription('Expires the users trashbin')
@ -52,15 +51,17 @@ class ExpireTrash extends Command {
return 1;
}
$users = $input->getArgument('user_id');
if (!empty($users)) {
foreach ($users as $user) {
if ($this->userManager->userExists($user)) {
$output->writeln("Remove deleted files of <info>$user</info>");
$userObject = $this->userManager->get($user);
$this->expireTrashForUser($userObject);
$userIds = $input->getArgument('user_id');
if (!empty($userIds)) {
foreach ($userIds as $userId) {
$user = $this->userManager->get($userId);
if ($user) {
$output->writeln("Remove deleted files of <info>$userId</info>");
$this->expireTrashForUser($user, $output);
$output->writeln("<error>Unknown user $userId</error>");
return 1;
} else {
$output->writeln("<error>Unknown user $user</error>");
$output->writeln("<error>Unknown user $userId</error>");
return 1;
}
}
@ -71,7 +72,7 @@ class ExpireTrash extends Command {
$users = $this->userManager->getSeenUsers();
foreach ($users as $user) {
$p->advance();
$this->expireTrashForUser($user);
$this->expireTrashForUser($user, $output);
}
$p->finish();
$output->writeln('');
@ -79,33 +80,25 @@ class ExpireTrash extends Command {
return 0;
}
public function expireTrashForUser(IUser $user) {
private function expireTrashForUser(IUser $user, OutputInterface $output): void {
try {
$uid = $user->getUID();
if (!$this->setupFS($uid)) {
return;
}
Trashbin::expire($uid);
$trashRoot = $this->getTrashRoot($user);
Trashbin::expire($trashRoot, $user);
} catch (\Throwable $e) {
$this->logger->error('Error while expiring trashbin for user ' . $user->getUID(), ['exception' => $e]);
$output->writeln('<error>Error while expiring trashbin for user ' . $user->getUID() . '</error>');
throw $e;
} finally {
$this->setupManager->tearDown();
}
}
/**
* Act on behalf on trash item owner
* @param string $user
* @return boolean
*/
protected function setupFS($user) {
\OC_Util::tearDownFS();
\OC_Util::setupFS($user);
private function getTrashRoot(IUser $user): Folder {
$this->setupManager->setupForUser($user);
// Check if this user has a trashbin directory
$view = new View('/' . $user);
if (!$view->is_dir('/files_trashbin/files')) {
return false;
$folder = $this->rootFolder->getUserFolder($user->getUID())->getParent()->get('files_trashbin');
if (!$folder instanceof Folder) {
throw new \LogicException("Didn't expect files_trashbin to be a file instead of a folder");
}
return true;
return $folder;
}
}

View file

@ -7,6 +7,7 @@
namespace OCA\Files_Trashbin\Command;
use OC\Core\Command\Base;
use OC\Files\SetupManager;
use OCA\Files_Trashbin\Trash\ITrashManager;
use OCA\Files_Trashbin\Trash\TrashItem;
use OCP\Files\IRootFolder;
@ -14,6 +15,7 @@ use OCP\IDBConnection;
use OCP\IL10N;
use OCP\IUserBackend;
use OCP\IUserManager;
use OCP\IUserSession;
use OCP\L10N\IFactory;
use Symfony\Component\Console\Exception\InvalidOptionException;
use Symfony\Component\Console\Input\InputArgument;
@ -48,6 +50,8 @@ class RestoreAllFiles extends Base {
protected IUserManager $userManager,
protected IDBConnection $dbConnection,
protected ITrashManager $trashManager,
protected SetupManager $setupManager,
protected IUserSession $userSession,
IFactory $l10nFactory,
) {
parent::__construct();
@ -140,17 +144,16 @@ class RestoreAllFiles extends Base {
* Restore deleted files for the given user according to the given filters
*/
protected function restoreDeletedFiles(string $uid, int $scope, ?int $since, ?int $until, bool $dryRun, OutputInterface $output): void {
\OC_Util::tearDownFS();
\OC_Util::setupFS($uid);
\OC_User::setUserId($uid);
$user = $this->userManager->get($uid);
if ($user === null) {
if (!$user) {
$output->writeln("<error>Unknown user $uid</error>");
return;
}
$this->setupManager->tearDown();
$this->setupManager->setupForUser($user);
$this->userSession->setUser($user);
$userTrashItems = $this->filterTrashItems(
$this->trashManager->listTrashRoot($user),
$scope,

View file

@ -10,10 +10,12 @@ namespace OCA\Files_Trashbin\Command;
use OC\Core\Command\Base;
use OCP\Command\IBus;
use OCP\IAppConfig;
use OCP\IConfig;
use OCP\IUser;
use OCP\IUserManager;
use OCP\Util;
use Override;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
@ -21,14 +23,16 @@ use Symfony\Component\Console\Output\OutputInterface;
class Size extends Base {
public function __construct(
private IConfig $config,
private IUserManager $userManager,
private IBus $commandBus,
private readonly IAppConfig $appConfig,
private readonly IConfig $config,
private readonly IUserManager $userManager,
private readonly IBus $commandBus,
) {
parent::__construct();
}
protected function configure() {
#[Override]
protected function configure(): void {
parent::configure();
$this
->setName('trashbin:size')
@ -41,6 +45,7 @@ class Size extends Base {
);
}
#[Override]
protected function execute(InputInterface $input, OutputInterface $output): int {
$user = $input->getOption('user');
$size = $input->getArgument('size');
@ -55,7 +60,7 @@ class Size extends Base {
$this->config->setUserValue($user, 'files_trashbin', 'trashbin_size', (string)$parsedSize);
$this->commandBus->push(new Expire($user));
} else {
$this->config->setAppValue('files_trashbin', 'trashbin_size', (string)$parsedSize);
$this->appConfig->setValueInt('files_trashbin', 'trashbin_size', $parsedSize);
$output->writeln('<info>Warning: changing the default trashbin size will automatically trigger cleanup of existing trashbins,</info>');
$output->writeln('<info>a users trashbin can exceed the configured size until they move a new file to the trashbin.</info>');
}
@ -66,8 +71,8 @@ class Size extends Base {
return 0;
}
private function printTrashbinSize(InputInterface $input, OutputInterface $output, ?string $user) {
$globalSize = (int)$this->config->getAppValue('files_trashbin', 'trashbin_size', '-1');
private function printTrashbinSize(InputInterface $input, OutputInterface $output, ?string $user): void {
$globalSize = $this->appConfig->getValueInt('files_trashbin', 'trashbin_size', -1);
if ($globalSize < 0) {
$globalHumanSize = 'default (50% of available space)';
} else {

View file

@ -15,11 +15,18 @@ use OCP\EventDispatcher\Event;
use OCP\EventDispatcher\IEventListener;
use OCP\Files\Events\BeforeFileSystemSetupEvent;
use OCP\Files\Events\Node\NodeWrittenEvent;
use OCP\Files\Folder;
use OCP\Files\IRootFolder;
use OCP\Files\NotFoundException;
use OCP\Files\NotPermittedException;
use OCP\IUserManager;
use OCP\User\Events\BeforeUserDeletedEvent;
/** @template-implements IEventListener<NodeWrittenEvent|BeforeUserDeletedEvent|BeforeFileSystemSetupEvent> */
class EventListener implements IEventListener {
public function __construct(
private IUserManager $userManager,
private IRootFolder $rootFolder,
private ?string $userId = null,
) {
}
@ -27,8 +34,19 @@ class EventListener implements IEventListener {
public function handle(Event $event): void {
if ($event instanceof NodeWrittenEvent) {
// Resize trash
if (!empty($this->userId)) {
Trashbin::resizeTrash($this->userId);
if (empty($this->userId)) {
return;
}
try {
/** @var Folder $trashRoot */
$trashRoot = $this->rootFolder->get('/' . $this->userId . '/files_trashbin');
} catch (NotFoundException|NotPermittedException) {
return;
}
$user = $this->userManager->get($this->userId);
if ($user) {
Trashbin::resizeTrash($trashRoot, $user);
}
}

View file

@ -44,6 +44,7 @@ use OCP\IConfig;
use OCP\IDBConnection;
use OCP\IRequest;
use OCP\IURLGenerator;
use OCP\IUser;
use OCP\IUserManager;
use OCP\Lock\ILockingProvider;
use OCP\Lock\LockedException;
@ -774,24 +775,19 @@ class Trashbin implements IEventListener {
}
/**
* calculate remaining free space for trash bin
* Calculate remaining free space for trash bin
*
* @param int|float $trashbinSize current size of the trash bin
* @param string $user
* @return int|float available free space for trash bin
*/
private static function calculateFreeSpace(int|float $trashbinSize, string $user): int|float {
$configuredTrashbinSize = static::getConfiguredTrashbinSize($user);
private static function calculateFreeSpace(Folder $userFolder, int|float $trashbinSize, IUser $user): int|float {
$configuredTrashbinSize = static::getConfiguredTrashbinSize($user->getUID());
if ($configuredTrashbinSize > -1) {
return $configuredTrashbinSize - $trashbinSize;
}
$userObject = Server::get(IUserManager::class)->get($user);
if (is_null($userObject)) {
return 0;
}
$softQuota = true;
$quota = $userObject->getQuota();
$quota = $user->getQuota();
if ($quota === null || $quota === 'none') {
$quota = Filesystem::free_space('/');
$softQuota = false;
@ -810,10 +806,6 @@ class Trashbin implements IEventListener {
// calculate available space for trash bin
// subtract size of files and current trash bin size from quota
if ($softQuota) {
$userFolder = \OC::$server->getUserFolder($user);
if (is_null($userFolder)) {
return 0;
}
$free = $quota - $userFolder->getSize(false); // remaining free space for user
if ($free > 0) {
$availableSpace = ($free * self::DEFAULTMAXSIZE / 100) - $trashbinSize; // how much space can be used for versions
@ -828,38 +820,34 @@ class Trashbin implements IEventListener {
}
/**
* resize trash bin if necessary after a new file was added to Nextcloud
*
* @param string $user user id
* Resize trash bin if necessary after a new file was added to Nextcloud
*/
public static function resizeTrash($user) {
$size = self::getTrashbinSize($user);
public static function resizeTrash(Folder $trashRoot, IUser $user): void {
$trashBinSize = $trashRoot->getSize();
$freeSpace = self::calculateFreeSpace($size, $user);
$freeSpace = self::calculateFreeSpace($trashRoot->getParent(), $trashBinSize, $user);
if ($freeSpace < 0) {
self::scheduleExpire($user);
self::scheduleExpire($user->getUID());
}
}
/**
* clean up the trash bin
*
* @param string $user
* Clean up the trash bin
*/
public static function expire($user) {
$trashBinSize = self::getTrashbinSize($user);
$availableSpace = self::calculateFreeSpace($trashBinSize, $user);
public static function expire(Folder $trashRoot, IUser $user): void {
$trashBinSize = $trashRoot->getSize();
$availableSpace = self::calculateFreeSpace($trashRoot->getParent(), $trashBinSize, $user);
$dirContent = Helper::getTrashFiles('/', $user, 'mtime');
$dirContent = Helper::getTrashFiles('/', $user->getUID(), 'mtime');
// delete all files older then $retention_obligation
[$delSize, $count] = self::deleteExpiredFiles($dirContent, $user);
[$delSize, $count] = self::deleteExpiredFiles($dirContent, $user->getUID());
$availableSpace += $delSize;
// delete files from trash until we meet the trash bin size limit again
self::deleteFiles(array_slice($dirContent, $count), $user, $availableSpace);
self::deleteFiles(array_slice($dirContent, $count), $user->getUID(), $availableSpace);
}
/**

View file

@ -14,6 +14,7 @@ use OCA\Files_Trashbin\BackgroundJob\ExpireTrash;
use OCA\Files_Trashbin\Expiration;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\BackgroundJob\IJobList;
use OCP\Files\IRootFolder;
use OCP\Files\ISetupManager;
use OCP\IAppConfig;
use OCP\IUserManager;
@ -31,6 +32,7 @@ class ExpireTrashTest extends TestCase {
private ITimeFactory&MockObject $time;
private ISetupManager&MockObject $setupManager;
private ILockingProvider&MockObject $lockingProvider;
private IRootFolder&MockObject $rootFolder;
protected function setUp(): void {
parent::setUp();
@ -42,6 +44,7 @@ class ExpireTrashTest extends TestCase {
$this->logger = $this->createMock(LoggerInterface::class);
$this->setupManager = $this->createMock(ISetupManager::class);
$this->lockingProvider = $this->createMock(ILockingProvider::class);
$this->rootFolder = $this->createMock(IRootFolder::class);
$this->time = $this->createMock(ITimeFactory::class);
$this->time->method('getTime')
@ -68,6 +71,7 @@ class ExpireTrashTest extends TestCase {
$this->logger,
$this->setupManager,
$this->lockingProvider,
$this->rootFolder,
$this->time,
);
$job->start($this->jobList);
@ -87,6 +91,7 @@ class ExpireTrashTest extends TestCase {
$this->logger,
$this->setupManager,
$this->lockingProvider,
$this->rootFolder,
$this->time,
);
$job->start($this->jobList);

View file

@ -8,9 +8,12 @@ declare(strict_types=1);
*/
namespace OCA\Files_Trashbin\Tests\Command;
use OC\Files\SetupManager;
use OCA\Files_Trashbin\Command\CleanUp;
use OCP\Files\IRootFolder;
use OCP\Files\NotFoundException;
use OCP\IDBConnection;
use OCP\IUser;
use OCP\IUserManager;
use OCP\Server;
use OCP\UserInterface;
@ -34,16 +37,22 @@ class CleanUpTest extends TestCase {
protected IDBConnection $dbConnection;
protected CleanUp $cleanup;
protected string $trashTable = 'files_trash';
protected string $user0 = 'user0';
protected IUser&MockObject $user0;
protected SetupManager&MockObject $setupManager;
protected function setUp(): void {
parent::setUp();
$this->user0 = $this->createMock(IUser::class);
$this->user0->method('getUID')->willReturn('user0');
$this->rootFolder = $this->createMock(IRootFolder::class);
$this->userManager = $this->createMock(IUserManager::class);
$this->dbConnection = Server::get(IDBConnection::class);
$this->setupManager = $this->createMock(SetupManager::class);
$this->cleanup = new CleanUp($this->rootFolder, $this->userManager, $this->dbConnection);
$this->cleanup = new CleanUp($this->rootFolder, $this->userManager, $this->dbConnection, $this->setupManager);
}
/**
@ -74,17 +83,20 @@ class CleanUpTest extends TestCase {
$this->initTable();
$this->rootFolder
->method('nodeExists')
->with('/' . $this->user0 . '/files_trashbin')
->willReturnOnConsecutiveCalls($nodeExists, false);
->with('/' . $this->user0->getUID() . '/files_trashbin')
->willReturn(false);
if ($nodeExists) {
$this->rootFolder
->method('get')
->with('/' . $this->user0 . '/files_trashbin')
->with('/' . $this->user0->getUID() . '/files_trashbin')
->willReturn($this->rootFolder);
$this->rootFolder
->method('delete');
} else {
$this->rootFolder->expects($this->never())->method('get');
$this->rootFolder
->method('get')
->with('/' . $this->user0->getUID() . '/files_trashbin')
->willThrowException(new NotFoundException());
$this->rootFolder->expects($this->never())->method('delete');
}
self::invokePrivate($this->cleanup, 'removeDeletedFiles', [$this->user0, new NullOutput(), false]);
@ -129,15 +141,19 @@ class CleanUpTest extends TestCase {
$userIds = ['user1', 'user2', 'user3'];
$instance = $this->getMockBuilder(CleanUp::class)
->onlyMethods(['removeDeletedFiles'])
->setConstructorArgs([$this->rootFolder, $this->userManager, $this->dbConnection])
->setConstructorArgs([$this->rootFolder, $this->userManager, $this->dbConnection, $this->setupManager])
->getMock();
$instance->expects($this->exactly(count($userIds)))
->method('removeDeletedFiles')
->willReturnCallback(function ($user) use ($userIds): void {
$this->assertTrue(in_array($user, $userIds));
->willReturnCallback(function (IUser $user) use ($userIds): void {
$this->assertTrue(in_array($user->getUID(), $userIds));
});
$this->userManager->expects($this->exactly(count($userIds)))
->method('userExists')->willReturn(true);
->method('get')->willReturnCallback(function (string $userId): IUser {
$user = $this->createMock(IUser::class);
$user->method('getUID')->willReturn($userId);
return $user;
});
$inputInterface = $this->createMock(\Symfony\Component\Console\Input\InputInterface::class);
$inputInterface->method('getArgument')
->with('user_id')
@ -159,7 +175,7 @@ class CleanUpTest extends TestCase {
$backendUsers = ['user1', 'user2'];
$instance = $this->getMockBuilder(CleanUp::class)
->onlyMethods(['removeDeletedFiles'])
->setConstructorArgs([$this->rootFolder, $this->userManager, $this->dbConnection])
->setConstructorArgs([$this->rootFolder, $this->userManager, $this->dbConnection, $this->setupManager])
->getMock();
$backend = $this->createMock(UserInterface::class);
$backend->method('getUsers')
@ -167,8 +183,8 @@ class CleanUpTest extends TestCase {
->willReturn($backendUsers);
$instance->expects($this->exactly(count($backendUsers)))
->method('removeDeletedFiles')
->willReturnCallback(function ($user) use ($backendUsers): void {
$this->assertTrue(in_array($user, $backendUsers));
->willReturnCallback(function (IUser $user) use ($backendUsers): void {
$this->assertTrue(in_array($user->getUID(), $backendUsers));
});
$inputInterface = $this->createMock(InputInterface::class);
$inputInterface->method('getArgument')

View file

@ -6,6 +6,7 @@
*/
namespace OCA\Files_Trashbin\Tests\Command;
use OC\Files\SetupManager;
use OCA\Files_Trashbin\Command\ExpireTrash;
use OCA\Files_Trashbin\Expiration;
use OCA\Files_Trashbin\Helper;
@ -16,9 +17,9 @@ use OCP\IConfig;
use OCP\IUser;
use OCP\IUserManager;
use OCP\Server;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\Attributes\Group;
use PHPUnit\Framework\MockObject\MockObject;
use Psr\Log\LoggerInterface;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Test\TestCase;
@ -38,7 +39,6 @@ class ExpireTrashTest extends TestCase {
private IUser $user;
private ITimeFactory&MockObject $timeFactory;
protected function setUp(): void {
parent::setUp();
@ -66,7 +66,7 @@ class ExpireTrashTest extends TestCase {
parent::tearDown();
}
#[\PHPUnit\Framework\Attributes\DataProvider(methodName: 'retentionObligationProvider')]
#[DataProvider(methodName: '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);
@ -99,9 +99,10 @@ class ExpireTrashTest extends TestCase {
->willReturn([$userId]);
$command = new ExpireTrash(
Server::get(LoggerInterface::class),
Server::get(IUserManager::class),
$this->expiration
$this->expiration,
Server::get(SetupManager::class),
Server::get(IRootFolder::class),
);
$this->invokePrivate($command, 'execute', [$inputInterface, $outputInterface]);

View file

@ -1707,72 +1707,18 @@
<code><![CDATA[moveMount]]></code>
</UndefinedMethod>
</file>
<file src="apps/files_trashbin/lib/BackgroundJob/ExpireTrash.php">
<InternalClass>
<code><![CDATA[new View('/' . $user->getUID())]]></code>
</InternalClass>
<InternalMethod>
<code><![CDATA[is_dir]]></code>
<code><![CDATA[new View('/' . $user->getUID())]]></code>
</InternalMethod>
</file>
<file src="apps/files_trashbin/lib/Command/CleanUp.php">
<DeprecatedClass>
<code><![CDATA[\OC_Util::setupFS($uid)]]></code>
<code><![CDATA[\OC_Util::tearDownFS()]]></code>
</DeprecatedClass>
<DeprecatedMethod>
<code><![CDATA[\OC_Util::tearDownFS()]]></code>
</DeprecatedMethod>
</file>
<file src="apps/files_trashbin/lib/Command/Expire.php">
<DeprecatedClass>
<code><![CDATA[\OC_Util::setupFS($this->user)]]></code>
<code><![CDATA[\OC_Util::tearDownFS()]]></code>
<code><![CDATA[\OC_Util::tearDownFS()]]></code>
</DeprecatedClass>
<DeprecatedInterface>
<code><![CDATA[Expire]]></code>
</DeprecatedInterface>
<DeprecatedMethod>
<code><![CDATA[\OC_Util::tearDownFS()]]></code>
<code><![CDATA[\OC_Util::tearDownFS()]]></code>
</DeprecatedMethod>
</file>
<file src="apps/files_trashbin/lib/Command/ExpireTrash.php">
<DeprecatedClass>
<code><![CDATA[\OC_Util::setupFS($user)]]></code>
<code><![CDATA[\OC_Util::tearDownFS()]]></code>
</DeprecatedClass>
<DeprecatedMethod>
<code><![CDATA[\OC_Util::tearDownFS()]]></code>
</DeprecatedMethod>
<InternalClass>
<code><![CDATA[new View('/' . $user)]]></code>
</InternalClass>
<InternalMethod>
<code><![CDATA[is_dir]]></code>
<code><![CDATA[new View('/' . $user)]]></code>
</InternalMethod>
</file>
<file src="apps/files_trashbin/lib/Command/RestoreAllFiles.php">
<DeprecatedClass>
<code><![CDATA[\OC_Util::setupFS($uid)]]></code>
<code><![CDATA[\OC_Util::tearDownFS()]]></code>
</DeprecatedClass>
<DeprecatedMethod>
<code><![CDATA[\OC_Util::tearDownFS()]]></code>
</DeprecatedMethod>
</file>
<file src="apps/files_trashbin/lib/Command/Size.php">
<DeprecatedInterface>
<code><![CDATA[private]]></code>
</DeprecatedInterface>
<DeprecatedMethod>
<code><![CDATA[getAppValue]]></code>
<code><![CDATA[getUserValue]]></code>
<code><![CDATA[getUserValueForUsers]]></code>
<code><![CDATA[setAppValue]]></code>
<code><![CDATA[setUserValue]]></code>
</DeprecatedMethod>
</file>
@ -1850,7 +1796,6 @@
<code><![CDATA[Util::emitHook('\OCA\Files_Trashbin\Trashbin', 'post_restore', ['filePath' => $targetPath, 'trashPath' => $sourcePath])]]></code>
<code><![CDATA[getUserFolder]]></code>
<code><![CDATA[getUserFolder]]></code>
<code><![CDATA[getUserFolder]]></code>
</DeprecatedMethod>
<InternalClass>
<code><![CDATA[new View('/' . $owner)]]></code>