diff options
author | Carl Schwan <carl.schwan@nextclound.com> | 2025-08-05 15:34:14 +0200 |
---|---|---|
committer | Carl Schwan <carl.schwan@nextclound.com> | 2025-08-05 17:33:12 +0200 |
commit | 44a9c397122100e59b4eb531d22ca5d5c70bbbb5 (patch) | |
tree | 6a0e6a17509e0e47c8757f25ccc0e250324317c0 | |
parent | 2211390ca5c0972b45584948e14dbcee00f4ad89 (diff) | |
download | nextcloud-server-feat/trashbin-hierarchy.tar.gz nextcloud-server-feat/trashbin-hierarchy.zip |
feat(trash): Store folder hierarchy of deleted files in trashfeat/trashbin-hierarchy
When deleting a nested file, this will create a copy of the folder hierarchy in the trash folder and then copy the file inside the copied folder.
This will prevent too many files to appears in the trash bin.
Signed-off-by: Carl Schwan <carl.schwan@nextclound.com>
-rw-r--r-- | apps/files_trashbin/lib/Helper.php | 24 | ||||
-rw-r--r-- | apps/files_trashbin/lib/Sabre/AbstractTrashFolder.php | 7 | ||||
-rw-r--r-- | apps/files_trashbin/lib/Sabre/TrashFolder.php | 6 | ||||
-rw-r--r-- | apps/files_trashbin/lib/Trash/LegacyTrashBackend.php | 4 | ||||
-rw-r--r-- | apps/files_trashbin/lib/Trash/TrashItem.php | 2 | ||||
-rw-r--r-- | apps/files_trashbin/lib/Trashbin.php | 16 |
6 files changed, 48 insertions, 11 deletions
diff --git a/apps/files_trashbin/lib/Helper.php b/apps/files_trashbin/lib/Helper.php index 746832e9280..a01786bf304 100644 --- a/apps/files_trashbin/lib/Helper.php +++ b/apps/files_trashbin/lib/Helper.php @@ -45,17 +45,30 @@ class Helper { foreach ($dirContent as $entry) { $entryName = $entry->getName(); $name = $entryName; + if ($dir === '' || $dir === '/') { - $pathparts = pathinfo($entryName); - $timestamp = substr($pathparts['extension'], 1); - $name = $pathparts['filename']; + $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); + $timestamp = ''; + for ($i = 0, $count = count($parts); $i < $count && $timestamp === ''; $i++) { + $timestamp = substr(pathinfo($parts[0], PATHINFO_EXTENSION), 1); + } + + if ($timestamp === '') { + $pathParts = pathinfo($entryName); + $timestamp = substr($pathParts['extension'], 1); + $name = $pathParts['filename']; + } } + + $originalPath = ''; - $originalName = substr($entryName, 0, -strlen($timestamp) - 2); + $originalName = $timestamp === '' ? $entryName : substr($entryName, 0, -strlen($timestamp) - 2); + $hasFileTrashEntry = array_key_exists($originalName, $extraData); if (isset($extraData[$originalName][$timestamp]['location'])) { $originalPath = $extraData[$originalName][$timestamp]['location']; if (substr($originalPath, -1) === '/') { @@ -73,6 +86,7 @@ class Helper { 'etag' => '', 'permissions' => Constants::PERMISSION_ALL - Constants::PERMISSION_SHARE, 'fileid' => $entry->getId(), + 'is_trash_root' => !$hasFileTrashEntry, ]; if ($originalPath) { if ($originalPath !== '.') { diff --git a/apps/files_trashbin/lib/Sabre/AbstractTrashFolder.php b/apps/files_trashbin/lib/Sabre/AbstractTrashFolder.php index 9e8f67f4db6..36054ce952d 100644 --- a/apps/files_trashbin/lib/Sabre/AbstractTrashFolder.php +++ b/apps/files_trashbin/lib/Sabre/AbstractTrashFolder.php @@ -19,6 +19,13 @@ abstract class AbstractTrashFolder extends AbstractTrash implements ICollection, $entries = $this->trashManager->listTrashFolder($this->data); $children = array_map(function (ITrashItem $entry) { + if (str_starts_with($entry->getTrashPath(), '/' . $entry->getOriginalLocation())) { + // parent folder is a fake trash folder + if ($entry->getType() === FileInfo::TYPE_FOLDER) { + return new TrashFolder($this->trashManager, $entry); + } + return new TrashFile($this->trashManager, $entry); + } if ($entry->getType() === FileInfo::TYPE_FOLDER) { return new TrashFolderFolder($this->trashManager, $entry); } diff --git a/apps/files_trashbin/lib/Sabre/TrashFolder.php b/apps/files_trashbin/lib/Sabre/TrashFolder.php index e1c495bf08e..000ee3fca87 100644 --- a/apps/files_trashbin/lib/Sabre/TrashFolder.php +++ b/apps/files_trashbin/lib/Sabre/TrashFolder.php @@ -12,6 +12,10 @@ use OCA\Files_Trashbin\Trashbin; class TrashFolder extends AbstractTrashFolder { public function getName(): string { - return Trashbin::getTrashFilename($this->data->getName(), $this->getDeletionTime()); + if ($this->getDeletionTime() === 0) { + return $this->data->getName(); + } else { + return Trashbin::getTrashFilename($this->data->getName(), $this->getDeletionTime()); + } } } diff --git a/apps/files_trashbin/lib/Trash/LegacyTrashBackend.php b/apps/files_trashbin/lib/Trash/LegacyTrashBackend.php index 204defde35c..cb77e7e5c50 100644 --- a/apps/files_trashbin/lib/Trash/LegacyTrashBackend.php +++ b/apps/files_trashbin/lib/Trash/LegacyTrashBackend.php @@ -45,12 +45,12 @@ class LegacyTrashBackend implements ITrashBackend { } /** @psalm-suppress UndefinedInterfaceMethod */ $deletedBy = $this->userManager->get($file['deletedBy']) ?? $parent?->getDeletedBy(); - $trashFilename = Trashbin::getTrashFilename($file->getName(), $file->getMtime()); + $trashFilename = $file->getMtime() === 0 ? $file->getName() : Trashbin::getTrashFilename($file->getName(), $file->getMtime()); return new TrashItem( $this, $originalLocation, $file->getMTime(), - $parentTrashPath . '/' . ($isRoot ? $trashFilename : $file->getName()), + $parentTrashPath . '/' . $trashFilename, $file, $user, $deletedBy, diff --git a/apps/files_trashbin/lib/Trash/TrashItem.php b/apps/files_trashbin/lib/Trash/TrashItem.php index 2ae999a2069..98d205e9330 100644 --- a/apps/files_trashbin/lib/Trash/TrashItem.php +++ b/apps/files_trashbin/lib/Trash/TrashItem.php @@ -39,7 +39,7 @@ class TrashItem implements ITrashItem { } public function isRootItem(): bool { - return substr_count($this->getTrashPath(), '/') === 1; + return substr_count($this->getTrashPath(), '/') === 1 || str_ends_with($this->trashPath, strval($this->deletedTime)); } public function getUser(): IUser { diff --git a/apps/files_trashbin/lib/Trashbin.php b/apps/files_trashbin/lib/Trashbin.php index 667066c2fca..b7d91d6227d 100644 --- a/apps/files_trashbin/lib/Trashbin.php +++ b/apps/files_trashbin/lib/Trashbin.php @@ -264,9 +264,21 @@ class Trashbin implements IEventListener { $lockingProvider = Server::get(ILockingProvider::class); // disable proxy to prevent recursive calls - $trashPath = '/files_trashbin/files/' . static::getTrashFilename($filename, $timestamp); + $trashPath = '/files_trashbin/files/' . $location . '/' . static::getTrashFilename($filename, $timestamp); $gotLock = false; + // Reproduce folder hierarchy of deleted file in trash + $parentDirs = explode('/', $location); + $pathPrefix = '/files_trashbin/files/'; + foreach ($parentDirs as $parentDir) { + $pathPrefix .= $parentDir . '/'; + if ($ownerView->is_dir($pathPrefix)) { + continue; + } + + $ownerView->mkdir($pathPrefix); + } + do { /** @var ILockingStorage & IStorage $trashStorage */ [$trashStorage, $trashInternalPath] = $ownerView->resolvePath($trashPath); @@ -279,7 +291,7 @@ class Trashbin implements IEventListener { $timestamp = $timestamp + 1; - $trashPath = '/files_trashbin/files/' . static::getTrashFilename($filename, $timestamp); + $trashPath = '/files_trashbin/files/' . $location . static::getTrashFilename($filename, $timestamp); } } while (!$gotLock); |