From ffe88e1c3d01abb48201bd3434fb914e92b657c5 Mon Sep 17 00:00:00 2001 From: Carl Schwan Date: Wed, 8 Jul 2026 11:35:10 +0200 Subject: [PATCH] perf: Don't fetch all the trash items when deleting a single item Signed-off-by: Carl Schwan --- apps/files_trashbin/lib/Helper.php | 61 +++++++++++++++++++ .../lib/Trash/ITrashManager.php | 9 +++ .../lib/Trash/LegacyTrashBackend.php | 5 ++ .../files_trashbin/lib/Trash/TrashManager.php | 19 ++++++ 4 files changed, 94 insertions(+) diff --git a/apps/files_trashbin/lib/Helper.php b/apps/files_trashbin/lib/Helper.php index fc7e97ec8f80d..3b98ea7eb1dc2 100644 --- a/apps/files_trashbin/lib/Helper.php +++ b/apps/files_trashbin/lib/Helper.php @@ -92,6 +92,67 @@ public static function getTrashFiles($dir, $user, $sortAttribute = '', $sortDesc return $result; } + public static function getTrashFile(string $dir, string $user, string $name): ?FileInfo { + $timestamp = null; + + $view = new View('/' . $user . '/files_trashbin/files'); + + if (ltrim($dir, '/') !== '' && !$view->is_dir($dir)) { + throw new \Exception('Directory does not exists'); + } + + $mount = $view->getMount($dir); + $storage = $mount->getStorage(); + $absoluteDir = $view->getAbsolutePath($dir); + $internalPath = $mount->getInternalPath($absoluteDir); + + $extraData = Trashbin::getExtraData($user); + $entry = $storage->getCache()->get($mount->getInternalPath($view->getAbsolutePath($dir . '/' . $name))); + if ($entry === null) { + return null; + } + $entryName = $entry->getName(); + $name = $entryName; + if ($dir === '' || $dir === '/') { + $pathparts = pathinfo($entryName); + $timestamp = substr($pathparts['extension'], 1); + $name = $pathparts['filename']; + } elseif ($timestamp === null) { + // for subfolders we need to calculate the timestamp only once + $parts = explode('/', ltrim($dir, '/')); + $timestamp = substr(pathinfo($parts[0], PATHINFO_EXTENSION), 1); + } + $originalPath = ''; + $originalName = substr($entryName, 0, -strlen($timestamp) - 2); + if (isset($extraData[$originalName][$timestamp]['location'])) { + $originalPath = $extraData[$originalName][$timestamp]['location']; + if (substr($originalPath, -1) === '/') { + $originalPath = substr($originalPath, 0, -1); + } + } + $type = $entry->getMimeType() === ICacheEntry::DIRECTORY_MIMETYPE ? 'dir' : 'file'; + $i = [ + 'name' => $name, + 'mtime' => $timestamp, + 'mimetype' => $type === 'dir' ? 'httpd/unix-directory' : Server::get(IMimeTypeDetector::class)->detectPath($name), + 'type' => $type, + 'directory' => ($dir === '/') ? '' : $dir, + 'size' => $entry->getSize(), + 'etag' => '', + 'permissions' => Constants::PERMISSION_ALL - Constants::PERMISSION_SHARE, + 'fileid' => $entry->getId(), + ]; + if ($originalPath) { + if ($originalPath !== '.') { + $i['extraData'] = $originalPath . '/' . $originalName; + } else { + $i['extraData'] = $originalName; + } + } + $i['deletedBy'] = $extraData[$originalName][$timestamp]['deletedBy'] ?? null; + return new FileInfo($absoluteDir . '/' . $i['name'], $storage, $internalPath . '/' . $i['name'], $i, $mount); + } + /** * Format file infos for JSON * diff --git a/apps/files_trashbin/lib/Trash/ITrashManager.php b/apps/files_trashbin/lib/Trash/ITrashManager.php index 25a07d66c8b8c..f5188dc19de71 100644 --- a/apps/files_trashbin/lib/Trash/ITrashManager.php +++ b/apps/files_trashbin/lib/Trash/ITrashManager.php @@ -31,6 +31,15 @@ public function registerBackend(string $storageType, ITrashBackend $backend); #[\Override] public function listTrashRoot(IUser $user): array; + /** + * Get a specific item in the root of the trashbin + * + * @param IUser $user + * @return ?ITrashItem + * @since 35.0.0 + */ + public function getTrashRootItem(IUser $user, string $name): ?ITrashItem; + /** * Temporally prevent files from being moved to the trash * diff --git a/apps/files_trashbin/lib/Trash/LegacyTrashBackend.php b/apps/files_trashbin/lib/Trash/LegacyTrashBackend.php index 54ffd6f4cabb9..2cf0bb0d80bd5 100644 --- a/apps/files_trashbin/lib/Trash/LegacyTrashBackend.php +++ b/apps/files_trashbin/lib/Trash/LegacyTrashBackend.php @@ -65,6 +65,11 @@ public function listTrashRoot(IUser $user): array { return $this->mapTrashItems($entries, $user); } + public function getTrashRootItem(IUser $user, string $name): ?ITrashItem { + $entry = Helper::getTrashFile('/', $user->getUID(), $name); + return $this->mapTrashItems([$entry], $user)[0]; + } + #[\Override] public function listTrashFolder(ITrashItem $folder): array { $user = $folder->getUser(); diff --git a/apps/files_trashbin/lib/Trash/TrashManager.php b/apps/files_trashbin/lib/Trash/TrashManager.php index 3706a6a2e8033..b76ad4e5ce108 100644 --- a/apps/files_trashbin/lib/Trash/TrashManager.php +++ b/apps/files_trashbin/lib/Trash/TrashManager.php @@ -39,6 +39,25 @@ public function listTrashRoot(IUser $user): array { return $items; } + #[\Override] + public function getTrashRootItem(IUser $user, string $name): ?ITrashItem { + foreach ($this->getBackends() as $backend) { + if (method_exists($backend, 'getTrashRootItem')) { + $item = $backend->getTrashRootItem($user, $name); + if ($item !== null) { + return $item; + } + } + $items = $backend->listTrashRoot($user); + foreach ($items as $item) { + if ($item->getName() === $name) { + return $item; + } + } + } + return null; + } + private function getBackendForItem(ITrashItem $item) { return $item->getTrashBackend(); }