diff --git a/apps/files_trashbin/composer/composer/autoload_classmap.php b/apps/files_trashbin/composer/composer/autoload_classmap.php
index 23e2e7baff6..22c89f7cc2b 100644
--- a/apps/files_trashbin/composer/composer/autoload_classmap.php
+++ b/apps/files_trashbin/composer/composer/autoload_classmap.php
@@ -42,6 +42,8 @@ return array(
'OCA\\Files_Trashbin\\Sabre\\TrashRoot' => $baseDir . '/../lib/Sabre/TrashRoot.php',
'OCA\\Files_Trashbin\\Sabre\\TrashbinPlugin' => $baseDir . '/../lib/Sabre/TrashbinPlugin.php',
'OCA\\Files_Trashbin\\Service\\ConfigService' => $baseDir . '/../lib/Service/ConfigService.php',
+ 'OCA\\Files_Trashbin\\Service\\ExpireService' => $baseDir . '/../lib/Service/ExpireService.php',
+ 'OCA\\Files_Trashbin\\Service\\TrashFolderService' => $baseDir . '/../lib/Service/TrashFolderService.php',
'OCA\\Files_Trashbin\\Storage' => $baseDir . '/../lib/Storage.php',
'OCA\\Files_Trashbin\\Trash\\BackendNotFoundException' => $baseDir . '/../lib/Trash/BackendNotFoundException.php',
'OCA\\Files_Trashbin\\Trash\\ITrashBackend' => $baseDir . '/../lib/Trash/ITrashBackend.php',
diff --git a/apps/files_trashbin/composer/composer/autoload_static.php b/apps/files_trashbin/composer/composer/autoload_static.php
index fc604299261..67e279bdb84 100644
--- a/apps/files_trashbin/composer/composer/autoload_static.php
+++ b/apps/files_trashbin/composer/composer/autoload_static.php
@@ -7,14 +7,14 @@ namespace Composer\Autoload;
class ComposerStaticInitFiles_Trashbin
{
public static $prefixLengthsPsr4 = array (
- 'O' =>
+ 'O' =>
array (
'OCA\\Files_Trashbin\\' => 19,
),
);
public static $prefixDirsPsr4 = array (
- 'OCA\\Files_Trashbin\\' =>
+ 'OCA\\Files_Trashbin\\' =>
array (
0 => __DIR__ . '/..' . '/../lib',
),
@@ -57,6 +57,8 @@ class ComposerStaticInitFiles_Trashbin
'OCA\\Files_Trashbin\\Sabre\\TrashRoot' => __DIR__ . '/..' . '/../lib/Sabre/TrashRoot.php',
'OCA\\Files_Trashbin\\Sabre\\TrashbinPlugin' => __DIR__ . '/..' . '/../lib/Sabre/TrashbinPlugin.php',
'OCA\\Files_Trashbin\\Service\\ConfigService' => __DIR__ . '/..' . '/../lib/Service/ConfigService.php',
+ 'OCA\\Files_Trashbin\\Service\\ExpireService' => __DIR__ . '/..' . '/../lib/Service/ExpireService.php',
+ 'OCA\\Files_Trashbin\\Service\\TrashFolderService' => __DIR__ . '/..' . '/../lib/Service/TrashFolderService.php',
'OCA\\Files_Trashbin\\Storage' => __DIR__ . '/..' . '/../lib/Storage.php',
'OCA\\Files_Trashbin\\Trash\\BackendNotFoundException' => __DIR__ . '/..' . '/../lib/Trash/BackendNotFoundException.php',
'OCA\\Files_Trashbin\\Trash\\ITrashBackend' => __DIR__ . '/..' . '/../lib/Trash/ITrashBackend.php',
diff --git a/apps/files_trashbin/lib/BackgroundJob/ExpireTrash.php b/apps/files_trashbin/lib/BackgroundJob/ExpireTrash.php
index bb383dab78d..73b770dc22c 100644
--- a/apps/files_trashbin/lib/BackgroundJob/ExpireTrash.php
+++ b/apps/files_trashbin/lib/BackgroundJob/ExpireTrash.php
@@ -7,10 +7,9 @@
*/
namespace OCA\Files_Trashbin\BackgroundJob;
-use OC\Files\View;
+use OC\Files\SetupManager;
use OCA\Files_Trashbin\Expiration;
-use OCA\Files_Trashbin\Helper;
-use OCA\Files_Trashbin\Trashbin;
+use OCA\Files_Trashbin\Service\ExpireService;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\BackgroundJob\TimedJob;
use OCP\IAppConfig;
@@ -19,10 +18,12 @@ use Psr\Log\LoggerInterface;
class ExpireTrash extends TimedJob {
public function __construct(
- private IAppConfig $appConfig,
- private IUserManager $userManager,
- private Expiration $expiration,
- private LoggerInterface $logger,
+ readonly private IAppConfig $appConfig,
+ readonly private IUserManager $userManager,
+ readonly private Expiration $expiration,
+ readonly private ExpireService $expireService,
+ readonly private SetupManager $setupManager,
+ readonly private LoggerInterface $logger,
ITimeFactory $time,
) {
parent::__construct($time);
@@ -47,12 +48,7 @@ class ExpireTrash extends TimedJob {
foreach ($users as $user) {
try {
- $uid = $user->getUID();
- if (!$this->setupFS($uid)) {
- continue;
- }
- $dirContent = Helper::getTrashFiles('/', $uid, 'mtime');
- Trashbin::deleteExpiredFiles($dirContent, $uid);
+ $this->expireService->expireTrashForUser($user);
} catch (\Throwable $e) {
$this->logger->error('Error while expiring trashbin for user ' . $user->getUID(), ['exception' => $e]);
}
@@ -61,28 +57,12 @@ class ExpireTrash extends TimedJob {
if ($stopTime < time()) {
$this->appConfig->setValueInt('files_trashbin', 'background_job_expire_trash_offset', $offset);
- \OC_Util::tearDownFS();
+ $this->setupManager->tearDown();
return;
}
}
$this->appConfig->setValueInt('files_trashbin', 'background_job_expire_trash_offset', 0);
- \OC_Util::tearDownFS();
- }
-
- /**
- * Act on behalf on trash item owner
- */
- protected function setupFS(string $user): bool {
- \OC_Util::tearDownFS();
- \OC_Util::setupFS($user);
-
- // Check if this user has a trashbin directory
- $view = new View('/' . $user);
- if (!$view->is_dir('/files_trashbin/files')) {
- return false;
- }
-
- return true;
+ $this->setupManager->tearDown();
}
}
diff --git a/apps/files_trashbin/lib/Command/Expire.php b/apps/files_trashbin/lib/Command/Expire.php
index 73a42cd4749..72e2d3fedda 100644
--- a/apps/files_trashbin/lib/Command/Expire.php
+++ b/apps/files_trashbin/lib/Command/Expire.php
@@ -8,32 +8,33 @@
namespace OCA\Files_Trashbin\Command;
use OC\Command\FileAccess;
-use OCA\Files_Trashbin\Trashbin;
+use OC\Files\SetupManager;
+use OCA\Encryption\Users\Setup;
+use OCA\Files_Trashbin\Service\ExpireService;
use OCP\Command\ICommand;
+use OCP\IUser;
use OCP\IUserManager;
use OCP\Server;
+use Psr\Log\LoggerInterface;
class Expire implements ICommand {
use FileAccess;
- /**
- * @param string $user
- */
public function __construct(
- private $user,
+ readonly private string $user,
) {
}
- public function handle() {
- $userManager = Server::get(IUserManager::class);
- if (!$userManager->userExists($this->user)) {
- // User has been deleted already
- return;
+ public function handle(): void {
+ try {
+ $user = Server::get(IUserManager::class)->get($this->user);
+ if (!$user) {
+ return;
+ }
+ Server::get(ExpireService::class)->expireTrashForUser($user);
+ Server::get(SetupManager::class)->execute();
+ } catch (\Throwable $e) {
+ Server::get(LoggerInterface::class)->error('Error while expiring trashbin for user ' . $this->user, ['exception' => $e]);
}
-
- \OC_Util::tearDownFS();
- \OC_Util::setupFS($this->user);
- Trashbin::expire($this->user);
- \OC_Util::tearDownFS();
}
}
diff --git a/apps/files_trashbin/lib/Command/ExpireTrash.php b/apps/files_trashbin/lib/Command/ExpireTrash.php
index 422d8379984..24c5940ed6f 100644
--- a/apps/files_trashbin/lib/Command/ExpireTrash.php
+++ b/apps/files_trashbin/lib/Command/ExpireTrash.php
@@ -7,12 +7,11 @@
*/
namespace OCA\Files_Trashbin\Command;
-use OC\Files\View;
+use OC\Files\SetupManager;
use OCA\Files_Trashbin\Expiration;
-use OCA\Files_Trashbin\Trashbin;
-use OCP\IUser;
+use OCA\Files_Trashbin\Service\ExpireService;
+use OCP\Files\IRootFolder;
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;
@@ -20,15 +19,12 @@ use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class ExpireTrash extends Command {
-
- /**
- * @param IUserManager|null $userManager
- * @param Expiration|null $expiration
- */
public function __construct(
- private LoggerInterface $logger,
- private ?IUserManager $userManager = null,
- private ?Expiration $expiration = null,
+ readonly SetupManager $setupManager,
+ readonly IRootFolder $rootFolder,
+ readonly private IUserManager $userManager,
+ readonly private Expiration $expiration,
+ readonly private ExpireService $expireService,
) {
parent::__construct();
}
@@ -55,10 +51,11 @@ class ExpireTrash extends Command {
$users = $input->getArgument('user_id');
if (!empty($users)) {
foreach ($users as $user) {
- if ($this->userManager->userExists($user)) {
- $output->writeln("Remove deleted files of $user");
- $userObject = $this->userManager->get($user);
- $this->expireTrashForUser($userObject);
+ $userObject = $this->userManager->get($user);
+ if ($userObject) {
+ $output->writeln("Remove deleted files of $user");
+ $this->expireService->expireTrashForUser($userObject);
+ $this->setupManager->tearDown();
} else {
$output->writeln("Unknown user $user");
return 1;
@@ -71,41 +68,18 @@ class ExpireTrash extends Command {
$users = $this->userManager->getSeenUsers();
foreach ($users as $user) {
$p->advance();
- $this->expireTrashForUser($user);
+ try {
+ $this->expireService->expireTrashForUser($user);
+ $this->setupManager->tearDown();
+ } catch (\Throwable $e) {
+ $displayName = $user->getDisplayName();
+ $output->writeln("Error while expiring trashbin for user $displayName");
+ throw $e;
+ }
}
$p->finish();
$output->writeln('');
}
return 0;
}
-
- public function expireTrashForUser(IUser $user) {
- try {
- $uid = $user->getUID();
- if (!$this->setupFS($uid)) {
- return;
- }
- Trashbin::expire($uid);
- } catch (\Throwable $e) {
- $this->logger->error('Error while expiring trashbin for user ' . $user->getUID(), ['exception' => $e]);
- }
- }
-
- /**
- * Act on behalf on trash item owner
- * @param string $user
- * @return boolean
- */
- protected function setupFS($user) {
- \OC_Util::tearDownFS();
- \OC_Util::setupFS($user);
-
- // Check if this user has a trashbin directory
- $view = new View('/' . $user);
- if (!$view->is_dir('/files_trashbin/files')) {
- return false;
- }
-
- return true;
- }
}
diff --git a/apps/files_trashbin/lib/Command/Size.php b/apps/files_trashbin/lib/Command/Size.php
index 9c19d4d92b3..dd4da791f57 100644
--- a/apps/files_trashbin/lib/Command/Size.php
+++ b/apps/files_trashbin/lib/Command/Size.php
@@ -9,11 +9,14 @@ declare(strict_types=1);
namespace OCA\Files_Trashbin\Command;
use OC\Core\Command\Base;
+use OC\Files\SetupManager;
+use OCA\Files_Trashbin\Service\ExpireService;
use OCP\Command\IBus;
use OCP\IConfig;
use OCP\IUser;
use OCP\IUserManager;
use OCP\Util;
+use Psr\Log\LoggerInterface;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
@@ -21,9 +24,9 @@ use Symfony\Component\Console\Output\OutputInterface;
class Size extends Base {
public function __construct(
- private IConfig $config,
- private IUserManager $userManager,
- private IBus $commandBus,
+ readonly private IConfig $config,
+ readonly private IUserManager $userManager,
+ readonly private ExpireService $expireService,
) {
parent::__construct();
}
@@ -53,7 +56,10 @@ class Size extends Base {
}
if ($user) {
$this->config->setUserValue($user, 'files_trashbin', 'trashbin_size', (string)$parsedSize);
- $this->commandBus->push(new Expire($user));
+ $userObject = $this->userManager->get($user);
+ if ($userObject) {
+ $this->expireService->scheduleExpirationJob($userObject);
+ }
} else {
$this->config->setAppValue('files_trashbin', 'trashbin_size', (string)$parsedSize);
$output->writeln('Warning: changing the default trashbin size will automatically trigger cleanup of existing trashbins,');
diff --git a/apps/files_trashbin/lib/Listener/EventListener.php b/apps/files_trashbin/lib/Listener/EventListener.php
index 63ecc9c81f7..0e952c27aa0 100644
--- a/apps/files_trashbin/lib/Listener/EventListener.php
+++ b/apps/files_trashbin/lib/Listener/EventListener.php
@@ -9,18 +9,23 @@ declare(strict_types=1);
namespace OCA\Files_Trashbin\Listener;
+use OCA\Files_Trashbin\Service\ExpireService;
use OCA\Files_Trashbin\Storage;
use OCA\Files_Trashbin\Trashbin;
use OCP\EventDispatcher\Event;
use OCP\EventDispatcher\IEventListener;
use OCP\Files\Events\BeforeFileSystemSetupEvent;
use OCP\Files\Events\Node\NodeWrittenEvent;
+use OCP\IUser;
+use OCP\IUserManager;
use OCP\User\Events\BeforeUserDeletedEvent;
/** @template-implements IEventListener */
class EventListener implements IEventListener {
public function __construct(
- private ?string $userId = null,
+ readonly private ExpireService $expireService,
+ readonly private IUserManager $userManager,
+ readonly private ?string $userId = null,
) {
}
@@ -28,7 +33,10 @@ class EventListener implements IEventListener {
if ($event instanceof NodeWrittenEvent) {
// Resize trash
if (!empty($this->userId)) {
- Trashbin::resizeTrash($this->userId);
+ $user = $this->userManager->get($this->userId);
+ if ($user) {
+ $this->expireService->scheduleExpirationJobIfNeeded($user);
+ }
}
}
diff --git a/apps/files_trashbin/lib/Service/ExpireService.php b/apps/files_trashbin/lib/Service/ExpireService.php
new file mode 100644
index 00000000000..3830980faf0
--- /dev/null
+++ b/apps/files_trashbin/lib/Service/ExpireService.php
@@ -0,0 +1,122 @@
+trashFolderService->getTrashFolderRoot($user);
+ if (!$trashFolderRoot) {
+ return;
+ }
+
+ $availableSpace = $this->trashFolderService->getAvailableSpace($trashFolderRoot, $user);
+
+ try {
+ /** @var Folder $trashFolder */
+ $trashFolder = $trashFolderRoot->get('files');
+ } catch (NotFoundException) {
+ echo "bug";
+ return; // Nothing to expire
+ }
+
+ $nodes = $trashFolder->getDirectoryListing();
+
+ usort($nodes, fn (Node $a, Node $b): int => $a->getMTime() <=> $b->getMTime());
+
+ // delete all files older then $retention_obligation
+ [$delSize, $count] = $this->deleteExpiredNodes($trashFolder, $nodes, $user);
+
+ $availableSpace += $delSize;
+
+ // delete files from trash until we meet the trash bin size limit again
+ Trashbin::deleteNodes(array_slice($nodes, $count), $user, $availableSpace);
+ }
+
+ public function scheduleExpirationJobIfNeeded(IUser $user): void {
+ $trashFolderRoot = $this->trashFolderService->getTrashFolderRoot($user);
+ if (!$trashFolderRoot) {
+ return;
+ }
+
+ $freeSpace = $this->trashFolderService->getAvailableSpace($trashFolderRoot, $user);
+
+ if ($freeSpace < 0) {
+ $this->scheduleExpirationJob($user);
+ }
+ }
+
+ public function scheduleExpirationJob(IUser $user): void {
+ // let the admin disable auto expire
+ if ($this->expiration->isEnabled()) {
+ $this->ibus->push(new Expire($user->getUID()));
+ }
+ }
+
+ /**
+ * @param Node[] $nodes
+ */
+ private function deleteExpiredNodes(Folder $trashFolder, array $nodes, IUser $user) {
+ /** @var Expiration $expiration */
+ $expiration = Server::get(Expiration::class);
+ $size = 0;
+ $count = 0;
+ foreach ($nodes as $node) {
+ $timestamp = $node->getMTime();
+ if (!$expiration->isExpired($timestamp)) {
+ break; // Since the nodes are sorted by mtime, we can already abord
+ }
+
+ try {
+ $size += $this->trashFolderService->delete($trashFolder, $node, $user, $timestamp);
+ $count++;
+ } catch (NotPermittedException $e) {
+ $this->logger->warning('Removing "' . $node->getName() . '" from trashbin failed for user "{user}"',
+ [
+ 'exception' => $e,
+ 'app' => 'files_trashbin',
+ 'user' => $user,
+ ]
+ );
+ continue;
+ }
+ $this->logger->info(
+ 'Remove "' . $node->getName() . '" from trashbin for user "{user}" because it exceeds max retention obligation term.',
+ [
+ 'app' => 'files_trashbin',
+ 'user' => $user,
+ ],
+ );
+ }
+
+ return [$size, $count];
+ }
+}
diff --git a/apps/files_trashbin/lib/Service/TrashFolderService.php b/apps/files_trashbin/lib/Service/TrashFolderService.php
new file mode 100644
index 00000000000..4a8732efbfa
--- /dev/null
+++ b/apps/files_trashbin/lib/Service/TrashFolderService.php
@@ -0,0 +1,174 @@
+rootFolder->getUserFolder($user->getUID())->getParent();
+
+ try {
+ /** @var Folder $folder */
+ $folder = $userRoot->get('files_trashbin');
+ return $folder;
+ } catch (NotFoundException) {
+ return false;
+ }
+ }
+
+ public function getTrashFolder(IUser $user): false|Folder {
+ $rootTrashFolder = $this->getTrashFolderRoot($user);
+ if (!$rootTrashFolder) {
+ return false;
+ }
+
+ /** @var Folder $folder */
+ try {
+ /** @var Folder $folder */
+ $folder = $rootTrashFolder->get('files');
+ return $folder;
+ } catch (NotFoundException) {
+ return $rootTrashFolder->newFolder('files');
+ }
+ }
+
+ /**
+ * Calculate remaining free space for trash bin
+ *
+ * @return int|float The available space
+ */
+ public function getAvailableSpace(Folder $trashFolderRoot, IUser $user): int|float {
+ $configuredTrashBinSize = TrashBin::getConfiguredTrashbinSize($user->getUID());
+ $trashBinSize = $trashFolderRoot->getSize(false);
+ if ($configuredTrashBinSize > -1) {
+ return $configuredTrashBinSize - $trashBinSize;
+ }
+
+ $softQuota = true;
+ $quota = $user->getQuota();
+ if ($quota === null || $quota === 'none') {
+ $quota = $this->rootFolder->getFreeSpace();
+ $softQuota = false;
+ // inf or unknown free space
+ if ($quota < 0) {
+ $quota = PHP_INT_MAX;
+ }
+ } else {
+ $quota = Util::computerFileSize($quota);
+ // invalid quota
+ if ($quota === false) {
+ $quota = PHP_INT_MAX;
+ }
+ }
+
+ // calculate available space for trash bin
+ // subtract size of files and current trash bin size from quota
+ if ($softQuota) {
+ $userFolder = $trashFolderRoot->getParent();
+ if (is_null($userFolder)) {
+ return 0;
+ }
+ $free = $quota - $userFolder->getSize(false); // remaining free space for user
+ if ($free > 0) {
+ $availableSpace = ($free * Trashbin::DEFAULTMAXSIZE / 100) - $trashBinSize; // how much space can be used for versions
+ } else {
+ $availableSpace = $free - $trashBinSize;
+ }
+ } else {
+ $availableSpace = $quota;
+ }
+
+ return Util::numericToNumber($availableSpace);
+ }
+
+ public static function delete(Folder $trashFolder, Node $node, IUser $user, $timestamp = null) {
+ $size = 0;
+
+ if ($timestamp) {
+ $query = Server::get(IDBConnection::class)->getQueryBuilder();
+ $query->delete('files_trash')
+ ->where($query->expr()->eq('user', $query->createNamedParameter($user)))
+ ->andWhere($query->expr()->eq('id', $query->createNamedParameter($node->getName())))
+ ->andWhere($query->expr()->eq('timestamp', $query->createNamedParameter($timestamp)));
+ $query->executeStatement();
+
+ $file = Trashbin::getTrashFilename($node->getName(), $timestamp);
+ } else {
+ $file = $node->getName();
+ }
+
+ //$size += Trashbin::deleteVersions($view, $file, $node, $timestamp, $user);
+
+ try {
+ $node = $trashFolder->get($file);
+ } catch (NotFoundException) {
+ return $size;
+ }
+
+ if ($node instanceof Folder) {
+ $size += Trashbin::calculateSize(new View('/' . $user . '/files_trashbin/files/' . $file));
+ } elseif ($node instanceof File) {
+ $size += $view->filesize('/files_trashbin/files/' . $file);
+ }
+
+ Trashbin::emitTrashbinPreDelete('/files_trashbin/files/' . $file);
+ $node->delete();
+ Trashbin::emitTrashbinPostDelete('/files_trashbin/files/' . $file);
+
+ return $size;
+ }
+
+ /**
+ * @param string $file
+ * @param string $filename
+ * @param ?int $timestamp
+ */
+ private function deleteVersions(Folder $trashFolderRoot, $fileName, Node $node, ?int $timestamp, IUser $user): int|float {
+ $size = 0;
+ if ($this->appManager->isEnabledForUser('files_versions')) {
+ $trashFolderRoot->get('versions/' . $fileName);
+ if ($view->is_dir('files_trashbin/versions/' . $file)) {
+ $size += Trashbin::calculateSize(new View('/' . $user . '/files_trashbin/versions/' . $file));
+ $view->unlink('files_trashbin/versions/' . $file);
+ } elseif ($versions = self::getVersionsFromTrash($filename, $timestamp, $user)) {
+ foreach ($versions as $v) {
+ if ($timestamp) {
+ $size += $view->filesize('/files_trashbin/versions/' . static::getTrashFilename($filename . '.v' . $v, $timestamp));
+ $view->unlink('/files_trashbin/versions/' . static::getTrashFilename($filename . '.v' . $v, $timestamp));
+ } else {
+ $size += $view->filesize('/files_trashbin/versions/' . $filename . '.v' . $v);
+ $view->unlink('/files_trashbin/versions/' . $filename . '.v' . $v);
+ }
+ }
+ }
+ }
+ return $size;
+ }
+}
diff --git a/apps/files_trashbin/lib/Trashbin.php b/apps/files_trashbin/lib/Trashbin.php
index 63b3f28d33c..59cdbf4cd73 100644
--- a/apps/files_trashbin/lib/Trashbin.php
+++ b/apps/files_trashbin/lib/Trashbin.php
@@ -10,6 +10,7 @@ namespace OCA\Files_Trashbin;
use OC\Files\Cache\Cache;
use OC\Files\Cache\CacheEntry;
use OC\Files\Cache\CacheQueryBuilder;
+use OC\Files\FileInfo;
use OC\Files\Filesystem;
use OC\Files\Node\NonExistingFile;
use OC\Files\Node\NonExistingFolder;
@@ -17,14 +18,13 @@ use OC\Files\View;
use OC\User\NoUserException;
use OC_User;
use OCA\Files_Trashbin\AppInfo\Application;
-use OCA\Files_Trashbin\Command\Expire;
use OCA\Files_Trashbin\Events\BeforeNodeRestoredEvent;
use OCA\Files_Trashbin\Events\NodeRestoredEvent;
use OCA\Files_Trashbin\Exceptions\CopyRecursiveException;
+use OCA\Files_Trashbin\Service\ExpireService;
use OCA\Files_Versions\Storage;
use OCP\App\IAppManager;
use OCP\AppFramework\Utility\ITimeFactory;
-use OCP\Command\IBus;
use OCP\EventDispatcher\Event;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\EventDispatcher\IEventListener;
@@ -42,6 +42,7 @@ use OCP\FilesMetadata\IFilesMetadataManager;
use OCP\IConfig;
use OCP\IDBConnection;
use OCP\IURLGenerator;
+use OCP\IUser;
use OCP\IUserManager;
use OCP\Lock\ILockingProvider;
use OCP\Lock\LockedException;
@@ -350,17 +351,23 @@ class Trashbin implements IEventListener {
$trashStorage->releaseLock($trashInternalPath, ILockingProvider::LOCK_EXCLUSIVE, $lockingProvider);
- self::scheduleExpire($user);
+ $userManager = Server::get(IUserManager::class);
+ $expireServie = Server::get(ExpireService::class);
+ if ($userObject = $userManager->get($user)) {
+ $expireServie->scheduleExpirationJob($userObject);
+ }
// if owner !== user we also need to update the owners trash size
if ($owner !== $user) {
- self::scheduleExpire($owner);
+ if ($ownerObject = $userManager->get($owner)) {
+ $expireServie->scheduleExpirationJob($ownerObject);
+ }
}
return $moveSuccessful;
}
- private static function getConfiguredTrashbinSize(string $user): int|float {
+ public static function getConfiguredTrashbinSize(string $user): int|float {
$config = Server::get(IConfig::class);
$userTrashbinSize = $config->getUserValue($user, 'files_trashbin', 'trashbin_size', '-1');
if (is_numeric($userTrashbinSize) && ($userTrashbinSize > -1)) {
@@ -643,7 +650,7 @@ class Trashbin implements IEventListener {
*
* @param string $path
*/
- protected static function emitTrashbinPreDelete($path) {
+ public static function emitTrashbinPreDelete($path) {
\OC_Hook::emit('\OCP\Trashbin', 'preDelete', ['path' => $path]);
}
@@ -652,7 +659,7 @@ class Trashbin implements IEventListener {
*
* @param string $path
*/
- protected static function emitTrashbinPostDelete($path) {
+ public static function emitTrashbinPostDelete($path) {
\OC_Hook::emit('\OCP\Trashbin', 'delete', ['path' => $path]);
}
@@ -664,6 +671,7 @@ class Trashbin implements IEventListener {
* @param int $timestamp of deletion time
*
* @return int|float size of deleted files
+ * @throws NotPermittedException
*/
public static function delete($filename, $user, $timestamp = null) {
$userRoot = \OC::$server->getUserFolder($user)->getParent();
@@ -704,32 +712,6 @@ class Trashbin implements IEventListener {
return $size;
}
- /**
- * @param string $file
- * @param string $filename
- * @param ?int $timestamp
- */
- private static function deleteVersions(View $view, $file, $filename, $timestamp, string $user): int|float {
- $size = 0;
- if (Server::get(IAppManager::class)->isEnabledForUser('files_versions')) {
- if ($view->is_dir('files_trashbin/versions/' . $file)) {
- $size += self::calculateSize(new View('/' . $user . '/files_trashbin/versions/' . $file));
- $view->unlink('files_trashbin/versions/' . $file);
- } elseif ($versions = self::getVersionsFromTrash($filename, $timestamp, $user)) {
- foreach ($versions as $v) {
- if ($timestamp) {
- $size += $view->filesize('/files_trashbin/versions/' . static::getTrashFilename($filename . '.v' . $v, $timestamp));
- $view->unlink('/files_trashbin/versions/' . static::getTrashFilename($filename . '.v' . $v, $timestamp));
- } else {
- $size += $view->filesize('/files_trashbin/versions/' . $filename . '.v' . $v);
- $view->unlink('/files_trashbin/versions/' . $filename . '.v' . $v);
- }
- }
- }
- }
- return $size;
- }
-
/**
* check to see whether a file exists in trashbin
*
@@ -762,113 +744,11 @@ class Trashbin implements IEventListener {
return (bool)$query->executeStatement();
}
- /**
- * 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);
- if ($configuredTrashbinSize > -1) {
- return $configuredTrashbinSize - $trashbinSize;
- }
-
- $userObject = Server::get(IUserManager::class)->get($user);
- if (is_null($userObject)) {
- return 0;
- }
- $softQuota = true;
- $quota = $userObject->getQuota();
- if ($quota === null || $quota === 'none') {
- $quota = Filesystem::free_space('/');
- $softQuota = false;
- // inf or unknown free space
- if ($quota < 0) {
- $quota = PHP_INT_MAX;
- }
- } else {
- $quota = Util::computerFileSize($quota);
- // invalid quota
- if ($quota === false) {
- $quota = PHP_INT_MAX;
- }
- }
-
- // 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
- } else {
- $availableSpace = $free - $trashbinSize;
- }
- } else {
- $availableSpace = $quota;
- }
-
- return Util::numericToNumber($availableSpace);
- }
-
- /**
- * resize trash bin if necessary after a new file was added to Nextcloud
- *
- * @param string $user user id
- */
- public static function resizeTrash($user) {
- $size = self::getTrashbinSize($user);
-
- $freeSpace = self::calculateFreeSpace($size, $user);
-
- if ($freeSpace < 0) {
- self::scheduleExpire($user);
- }
- }
-
- /**
- * clean up the trash bin
- *
- * @param string $user
- */
- public static function expire($user) {
- $trashBinSize = self::getTrashbinSize($user);
- $availableSpace = self::calculateFreeSpace($trashBinSize, $user);
-
- $dirContent = Helper::getTrashFiles('/', $user, 'mtime');
-
- // delete all files older then $retention_obligation
- [$delSize, $count] = self::deleteExpiredFiles($dirContent, $user);
-
- $availableSpace += $delSize;
-
- // delete files from trash until we meet the trash bin size limit again
- self::deleteFiles(array_slice($dirContent, $count), $user, $availableSpace);
- }
-
- /**
- * @param string $user
- */
- private static function scheduleExpire($user) {
- // let the admin disable auto expire
- /** @var Application $application */
- $application = Server::get(Application::class);
- $expiration = $application->getContainer()->query('Expiration');
- if ($expiration->isEnabled()) {
- Server::get(IBus::class)->push(new Expire($user));
- }
- }
-
/**
* if the size limit for the trash bin is reached, we delete the oldest
* files in the trash bin until we meet the limit again
*
- * @param array $files
+ * @param FileInfo[] $files
* @param string $user
* @param int|float $availableSpace available disc space
* @return int|float size of deleted files
@@ -900,6 +780,40 @@ class Trashbin implements IEventListener {
return $size;
}
+ /**
+ * if the size limit for the trash bin is reached, we delete the oldest
+ * files in the trash bin until we meet the limit again
+ *
+ * @param Node[] $nodes
+ * @return int|float size of deleted files
+ */
+ public static function deleteNodes(array $nodes, IUser $user, int|float $availableSpace): int|float {
+ /** @var Application $application */
+ $application = Server::get(Application::class);
+ $expiration = $application->getContainer()->get('Expiration');
+ $size = 0;
+
+ if ($availableSpace < 0) {
+ foreach ($nodes as $node) {
+ if ($availableSpace < 0 && $expiration->isExpired($node->getMTime(), true)) {
+ $tmp = self::delete($node->getName(), $user->getUID(), $node->getMTime());
+ Server::get(LoggerInterface::class)->info(
+ 'remove "' . $node->getName() . '" (' . $tmp . 'B) to meet the limit of trash bin size (50% of available quota) for user "{user}"',
+ [
+ 'app' => 'files_trashbin',
+ 'user' => $user,
+ ]
+ );
+ $availableSpace += $tmp;
+ $size += $tmp;
+ } else {
+ break;
+ }
+ }
+ }
+ return $size;
+ }
+
/**
* delete files older then max storage time
*
@@ -1108,18 +1022,6 @@ class Trashbin implements IEventListener {
return $size;
}
- /**
- * get current size of trash bin from a given user
- *
- * @param string $user user who owns the trash bin
- * @return int|float trash bin size
- */
- private static function getTrashbinSize(string $user): int|float {
- $view = new View('/' . $user);
- $fileInfo = $view->getFileInfo('/files_trashbin');
- return isset($fileInfo['size']) ? $fileInfo['size'] : 0;
- }
-
/**
* check if trash bin is empty for a given user
*
diff --git a/apps/files_trashbin/tests/BackgroundJob/ExpireTrashTest.php b/apps/files_trashbin/tests/BackgroundJob/ExpireTrashTest.php
index 9468fb7add0..da87377aa09 100644
--- a/apps/files_trashbin/tests/BackgroundJob/ExpireTrashTest.php
+++ b/apps/files_trashbin/tests/BackgroundJob/ExpireTrashTest.php
@@ -9,8 +9,10 @@ declare(strict_types=1);
namespace OCA\Files_Trashbin\Tests\BackgroundJob;
+use OC\Files\SetupManager;
use OCA\Files_Trashbin\BackgroundJob\ExpireTrash;
use OCA\Files_Trashbin\Expiration;
+use OCA\Files_Trashbin\Service\ExpireService;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\BackgroundJob\IJobList;
use OCP\IAppConfig;
@@ -23,6 +25,8 @@ class ExpireTrashTest extends TestCase {
private IAppConfig&MockObject $appConfig;
private IUserManager&MockObject $userManager;
private Expiration&MockObject $expiration;
+ private ExpireService&MockObject $expireService;
+ private SetupManager&MockObject $setupManager;
private IJobList&MockObject $jobList;
private LoggerInterface&MockObject $logger;
private ITimeFactory&MockObject $time;
@@ -35,6 +39,8 @@ class ExpireTrashTest extends TestCase {
$this->expiration = $this->createMock(Expiration::class);
$this->jobList = $this->createMock(IJobList::class);
$this->logger = $this->createMock(LoggerInterface::class);
+ $this->expireService = $this->createMock(ExpireService::class);
+ $this->setupManager = $this->createMock(SetupManager::class);
$this->time = $this->createMock(ITimeFactory::class);
$this->time->method('getTime')
@@ -54,7 +60,7 @@ class ExpireTrashTest extends TestCase {
->with('files_trashbin', 'background_job_expire_trash_offset', 0)
->willReturn(0);
- $job = new ExpireTrash($this->appConfig, $this->userManager, $this->expiration, $this->logger, $this->time);
+ $job = new ExpireTrash($this->appConfig, $this->userManager, $this->expiration, $this->expireService, $this->setupManager, $this->logger, $this->time);
$job->start($this->jobList);
}
@@ -65,7 +71,7 @@ class ExpireTrashTest extends TestCase {
$this->expiration->expects($this->never())
->method('getMaxAgeAsTimestamp');
- $job = new ExpireTrash($this->appConfig, $this->userManager, $this->expiration, $this->logger, $this->time);
+ $job = new ExpireTrash($this->appConfig, $this->userManager, $this->expiration, $this->expireService, $this->setupManager, $this->logger, $this->time);
$job->start($this->jobList);
}
}
diff --git a/apps/files_trashbin/tests/Command/ExpireTrashTest.php b/apps/files_trashbin/tests/Command/ExpireTrashTest.php
index 0d0ee98ca7a..511da1dd4ef 100644
--- a/apps/files_trashbin/tests/Command/ExpireTrashTest.php
+++ b/apps/files_trashbin/tests/Command/ExpireTrashTest.php
@@ -6,10 +6,13 @@
*/
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;
+use OCA\Files_Trashbin\Service\ExpireService;
use OCP\AppFramework\Utility\ITimeFactory;
+use OCP\Files\Folder;
use OCP\Files\IRootFolder;
use OCP\Files\Node;
use OCP\IConfig;
@@ -30,11 +33,14 @@ use Test\TestCase;
*/
class ExpireTrashTest extends TestCase {
private Expiration $expiration;
- private Node $userFolder;
+ private Folder $userFolder;
+ private IRootFolder $rootFolder;
private IConfig $config;
private IUserManager $userManager;
private IUser $user;
private ITimeFactory $timeFactory;
+ private ExpireService $expireService;
+ private SetupManager $setupManager;
protected function setUp(): void {
@@ -47,10 +53,13 @@ class ExpireTrashTest extends TestCase {
$userId = self::getUniqueID('user');
$this->userManager = Server::get(IUserManager::class);
+ $this->rootFolder = Server::get(IRootFolder::class);
$this->user = $this->userManager->createUser($userId, $userId);
+ $this->expireService = Server::get(ExpireService::class);
+ $this->setupManager = Server::get(SetupManager::class);
$this->loginAsUser($userId);
- $this->userFolder = Server::get(IRootFolder::class)->getUserFolder($userId);
+ $this->userFolder = $this->rootFolder->getUserFolder($userId);
}
protected function tearDown(): void {
@@ -99,9 +108,11 @@ class ExpireTrashTest extends TestCase {
->willReturn([$userId]);
$command = new ExpireTrash(
- Server::get(LoggerInterface::class),
+ $this->setupManager,
+ $this->rootFolder,
Server::get(IUserManager::class),
- $this->expiration
+ $this->expiration,
+ $this->expireService,
);
$this->invokePrivate($command, 'execute', [$inputInterface, $outputInterface]);
diff --git a/apps/files_trashbin/tests/TrashbinTest.php b/apps/files_trashbin/tests/TrashbinTest.php
index 6104a242104..0c39ffc55d3 100644
--- a/apps/files_trashbin/tests/TrashbinTest.php
+++ b/apps/files_trashbin/tests/TrashbinTest.php
@@ -19,6 +19,7 @@ use OCA\Files_Sharing\AppInfo\Application;
use OCA\Files_Trashbin\AppInfo\Application as TrashbinApplication;
use OCA\Files_Trashbin\Expiration;
use OCA\Files_Trashbin\Helper;
+use OCA\Files_Trashbin\Service\ExpireService;
use OCA\Files_Trashbin\Trashbin;
use OCP\App\IAppManager;
use OCP\AppFramework\Utility\ITimeFactory;
@@ -177,6 +178,8 @@ class TrashbinTest extends \Test\TestCase {
// every second file will get a date in the past so that it will get expired
$manipulatedList = $this->manipulateDeleteTime($filesInTrash, $this->trashRoot1, $expiredDate);
+ $expireService = Server::get(ExpireService::class);
+ $expireService->
$testClass = new TrashbinForTesting();
[$sizeOfDeletedFiles, $count] = $testClass->dummyDeleteExpiredFiles($manipulatedList, $expireAt);
@@ -681,15 +684,6 @@ class TrashbinTest extends \Test\TestCase {
// just a dummy class to make protected methods available for testing
class TrashbinForTesting extends Trashbin {
- /**
- * @param FileInfo[] $files
- * @param integer $limit
- */
- public function dummyDeleteExpiredFiles($files) {
- // dummy value for $retention_obligation because it is not needed here
- return parent::deleteExpiredFiles($files, TrashbinTest::TEST_TRASHBIN_USER1);
- }
-
/**
* @param FileInfo[] $files
* @param integer $availableSpace
diff --git a/apps/user_ldap/lib/LDAP.php b/apps/user_ldap/lib/LDAP.php
index 1cf20c4b939..5d2bf09b2ba 100644
--- a/apps/user_ldap/lib/LDAP.php
+++ b/apps/user_ldap/lib/LDAP.php
@@ -351,12 +351,17 @@ class LDAP implements ILDAPWrapper {
* @throws \Exception
*/
private function processLDAPError($resource, string $functionName, int $errorCode, string $errorMsg): void {
- $this->logger->debug('LDAP error {message} ({code}) after calling {func}', [
+ $args = [
'app' => 'user_ldap',
'message' => $errorMsg,
'code' => $errorCode,
'func' => $functionName,
- ]);
+ ];
+ if ($errorCode === 1) {
+ $this->logger->warning('LDAP error {message} ({code}) after calling {func}', $args);
+ } else {
+ $this->logger->debug('LDAP error {message} ({code}) after calling {func}', $args);
+ }
if ($functionName === 'ldap_get_entries'
&& $errorCode === -4) {
} elseif ($errorCode === 32) {