aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorCarl Schwan <carl.schwan@nextclound.com>2025-08-05 15:34:14 +0200
committerCarl Schwan <carl.schwan@nextclound.com>2025-08-05 17:33:12 +0200
commit44a9c397122100e59b4eb531d22ca5d5c70bbbb5 (patch)
tree6a0e6a17509e0e47c8757f25ccc0e250324317c0
parent2211390ca5c0972b45584948e14dbcee00f4ad89 (diff)
downloadnextcloud-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.php24
-rw-r--r--apps/files_trashbin/lib/Sabre/AbstractTrashFolder.php7
-rw-r--r--apps/files_trashbin/lib/Sabre/TrashFolder.php6
-rw-r--r--apps/files_trashbin/lib/Trash/LegacyTrashBackend.php4
-rw-r--r--apps/files_trashbin/lib/Trash/TrashItem.php2
-rw-r--r--apps/files_trashbin/lib/Trashbin.php16
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);