mirror of
https://github.com/nextcloud/server.git
synced 2026-06-13 18:50:47 -04:00
feat(trash): Store folder hierarchy of deleted files in trash
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>
This commit is contained in:
parent
2211390ca5
commit
44a9c39712
6 changed files with 48 additions and 11 deletions
|
|
@ -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 !== '.') {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue