diff options
author | Morris Jobke <hey@morrisjobke.de> | 2018-10-23 17:11:16 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2018-10-23 17:11:16 +0200 |
commit | 4ad27260a92961e9893d8351f607e1714f73007f (patch) | |
tree | fb5a6aa474e30e83b779ba9cf92100bd6432f78e /apps/files_trashbin | |
parent | e0f9257be933ae8605424aaf5a08860cfd4359ff (diff) | |
parent | 9e0ebf183044f00d7e1e3b30c9a01e84d051dd78 (diff) | |
download | nextcloud-server-4ad27260a92961e9893d8351f607e1714f73007f.tar.gz nextcloud-server-4ad27260a92961e9893d8351f607e1714f73007f.zip |
Merge pull request #11439 from nextcloud/trash-modular-api
Modular trashbin api
Diffstat (limited to 'apps/files_trashbin')
27 files changed, 1027 insertions, 479 deletions
diff --git a/apps/files_trashbin/appinfo/info.xml b/apps/files_trashbin/appinfo/info.xml index eb8f46dced1..023cad1264f 100644 --- a/apps/files_trashbin/appinfo/info.xml +++ b/apps/files_trashbin/appinfo/info.xml @@ -44,4 +44,8 @@ To prevent a user from running out of disk space, the Deleted files app will not <plugin>OCA\Files_Trashbin\Sabre\PropfindPlugin</plugin> </plugins> </sabre> + + <trash> + <backend for="OCP\Files\Storage\IStorage">OCA\Files_Trashbin\Trash\LegacyTrashBackend</backend> + </trash> </info> diff --git a/apps/files_trashbin/composer/composer/autoload_classmap.php b/apps/files_trashbin/composer/composer/autoload_classmap.php index 164d64333ce..011e178a5bd 100644 --- a/apps/files_trashbin/composer/composer/autoload_classmap.php +++ b/apps/files_trashbin/composer/composer/autoload_classmap.php @@ -19,6 +19,8 @@ return array( 'OCA\\Files_Trashbin\\Helper' => $baseDir . '/../lib/Helper.php', 'OCA\\Files_Trashbin\\Hooks' => $baseDir . '/../lib/Hooks.php', 'OCA\\Files_Trashbin\\Sabre\\AbstractTrash' => $baseDir . '/../lib/Sabre/AbstractTrash.php', + 'OCA\\Files_Trashbin\\Sabre\\AbstractTrashFile' => $baseDir . '/../lib/Sabre/AbstractTrashFile.php', + 'OCA\\Files_Trashbin\\Sabre\\AbstractTrashFolder' => $baseDir . '/../lib/Sabre/AbstractTrashFolder.php', 'OCA\\Files_Trashbin\\Sabre\\ITrash' => $baseDir . '/../lib/Sabre/ITrash.php', 'OCA\\Files_Trashbin\\Sabre\\PropfindPlugin' => $baseDir . '/../lib/Sabre/PropfindPlugin.php', 'OCA\\Files_Trashbin\\Sabre\\RestoreFolder' => $baseDir . '/../lib/Sabre/RestoreFolder.php', @@ -30,5 +32,12 @@ return array( 'OCA\\Files_Trashbin\\Sabre\\TrashHome' => $baseDir . '/../lib/Sabre/TrashHome.php', 'OCA\\Files_Trashbin\\Sabre\\TrashRoot' => $baseDir . '/../lib/Sabre/TrashRoot.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', + 'OCA\\Files_Trashbin\\Trash\\ITrashItem' => $baseDir . '/../lib/Trash/ITrashItem.php', + 'OCA\\Files_Trashbin\\Trash\\ITrashManager' => $baseDir . '/../lib/Trash/ITrashManager.php', + 'OCA\\Files_Trashbin\\Trash\\LegacyTrashBackend' => $baseDir . '/../lib/Trash/LegacyTrashBackend.php', + 'OCA\\Files_Trashbin\\Trash\\TrashItem' => $baseDir . '/../lib/Trash/TrashItem.php', + 'OCA\\Files_Trashbin\\Trash\\TrashManager' => $baseDir . '/../lib/Trash/TrashManager.php', 'OCA\\Files_Trashbin\\Trashbin' => $baseDir . '/../lib/Trashbin.php', ); diff --git a/apps/files_trashbin/composer/composer/autoload_static.php b/apps/files_trashbin/composer/composer/autoload_static.php index 6ebb8c35f31..37144ec4288 100644 --- a/apps/files_trashbin/composer/composer/autoload_static.php +++ b/apps/files_trashbin/composer/composer/autoload_static.php @@ -34,6 +34,8 @@ class ComposerStaticInitFiles_Trashbin 'OCA\\Files_Trashbin\\Helper' => __DIR__ . '/..' . '/../lib/Helper.php', 'OCA\\Files_Trashbin\\Hooks' => __DIR__ . '/..' . '/../lib/Hooks.php', 'OCA\\Files_Trashbin\\Sabre\\AbstractTrash' => __DIR__ . '/..' . '/../lib/Sabre/AbstractTrash.php', + 'OCA\\Files_Trashbin\\Sabre\\AbstractTrashFile' => __DIR__ . '/..' . '/../lib/Sabre/AbstractTrashFile.php', + 'OCA\\Files_Trashbin\\Sabre\\AbstractTrashFolder' => __DIR__ . '/..' . '/../lib/Sabre/AbstractTrashFolder.php', 'OCA\\Files_Trashbin\\Sabre\\ITrash' => __DIR__ . '/..' . '/../lib/Sabre/ITrash.php', 'OCA\\Files_Trashbin\\Sabre\\PropfindPlugin' => __DIR__ . '/..' . '/../lib/Sabre/PropfindPlugin.php', 'OCA\\Files_Trashbin\\Sabre\\RestoreFolder' => __DIR__ . '/..' . '/../lib/Sabre/RestoreFolder.php', @@ -45,6 +47,13 @@ class ComposerStaticInitFiles_Trashbin 'OCA\\Files_Trashbin\\Sabre\\TrashHome' => __DIR__ . '/..' . '/../lib/Sabre/TrashHome.php', 'OCA\\Files_Trashbin\\Sabre\\TrashRoot' => __DIR__ . '/..' . '/../lib/Sabre/TrashRoot.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', + 'OCA\\Files_Trashbin\\Trash\\ITrashItem' => __DIR__ . '/..' . '/../lib/Trash/ITrashItem.php', + 'OCA\\Files_Trashbin\\Trash\\ITrashManager' => __DIR__ . '/..' . '/../lib/Trash/ITrashManager.php', + 'OCA\\Files_Trashbin\\Trash\\LegacyTrashBackend' => __DIR__ . '/..' . '/../lib/Trash/LegacyTrashBackend.php', + 'OCA\\Files_Trashbin\\Trash\\TrashItem' => __DIR__ . '/..' . '/../lib/Trash/TrashItem.php', + 'OCA\\Files_Trashbin\\Trash\\TrashManager' => __DIR__ . '/..' . '/../lib/Trash/TrashManager.php', 'OCA\\Files_Trashbin\\Trashbin' => __DIR__ . '/..' . '/../lib/Trashbin.php', ); diff --git a/apps/files_trashbin/lib/AppInfo/Application.php b/apps/files_trashbin/lib/AppInfo/Application.php index 8e4ec255567..06a34e0df84 100644 --- a/apps/files_trashbin/lib/AppInfo/Application.php +++ b/apps/files_trashbin/lib/AppInfo/Application.php @@ -24,8 +24,11 @@ namespace OCA\Files_Trashbin\AppInfo; use OCA\DAV\Connector\Sabre\Principal; +use OCA\Files_Trashbin\Trash\ITrashManager; +use OCA\Files_Trashbin\Trash\TrashManager; use OCP\AppFramework\App; use OCA\Files_Trashbin\Expiration; +use OCP\AppFramework\IAppContainer; use OCP\AppFramework\Utility\ITimeFactory; use OCA\Files_Trashbin\Capabilities; @@ -61,5 +64,36 @@ class Application extends App { \OC::$server->getConfig() ); }); + + $container->registerService(ITrashManager::class, function(IAppContainer $c) { + return new TrashManager(); + }); + + $this->registerTrashBackends(); + } + + public function registerTrashBackends() { + $server = $this->getContainer()->getServer(); + $logger = $server->getLogger(); + $appManager = $server->getAppManager(); + /** @var ITrashManager $trashManager */ + $trashManager = $this->getContainer()->getServer()->query(ITrashManager::class); + foreach($appManager->getInstalledApps() as $app) { + $appInfo = $appManager->getAppInfo($app); + if (isset($appInfo['trash'])) { + $backends = $appInfo['trash']; + foreach($backends as $backend) { + $class = $backend['@value']; + $for = $backend['@attributes']['for']; + + try { + $backendObject = $server->query($class); + $trashManager->registerBackend($for, $backendObject); + } catch (\Exception $e) { + $logger->logException($e); + } + } + } + } } } diff --git a/apps/files_trashbin/lib/Controller/PreviewController.php b/apps/files_trashbin/lib/Controller/PreviewController.php index 8a1b31703bb..f79e19a463e 100644 --- a/apps/files_trashbin/lib/Controller/PreviewController.php +++ b/apps/files_trashbin/lib/Controller/PreviewController.php @@ -22,27 +22,31 @@ declare(strict_types=1); * along with this program. If not, see <http://www.gnu.org/licenses/>. * */ + namespace OCA\Files_Trashbin\Controller; +use OCA\Files_Trashbin\Trash\ITrashManager; use OCP\AppFramework\Controller; use OCP\AppFramework\Http; use OCP\AppFramework\Http\DataResponse; use OCP\AppFramework\Utility\ITimeFactory; -use OCP\Files\File; use OCP\Files\Folder; use OCP\Files\IMimeTypeDetector; use OCP\Files\IRootFolder; use OCP\Files\NotFoundException; use OCP\IPreview; use OCP\IRequest; +use OCP\IUserSession; class PreviewController extends Controller { - /** @var IRootFolder */ private $rootFolder; - /** @var string */ - private $userId; + /** @var ITrashManager */ + private $trashManager; + + /** @var IUserSession */ + private $userSession; /** @var IMimeTypeDetector */ private $mimeTypeDetector; @@ -53,17 +57,21 @@ class PreviewController extends Controller { /** @var ITimeFactory */ private $time; - public function __construct(string $appName, - IRequest $request, - IRootFolder $rootFolder, - string $userId, - IMimeTypeDetector $mimeTypeDetector, - IPreview $previewManager, - ITimeFactory $time) { + public function __construct( + string $appName, + IRequest $request, + IRootFolder $rootFolder, + ITrashManager $trashManager, + IUserSession $userSession, + IMimeTypeDetector $mimeTypeDetector, + IPreview $previewManager, + ITimeFactory $time + ) { parent::__construct($appName, $request); + $this->trashManager = $trashManager; $this->rootFolder = $rootFolder; - $this->userId = $userId; + $this->userSession = $userSession; $this->mimeTypeDetector = $mimeTypeDetector; $this->previewManager = $previewManager; $this->time = $time; @@ -86,39 +94,28 @@ class PreviewController extends Controller { } try { - $userFolder = $this->rootFolder->getUserFolder($this->userId); - /** @var Folder $trash */ - $trash = $userFolder->getParent()->get('files_trashbin/files'); - $trashFiles = $trash->getById($fileId); - - if (empty($trashFiles)) { - throw new NotFoundException(); + $file = $this->trashManager->getTrashNodeById($this->userSession->getUser(), $fileId); + if ($file === null) { + return new DataResponse([], Http::STATUS_NOT_FOUND); } - - $trashFile = array_pop($trashFiles); - - if ($trashFile instanceof Folder) { + if ($file instanceof Folder) { return new DataResponse([], Http::STATUS_BAD_REQUEST); } + $pathParts = pathinfo($file->getName()); + $extension = $pathParts['extension']; + $fileName = $pathParts['filename']; /* * Files in the root of the trashbin are timetamped. * So we have to strip that in order to properly detect the mimetype of the file. */ - if ($trashFile->getParent()->getPath() === $trash->getPath()) { - /** @var File $trashFile */ - $fileName = $trashFile->getName(); - $i = strrpos($fileName, '.'); - if ($i !== false) { - $fileName = substr($fileName, 0, $i); - } - + if (preg_match('/d\d+/', $extension)) { $mimeType = $this->mimeTypeDetector->detectPath($fileName); } else { - $mimeType = $this->mimeTypeDetector->detectPath($trashFile->getName()); + $mimeType = $this->mimeTypeDetector->detectPath($file->getName()); } - $f = $this->previewManager->getPreview($trashFile, $x, $y, true, IPreview::MODE_FILL, $mimeType); + $f = $this->previewManager->getPreview($file, $x, $y, true, IPreview::MODE_FILL, $mimeType); $response = new Http\FileDisplayResponse($f, Http::STATUS_OK, ['Content-Type' => $f->getMimeType()]); // Cache previews for 24H diff --git a/apps/files_trashbin/lib/Sabre/AbstractTrash.php b/apps/files_trashbin/lib/Sabre/AbstractTrash.php index 43f9cc02749..b4d13a41e38 100644 --- a/apps/files_trashbin/lib/Sabre/AbstractTrash.php +++ b/apps/files_trashbin/lib/Sabre/AbstractTrash.php @@ -1,4 +1,5 @@ <?php +declare(strict_types=1); /** * @copyright Copyright (c) 2018 Robin Appelman <robin@icewind.nl> * @@ -21,13 +22,20 @@ namespace OCA\Files_Trashbin\Sabre; +use OCA\Files_Trashbin\Trash\ITrashItem; +use OCA\Files_Trashbin\Trash\ITrashManager; use OCP\Files\FileInfo; +use OCP\IUser; abstract class AbstractTrash implements ITrash { - /** @var FileInfo */ + /** @var ITrashItem */ protected $data; - public function __construct(FileInfo $data) { + /** @var ITrashManager */ + protected $trashManager; + + public function __construct(ITrashManager $trashManager, ITrashItem $data) { + $this->trashManager = $trashManager; $this->data = $data; } @@ -36,7 +44,7 @@ abstract class AbstractTrash implements ITrash { } public function getDeletionTime(): int { - return $this->data->getMtime(); + return $this->data->getDeletedTime(); } public function getFileId(): int { @@ -66,4 +74,17 @@ abstract class AbstractTrash implements ITrash { public function getName(): string { return $this->data->getName(); } + + public function getOriginalLocation(): string { + return $this->data->getOriginalLocation(); + } + + public function delete() { + $this->trashManager->removeItem($this->data); + } + + public function restore(): bool { + $this->trashManager->restoreItem($this->data); + return true; + } } diff --git a/apps/files_trashbin/lib/Sabre/AbstractTrashFile.php b/apps/files_trashbin/lib/Sabre/AbstractTrashFile.php new file mode 100644 index 00000000000..da7c94eb35d --- /dev/null +++ b/apps/files_trashbin/lib/Sabre/AbstractTrashFile.php @@ -0,0 +1,36 @@ +<?php +declare(strict_types=1); +/** + * @copyright Copyright (c) 2018 Robin Appelman <robin@icewind.nl> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +namespace OCA\Files_Trashbin\Sabre; + +use Sabre\DAV\Exception\Forbidden; +use Sabre\DAV\IFile; + +abstract class AbstractTrashFile extends AbstractTrash implements IFile , ITrash{ + public function put($data) { + throw new Forbidden(); + } + + public function setName($name) { + throw new Forbidden(); + } +} diff --git a/apps/files_trashbin/lib/Sabre/AbstractTrashFolder.php b/apps/files_trashbin/lib/Sabre/AbstractTrashFolder.php new file mode 100644 index 00000000000..dd45dbd5c8c --- /dev/null +++ b/apps/files_trashbin/lib/Sabre/AbstractTrashFolder.php @@ -0,0 +1,77 @@ +<?php +declare(strict_types=1); +/** + * @copyright Copyright (c) 2018 Robin Appelman <robin@icewind.nl> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +namespace OCA\Files_Trashbin\Sabre; + +use OCA\Files_Trashbin\Trash\ITrashItem; +use OCP\Files\FileInfo; +use Sabre\DAV\Exception\Forbidden; +use Sabre\DAV\Exception\NotFound; +use Sabre\DAV\ICollection; + +abstract class AbstractTrashFolder extends AbstractTrash implements ICollection, ITrash { + public function getChildren(): array { + $entries = $this->trashManager->listTrashFolder($this->data); + + $children = array_map(function (ITrashItem $entry) { + if ($entry->getType() === FileInfo::TYPE_FOLDER) { + return new TrashFolderFolder($this->trashManager, $entry); + } + return new TrashFolderFile($this->trashManager, $entry); + }, $entries); + + return $children; + } + + public function getChild($name): ITrash { + $entries = $this->getChildren(); + + foreach ($entries as $entry) { + if ($entry->getName() === $name) { + return $entry; + } + } + + throw new NotFound(); + } + + public function childExists($name): bool { + try { + $this->getChild($name); + return true; + } catch (NotFound $e) { + return false; + } + } + + public function setName($name) { + throw new Forbidden(); + } + + public function createFile($name, $data = null) { + throw new Forbidden(); + } + + public function createDirectory($name) { + throw new Forbidden(); + } +} diff --git a/apps/files_trashbin/lib/Sabre/RestoreFolder.php b/apps/files_trashbin/lib/Sabre/RestoreFolder.php index 04f23db0ed4..177064dbb48 100644 --- a/apps/files_trashbin/lib/Sabre/RestoreFolder.php +++ b/apps/files_trashbin/lib/Sabre/RestoreFolder.php @@ -31,14 +31,6 @@ use Sabre\DAV\INode; class RestoreFolder implements ICollection, IMoveTarget { - - /** @var string */ - protected $userId; - - public function __construct(string $userId) { - $this->userId = $userId; - } - public function createFile($name, $data = null) { throw new Forbidden(); } diff --git a/apps/files_trashbin/lib/Sabre/RootCollection.php b/apps/files_trashbin/lib/Sabre/RootCollection.php index be31d200f71..0b55953aa3f 100644 --- a/apps/files_trashbin/lib/Sabre/RootCollection.php +++ b/apps/files_trashbin/lib/Sabre/RootCollection.php @@ -21,18 +21,27 @@ declare(strict_types=1); * along with this program. If not, see <http://www.gnu.org/licenses/>. * */ + namespace OCA\Files_Trashbin\Sabre; +use OCA\Files_Trashbin\Trash\ITrashManager; use OCP\IConfig; use Sabre\DAV\INode; use Sabre\DAVACL\AbstractPrincipalCollection; use Sabre\DAVACL\PrincipalBackend; class RootCollection extends AbstractPrincipalCollection { + /** @var ITrashManager */ + private $trashManager; - public function __construct(PrincipalBackend\BackendInterface $principalBackend, IConfig $config) { + public function __construct( + ITrashManager $trashManager, + PrincipalBackend\BackendInterface $principalBackend, + IConfig $config + ) { parent::__construct($principalBackend, 'principals/users'); + $this->trashManager = $trashManager; $this->disableListing = !$config->getSystemValue('debug', false); } @@ -47,12 +56,12 @@ class RootCollection extends AbstractPrincipalCollection { * @return INode */ public function getChildForPrincipal(array $principalInfo): TrashHome { - list(,$name) = \Sabre\Uri\split($principalInfo['uri']); + list(, $name) = \Sabre\Uri\split($principalInfo['uri']); $user = \OC::$server->getUserSession()->getUser(); if (is_null($user) || $name !== $user->getUID()) { throw new \Sabre\DAV\Exception\Forbidden(); } - return new TrashHome($principalInfo); + return new TrashHome($principalInfo, $this->trashManager, $user); } public function getName(): string { diff --git a/apps/files_trashbin/lib/Sabre/TrashFile.php b/apps/files_trashbin/lib/Sabre/TrashFile.php index 840ca6a1938..dd6500e5b81 100644 --- a/apps/files_trashbin/lib/Sabre/TrashFile.php +++ b/apps/files_trashbin/lib/Sabre/TrashFile.php @@ -21,46 +21,15 @@ declare(strict_types=1); * along with this program. If not, see <http://www.gnu.org/licenses/>. * */ -namespace OCA\Files_Trashbin\Sabre; - -use OCP\Files\FileInfo; -use Sabre\DAV\Exception\Forbidden; -use Sabre\DAV\IFile; - -class TrashFile extends AbstractTrash implements IFile, ITrash { - /** @var string */ - private $userId; - - public function __construct(string $userId, FileInfo $data) { - $this->userId = $userId; - parent::__construct($data); - } - public function put($data) { - throw new Forbidden(); - } +namespace OCA\Files_Trashbin\Sabre; +class TrashFile extends AbstractTrashFile { public function get() { - return $this->data->getStorage()->fopen($this->data->getInternalPath().'.d'.$this->getLastModified(), 'rb'); - } - - public function delete() { - \OCA\Files_Trashbin\Trashbin::delete($this->data->getName(), $this->userId, $this->getLastModified()); + return $this->data->getStorage()->fopen($this->data->getInternalPath() . '.d' . $this->getLastModified(), 'rb'); } public function getName(): string { return $this->data->getName() . '.d' . $this->getLastModified(); } - - public function setName($name) { - throw new Forbidden(); - } - - public function restore(): bool { - return \OCA\Files_Trashbin\Trashbin::restore($this->getName(), $this->data->getName(), $this->getLastModified()); - } - - public function getOriginalLocation(): string { - return $this->data['extraData']; - } } diff --git a/apps/files_trashbin/lib/Sabre/TrashFolder.php b/apps/files_trashbin/lib/Sabre/TrashFolder.php index d884eefcc9f..108aaf4f312 100644 --- a/apps/files_trashbin/lib/Sabre/TrashFolder.php +++ b/apps/files_trashbin/lib/Sabre/TrashFolder.php @@ -23,85 +23,9 @@ declare(strict_types=1); */ namespace OCA\Files_Trashbin\Sabre; -use OCP\Files\FileInfo; -use Sabre\DAV\Exception\Forbidden; -use Sabre\DAV\Exception\NotFound; -use Sabre\DAV\ICollection; - -class TrashFolder extends AbstractTrash implements ICollection, ITrash { - /** @var string */ - private $userId; - - public function __construct(string $root, string $userId, FileInfo $data) { - $this->userId = $userId; - parent::__construct($data); - } - - public function createFile($name, $data = null) { - throw new Forbidden(); - } - - public function createDirectory($name) { - throw new Forbidden(); - } - - public function getChild($name): ITrash { - $entries = \OCA\Files_Trashbin\Helper::getTrashFiles($this->getName(), $this->userId); - - foreach ($entries as $entry) { - if ($entry->getName() === $name) { - if ($entry->getType() === FileInfo::TYPE_FOLDER) { - return new TrashFolderFolder($this->getName(), $this->userId, $entry, $this->getOriginalLocation()); - } - return new TrashFolderFile($this->getName(), $this->userId, $entry, $this->getOriginalLocation()); - } - } - - throw new NotFound(); - } - - public function getChildren(): array { - $entries = \OCA\Files_Trashbin\Helper::getTrashFiles($this->getName(), $this->userId); - - $children = array_map(function (FileInfo $entry) { - if ($entry->getType() === FileInfo::TYPE_FOLDER) { - return new TrashFolderFolder($this->getName(), $this->userId, $entry, $this->getOriginalLocation()); - } - return new TrashFolderFile($this->getName(), $this->userId, $entry, $this->getOriginalLocation()); - }, $entries); - - return $children; - } - - public function childExists($name): bool { - $entries = \OCA\Files_Trashbin\Helper::getTrashFiles($this->getName(), $this->userId); - - foreach ($entries as $entry) { - if ($entry->getName() === $name) { - return true; - } - } - - return false; - } - - public function delete() { - \OCA\Files_Trashbin\Trashbin::delete($this->data->getName(), $this->userId, $this->getLastModified()); - } +class TrashFolder extends AbstractTrashFolder { public function getName(): string { return $this->data->getName() . '.d' . $this->getLastModified(); } - - public function setName($name) { - throw new Forbidden(); - } - - public function restore(): bool { - return \OCA\Files_Trashbin\Trashbin::restore($this->getName(), $this->data->getName(), $this->getLastModified()); - } - - public function getOriginalLocation(): string { - return $this->data['extraData']; - } } diff --git a/apps/files_trashbin/lib/Sabre/TrashFolderFile.php b/apps/files_trashbin/lib/Sabre/TrashFolderFile.php index 3e28d048b42..31ee9535b72 100644 --- a/apps/files_trashbin/lib/Sabre/TrashFolderFile.php +++ b/apps/files_trashbin/lib/Sabre/TrashFolderFile.php @@ -23,51 +23,9 @@ declare(strict_types=1); */ namespace OCA\Files_Trashbin\Sabre; -use OCP\Files\FileInfo; -use Sabre\DAV\Exception\Forbidden; -use Sabre\DAV\IFile; - -class TrashFolderFile extends AbstractTrash implements IFile, ITrash { - /** @var string */ - private $root; - - /** @var string */ - private $userId; - - /** @var string */ - private $location; - - public function __construct(string $root, - string $userId, - FileInfo $data, - string $location) { - $this->root = $root; - $this->userId = $userId; - $this->location = $location; - parent::__construct($data); - } - - public function put($data) { - throw new Forbidden(); - } +class TrashFolderFile extends AbstractTrashFile { public function get() { return $this->data->getStorage()->fopen($this->data->getInternalPath(), 'rb'); } - - public function delete() { - \OCA\Files_Trashbin\Trashbin::delete($this->root . '/' . $this->getName(), $this->userId, null); - } - - public function setName($name) { - throw new Forbidden(); - } - - public function restore(): bool { - return \OCA\Files_Trashbin\Trashbin::restore($this->root . '/' . $this->getName(), $this->data->getName(), null); - } - - public function getOriginalLocation(): string { - return $this->location . '/' . $this->getFilename(); - } } diff --git a/apps/files_trashbin/lib/Sabre/TrashFolderFolder.php b/apps/files_trashbin/lib/Sabre/TrashFolderFolder.php index 4ee9a0e2db0..5332b7dd4c0 100644 --- a/apps/files_trashbin/lib/Sabre/TrashFolderFolder.php +++ b/apps/files_trashbin/lib/Sabre/TrashFolderFolder.php @@ -21,95 +21,8 @@ declare(strict_types=1); * along with this program. If not, see <http://www.gnu.org/licenses/>. * */ -namespace OCA\Files_Trashbin\Sabre; - -use OCP\Files\FileInfo; -use Sabre\DAV\Exception\Forbidden; -use Sabre\DAV\Exception\NotFound; -use Sabre\DAV\ICollection; - -class TrashFolderFolder extends AbstractTrash implements ICollection, ITrash { - - /** @var string */ - private $root; - - /** @var string */ - private $userId; - - /** @var string */ - private $location; - - public function __construct(string $root, - string $userId, - FileInfo $data, - string $location) { - $this->root = $root; - $this->userId = $userId; - $this->location = $location; - parent::__construct($data); - } - - public function createFile($name, $data = null) { - throw new Forbidden(); - } - - public function createDirectory($name) { - throw new Forbidden(); - } - - public function getChild($name): ITrash { - $entries = \OCA\Files_Trashbin\Helper::getTrashFiles($this->root . '/' . $this->getName(), $this->userId); - - foreach ($entries as $entry) { - if ($entry->getName() === $name) { - if ($entry->getType() === FileInfo::TYPE_FOLDER) { - return new TrashFolderFolder($this->root . '/' . $this->getName(), $this->userId, $entry, $this->getOriginalLocation()); - } - return new TrashFolderFile($this->root . '/' . $this->getName(), $this->userId, $entry, $this->getOriginalLocation()); - } - } - throw new NotFound(); - } - - public function getChildren(): array { - $entries = \OCA\Files_Trashbin\Helper::getTrashFiles($this->root . '/' . $this->getName(), $this->userId); - - $children = array_map(function (FileInfo $entry) { - if ($entry->getType() === FileInfo::TYPE_FOLDER) { - return new TrashFolderFolder($this->root.'/'.$this->getName(), $this->userId, $entry, $this->getOriginalLocation()); - } - return new TrashFolderFile($this->root.'/'.$this->getName(), $this->userId, $entry, $this->getOriginalLocation()); - }, $entries); - - return $children; - } - - public function childExists($name): bool { - $entries = \OCA\Files_Trashbin\Helper::getTrashFiles($this->root . '/' . $this->getName(), $this->userId); - - foreach ($entries as $entry) { - if ($entry->getName() === $name) { - return true; - } - } - - return false; - } - - public function delete() { - \OCA\Files_Trashbin\Trashbin::delete($this->root . '/' . $this->getName(), $this->userId, null); - } - - public function setName($name) { - throw new Forbidden(); - } - - public function restore(): bool { - return \OCA\Files_Trashbin\Trashbin::restore($this->root . '/' . $this->getName(), $this->data->getName(), null); - } +namespace OCA\Files_Trashbin\Sabre; - public function getOriginalLocation(): string { - return $this->location . '/' . $this->getFilename(); - } +class TrashFolderFolder extends AbstractTrashFolder { } diff --git a/apps/files_trashbin/lib/Sabre/TrashHome.php b/apps/files_trashbin/lib/Sabre/TrashHome.php index d1c50c9c6a3..12c2578bc3b 100644 --- a/apps/files_trashbin/lib/Sabre/TrashHome.php +++ b/apps/files_trashbin/lib/Sabre/TrashHome.php @@ -21,19 +21,33 @@ declare(strict_types=1); * along with this program. If not, see <http://www.gnu.org/licenses/>. * */ + namespace OCA\Files_Trashbin\Sabre; +use OCA\Files_Trashbin\Trash\ITrashManager; +use OCP\IUser; use Sabre\DAV\Exception\Forbidden; use Sabre\DAV\Exception\NotFound; use Sabre\DAV\ICollection; class TrashHome implements ICollection { + /** @var ITrashManager */ + private $trashManager; /** @var array */ private $principalInfo; - public function __construct(array $principalInfo) { + /** @var IUser */ + private $user; + + public function __construct( + array $principalInfo, + ITrashManager $trashManager, + IUser $user + ) { $this->principalInfo = $principalInfo; + $this->trashManager = $trashManager; + $this->user = $user; } public function delete() { @@ -41,7 +55,7 @@ class TrashHome implements ICollection { } public function getName(): string { - list(,$name) = \Sabre\Uri\split($this->principalInfo['uri']); + list(, $name) = \Sabre\Uri\split($this->principalInfo['uri']); return $name; } @@ -58,24 +72,20 @@ class TrashHome implements ICollection { } public function getChild($name) { - list(,$userId) = \Sabre\Uri\split($this->principalInfo['uri']); - if ($name === 'restore') { - return new RestoreFolder($userId); + return new RestoreFolder(); } if ($name === 'trash') { - return new TrashRoot($userId); + return new TrashRoot($this->user, $this->trashManager); } throw new NotFound(); } public function getChildren(): array { - list(,$userId) = \Sabre\Uri\split($this->principalInfo['uri']); - return [ - new RestoreFolder($userId), - new TrashRoot($userId), + new RestoreFolder(), + new TrashRoot($this->user, $this->trashManager) ]; } diff --git a/apps/files_trashbin/lib/Sabre/TrashRoot.php b/apps/files_trashbin/lib/Sabre/TrashRoot.php index 73b9d44d7e1..45f27f48b17 100644 --- a/apps/files_trashbin/lib/Sabre/TrashRoot.php +++ b/apps/files_trashbin/lib/Sabre/TrashRoot.php @@ -23,18 +23,25 @@ declare(strict_types=1); */ namespace OCA\Files_Trashbin\Sabre; +use OCA\Files_Trashbin\Trash\ITrashItem; +use OCA\Files_Trashbin\Trash\ITrashManager; use OCP\Files\FileInfo; +use OCP\IUser; use Sabre\DAV\Exception\Forbidden; use Sabre\DAV\Exception\NotFound; use Sabre\DAV\ICollection; class TrashRoot implements ICollection { - /** @var string */ - private $userId; + /** @var IUser */ + private $user; - public function __construct(string $userId) { - $this->userId = $userId; + /** @var ITrashManager */ + private $trashManager; + + public function __construct(IUser $user, ITrashManager $trashManager) { + $this->user = $user; + $this->trashManager = $trashManager; } public function delete() { @@ -57,44 +64,38 @@ class TrashRoot implements ICollection { throw new Forbidden('Not allowed to create folders in the trashbin'); } - public function getChild($name): ITrash { - $entries = \OCA\Files_Trashbin\Helper::getTrashFiles('/', $this->userId); - - foreach ($entries as $entry) { - if ($entry->getName() . '.d'.$entry->getMtime() === $name) { - if ($entry->getType() === FileInfo::TYPE_FOLDER) { - return new TrashFolder('/', $this->userId, $entry); - } - return new TrashFile($this->userId, $entry); - } - } - - throw new NotFound(); - } - public function getChildren(): array { - $entries = \OCA\Files_Trashbin\Helper::getTrashFiles('/', $this->userId); + $entries = $this->trashManager->listTrashRoot($this->user); - $children = array_map(function (FileInfo $entry) { + $children = array_map(function (ITrashItem $entry) { if ($entry->getType() === FileInfo::TYPE_FOLDER) { - return new TrashFolder('/', $this->userId, $entry); + return new TrashFolder($this->trashManager, $entry); } - return new TrashFile($this->userId, $entry); + return new TrashFile($this->trashManager, $entry); }, $entries); return $children; } - public function childExists($name): bool { - $entries = \OCA\Files_Trashbin\Helper::getTrashFiles('/', $this->userId); + public function getChild($name): ITrash { + $entries = $this->getChildren(); foreach ($entries as $entry) { - if ($entry->getName() . '.d'.$entry->getMtime() === $name) { - return true; + if ($entry->getName() === $name) { + return $entry; } } - return false; + throw new NotFound(); + } + + public function childExists($name): bool { + try { + $this->getChild($name); + return true; + } catch (NotFound $e) { + return false; + } } public function getLastModified(): int { diff --git a/apps/files_trashbin/lib/Storage.php b/apps/files_trashbin/lib/Storage.php index 54b47a6a19e..0db634eeb9e 100644 --- a/apps/files_trashbin/lib/Storage.php +++ b/apps/files_trashbin/lib/Storage.php @@ -31,34 +31,18 @@ use OC\Files\Filesystem; use OC\Files\Storage\Wrapper\Wrapper; use OC\Files\View; use OCA\Files_Trashbin\Events\MoveToTrashEvent; +use OCA\Files_Trashbin\Trash\ITrashManager; use OCP\Encryption\Exceptions\GenericEncryptionException; use OCP\Files\IRootFolder; +use OCP\Files\Mount\IMountPoint; use OCP\Files\Node; use OCP\ILogger; use OCP\IUserManager; use Symfony\Component\EventDispatcher\EventDispatcher; class Storage extends Wrapper { - + /** @var IMountPoint */ private $mountPoint; - // remember already deleted files to avoid infinite loops if the trash bin - // move files across storages - private $deletedFiles = array(); - - /** - * Disable trash logic - * - * @var bool - */ - private static $disableTrash = false; - - /** - * remember which file/folder was moved out of s shared folder - * in this case we want to add a copy to the owners trash bin - * - * @var array - */ - private static $moveOutOfSharedFolder = []; /** @var IUserManager */ private $userManager; @@ -72,21 +56,29 @@ class Storage extends Wrapper { /** @var IRootFolder */ private $rootFolder; + /** @var ITrashManager */ + private $trashManager; + /** * Storage constructor. * * @param array $parameters + * @param ITrashManager $trashManager * @param IUserManager|null $userManager * @param ILogger|null $logger * @param EventDispatcher|null $eventDispatcher * @param IRootFolder|null $rootFolder */ - public function __construct($parameters, - IUserManager $userManager = null, - ILogger $logger = null, - EventDispatcher $eventDispatcher = null, - IRootFolder $rootFolder = null) { + public function __construct( + $parameters, + ITrashManager $trashManager = null, + IUserManager $userManager = null, + ILogger $logger = null, + EventDispatcher $eventDispatcher = null, + IRootFolder $rootFolder = null + ) { $this->mountPoint = $parameters['mountPoint']; + $this->trashManager = $trashManager; $this->userManager = $userManager; $this->logger = $logger; $this->eventDispatcher = $eventDispatcher; @@ -95,81 +87,6 @@ class Storage extends Wrapper { } /** - * @internal - */ - public static function preRenameHook($params) { - // in cross-storage cases, a rename is a copy + unlink, - // that last unlink must not go to trash, only exception: - // if the file was moved from a shared storage to a local folder, - // in this case the owner should get a copy in his trash bin so that - // they can restore the files again - - $oldPath = $params['oldpath']; - $newPath = dirname($params['newpath']); - $currentUser = \OC::$server->getUserSession()->getUser(); - - $fileMovedOutOfSharedFolder = false; - - try { - if ($currentUser) { - $currentUserId = $currentUser->getUID(); - - $view = new View($currentUserId . '/files'); - $fileInfo = $view->getFileInfo($oldPath); - if ($fileInfo) { - $sourceStorage = $fileInfo->getStorage(); - $sourceOwner = $view->getOwner($oldPath); - $targetOwner = $view->getOwner($newPath); - - if ($sourceOwner !== $targetOwner - && $sourceStorage->instanceOfStorage('OCA\Files_Sharing\SharedStorage') - ) { - $fileMovedOutOfSharedFolder = true; - } - } - } - } catch (\Exception $e) { - // do nothing, in this case we just disable the trashbin and continue - \OC::$server->getLogger()->logException($e, [ - 'message' => 'Trashbin storage could not check if a file was moved out of a shared folder.', - 'level' => ILogger::DEBUG, - 'app' => 'files_trashbin', - ]); - } - - if($fileMovedOutOfSharedFolder) { - self::$moveOutOfSharedFolder['/' . $currentUserId . '/files' . $oldPath] = true; - } else { - self::$disableTrash = true; - } - - } - - /** - * @internal - */ - public static function postRenameHook($params) { - self::$disableTrash = false; - } - - /** - * Rename path1 to path2 by calling the wrapped storage. - * - * @param string $path1 first path - * @param string $path2 second path - * @return bool - */ - public function rename($path1, $path2) { - $result = $this->storage->rename($path1, $path2); - if ($result === false) { - // when rename failed, the post_rename hook isn't triggered, - // but we still want to reenable the trash logic - self::$disableTrash = false; - } - return $result; - } - - /** * Deletes the given file by moving it into the trashbin. * * @param string $path path of file or folder to delete @@ -178,22 +95,15 @@ class Storage extends Wrapper { */ public function unlink($path) { try { - if (isset(self::$moveOutOfSharedFolder[$this->mountPoint . $path])) { - $result = $this->doDelete($path, 'unlink', true); - unset(self::$moveOutOfSharedFolder[$this->mountPoint . $path]); - } else { - $result = $this->doDelete($path, 'unlink'); - } + return $this->doDelete($path, 'unlink'); } catch (GenericEncryptionException $e) { // in case of a encryption exception we delete the file right away $this->logger->info( - "Can't move file" . $path . + "Can't move file" . $path . "to the trash bin, therefore it was deleted right away"); - $result = $this->storage->unlink($path); + return $this->storage->unlink($path); } - - return $result; } /** @@ -204,14 +114,7 @@ class Storage extends Wrapper { * @return bool true if the operation succeeded, false otherwise */ public function rmdir($path) { - if (isset(self::$moveOutOfSharedFolder[$this->mountPoint . $path])) { - $result = $this->doDelete($path, 'rmdir', true); - unset(self::$moveOutOfSharedFolder[$this->mountPoint . $path]); - } else { - $result = $this->doDelete($path, 'rmdir'); - } - - return $result; + return $this->doDelete($path, 'rmdir'); } /** @@ -221,7 +124,7 @@ class Storage extends Wrapper { * @param $path * @return bool */ - protected function shouldMoveToTrash($path){ + protected function shouldMoveToTrash($path) { // check if there is a app which want to disable the trash bin for this file $fileId = $this->storage->getCache()->getId($path); @@ -262,17 +165,16 @@ class Storage extends Wrapper { * * @param string $path path of file or folder to delete * @param string $method either "unlink" or "rmdir" - * @param bool $ownerOnly delete for owner only (if file gets moved out of a shared folder) * * @return bool true if the operation succeeded, false otherwise */ - private function doDelete($path, $method, $ownerOnly = false) { - if (self::$disableTrash - || !\OC::$server->getAppManager()->isEnabledForUser('files_trashbin') + private function doDelete($path, $method) { + if ( + !\OC::$server->getAppManager()->isEnabledForUser('files_trashbin') || (pathinfo($path, PATHINFO_EXTENSION) === 'part') || $this->shouldMoveToTrash($path) === false ) { - return call_user_func_array([$this->storage, $method], [$path]); + return call_user_func([$this->storage, $method], $path); } // check permissions before we continue, this is especially important for @@ -281,28 +183,12 @@ class Storage extends Wrapper { return false; } - $normalized = Filesystem::normalizePath($this->mountPoint . '/' . $path, true, false, true); - $result = true; - $view = Filesystem::getView(); - if (!isset($this->deletedFiles[$normalized]) && $view instanceof View) { - $this->deletedFiles[$normalized] = $normalized; - if ($filesPath = $view->getRelativePath($normalized)) { - $filesPath = trim($filesPath, '/'); - $result = \OCA\Files_Trashbin\Trashbin::move2trash($filesPath, $ownerOnly); - // in cross-storage cases the file will be copied - // but not deleted, so we delete it here - if ($result) { - call_user_func_array([$this->storage, $method], [$path]); - } - } else { - $result = call_user_func_array([$this->storage, $method], [$path]); - } - unset($this->deletedFiles[$normalized]); - } else if ($this->storage->file_exists($path)) { - $result = call_user_func_array([$this->storage, $method], [$path]); + $isMovedToTrash = $this->trashManager->moveToTrash($this, $path); + if (!$isMovedToTrash) { + return call_user_func([$this->storage, $method], $path); + } else { + return true; } - - return $result; } /** @@ -311,7 +197,8 @@ class Storage extends Wrapper { public static function setupStorage() { \OC\Files\Filesystem::addStorageWrapper('oc_trashbin', function ($mountPoint, $storage) { return new \OCA\Files_Trashbin\Storage( - array('storage' => $storage, 'mountPoint' => $mountPoint), + ['storage' => $storage, 'mountPoint' => $mountPoint], + \OC::$server->query(ITrashManager::class), \OC::$server->getUserManager(), \OC::$server->getLogger(), \OC::$server->getEventDispatcher(), @@ -320,4 +207,7 @@ class Storage extends Wrapper { }, 1); } + public function getMountPoint() { + return $this->mountPoint; + } } diff --git a/apps/files_trashbin/lib/Trash/BackendNotFoundException.php b/apps/files_trashbin/lib/Trash/BackendNotFoundException.php new file mode 100644 index 00000000000..13289db7ab0 --- /dev/null +++ b/apps/files_trashbin/lib/Trash/BackendNotFoundException.php @@ -0,0 +1,26 @@ +<?php +/** + * @copyright Copyright (c) 2018 Robin Appelman <robin@icewind.nl> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +namespace OCA\Files_Trashbin\Trash; + +class BackendNotFoundException extends \Exception { + +} diff --git a/apps/files_trashbin/lib/Trash/ITrashBackend.php b/apps/files_trashbin/lib/Trash/ITrashBackend.php new file mode 100644 index 00000000000..ebdf9720d7c --- /dev/null +++ b/apps/files_trashbin/lib/Trash/ITrashBackend.php @@ -0,0 +1,84 @@ +<?php +/** + * @copyright Copyright (c) 2018 Robin Appelman <robin@icewind.nl> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +namespace OCA\Files_Trashbin\Trash; + +use OCP\Files\FileInfo; +use OCP\Files\Node; +use OCP\Files\SimpleFS\ISimpleFile; +use OCP\Files\Storage\IStorage; +use OCP\IUser; + +/** + * @since 15.0.0 + */ +interface ITrashBackend { + /** + * List all trash items in the root of the trashbin + * + * @param IUser $user + * @return ITrashItem[] + * @since 15.0.0 + */ + public function listTrashRoot(IUser $user): array; + + /** + * List all trash items in a subfolder in the trashbin + * + * @param ITrashItem $folder + * @return ITrashItem[] + * @since 15.0.0 + */ + public function listTrashFolder(ITrashItem $folder): array; + + /** + * Restore a trashbin item + * + * @param ITrashItem $item + * @since 15.0.0 + */ + public function restoreItem(ITrashItem $item); + + /** + * Permanently remove an item from trash + * + * @param ITrashItem $item + * @since 15.0.0 + */ + public function removeItem(ITrashItem $item); + + /** + * Move a file or folder to trash + * + * @param IStorage $storage + * @param string $internalPath + * @return boolean whether or not the file was moved to trash, if false then the file should be deleted normally + * @since 15.0.0 + */ + public function moveToTrash(IStorage $storage, string $internalPath): bool; + + /** + * @param IUser $user + * @param int $fileId + * @return Node|null + */ + public function getTrashNodeById(IUser $user, int $fileId); +} diff --git a/apps/files_trashbin/lib/Trash/ITrashItem.php b/apps/files_trashbin/lib/Trash/ITrashItem.php new file mode 100644 index 00000000000..74348cf1817 --- /dev/null +++ b/apps/files_trashbin/lib/Trash/ITrashItem.php @@ -0,0 +1,78 @@ +<?php +/** + * @copyright Copyright (c) 2018 Robin Appelman <robin@icewind.nl> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +namespace OCA\Files_Trashbin\Trash; + +use OCP\Files\FileInfo; +use OCP\IUser; + +/** + * @since 15.0.0 + */ +interface ITrashItem extends FileInfo { + /** + * Get the trash backend for this item + * + * @return ITrashBackend + * @since 15.0.0 + */ + public function getTrashBackend(): ITrashBackend; + + /** + * Get the original location for the trash item + * + * @return string + * @since 15.0.0 + */ + public function getOriginalLocation(): string; + + /** + * Get the timestamp that the file was moved to trash + * + * @return int + * @since 15.0.0 + */ + public function getDeletedTime(): int; + + /** + * Get the path of the item relative to the users trashbin + * + * @return string + * @since 15.0.0 + */ + public function getTrashPath(): string; + + /** + * Whether the item is a deleted item in the root of the trash, or a file in a subfolder + * + * @return bool + * @since 15.0.0 + */ + public function isRootItem(): bool; + + /** + * Get the user for which this trash item applies + * + * @return IUser + * @since 15.0.0 + */ + public function getUser(): IUser; +} diff --git a/apps/files_trashbin/lib/Trash/ITrashManager.php b/apps/files_trashbin/lib/Trash/ITrashManager.php new file mode 100644 index 00000000000..fcd51f2d371 --- /dev/null +++ b/apps/files_trashbin/lib/Trash/ITrashManager.php @@ -0,0 +1,56 @@ +<?php +/** + * @copyright Copyright (c) 2018 Robin Appelman <robin@icewind.nl> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +namespace OCA\Files_Trashbin\Trash; + +use OCP\IUser; + +interface ITrashManager extends ITrashBackend { + /** + * Add a backend for the trashbin + * + * @param string $storageType + * @param ITrashBackend $backend + * @since 15.0.0 + */ + public function registerBackend(string $storageType, ITrashBackend $backend); + + /** + * List all trash items in the root of the trashbin + * + * @param IUser $user + * @return ITrashItem[] + * @since 15.0.0 + */ + public function listTrashRoot(IUser $user): array; + + /** + * Temporally prevent files from being moved to the trash + * + * @since 15.0.0 + */ + public function pauseTrash(); + + /** + * @since 15.0.0 + */ + public function resumeTrash(); +} diff --git a/apps/files_trashbin/lib/Trash/LegacyTrashBackend.php b/apps/files_trashbin/lib/Trash/LegacyTrashBackend.php new file mode 100644 index 00000000000..b8519fa27cc --- /dev/null +++ b/apps/files_trashbin/lib/Trash/LegacyTrashBackend.php @@ -0,0 +1,128 @@ +<?php +/** + * @copyright Copyright (c) 2018 Robin Appelman <robin@icewind.nl> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +namespace OCA\Files_Trashbin\Trash; + +use OC\Files\Filesystem; +use OC\Files\View; +use OCA\Files_Trashbin\Helper; +use OCA\Files_Trashbin\Storage; +use OCA\Files_Trashbin\Trashbin; +use OCP\Files\FileInfo; +use OCP\Files\IRootFolder; +use OCP\Files\NotFoundException; +use OCP\Files\Storage\IStorage; +use OCP\IUser; + +class LegacyTrashBackend implements ITrashBackend { + /** @var array */ + private $deletedFiles = []; + + /** @var IRootFolder */ + private $rootFolder; + + public function __construct(IRootFolder $rootFolder) { + $this->rootFolder = $rootFolder; + } + + /** + * @param array $items + * @param IUser $user + * @param ITrashItem $parent + * @return ITrashItem[] + */ + private function mapTrashItems(array $items, IUser $user, ITrashItem $parent = null): array { + $parentTrashPath = ($parent instanceof ITrashItem) ? $parent->getTrashPath() : ''; + $isRoot = $parent === null; + return array_map(function (FileInfo $file) use ($parent, $parentTrashPath, $isRoot, $user) { + return new TrashItem( + $this, + $isRoot ? $file['extraData'] : $parent->getOriginalLocation() . '/' . $file->getName(), + $file->getMTime(), + $parentTrashPath . '/' . $file->getName() . ($isRoot ? '.d' . $file->getMtime() : ''), + $file, + $user + ); + }, $items); + } + + public function listTrashRoot(IUser $user): array { + $entries = Helper::getTrashFiles('/', $user->getUID()); + return $this->mapTrashItems($entries, $user); + } + + public function listTrashFolder(ITrashItem $folder): array { + $user = $folder->getUser(); + $entries = Helper::getTrashFiles($folder->getTrashPath(), $user->getUID()); + return $this->mapTrashItems($entries, $user ,$folder); + } + + public function restoreItem(ITrashItem $item) { + Trashbin::restore($item->getTrashPath(), $item->getName(), $item->isRootItem() ? $item->getDeletedTime() : null); + } + + public function removeItem(ITrashItem $item) { + $user = $item->getUser(); + if ($item->isRootItem()) { + $path = substr($item->getTrashPath(), 0, -strlen('.d' . $item->getDeletedTime())); + Trashbin::delete($path, $user->getUID(), $item->getDeletedTime()); + } else { + Trashbin::delete($item->getTrashPath(), $user->getUID(), null); + } + + } + + public function moveToTrash(IStorage $storage, string $internalPath): bool { + if (!$storage instanceof Storage) { + return false; + } + $normalized = Filesystem::normalizePath($storage->getMountPoint() . '/' . $internalPath, true, false, true); + $view = Filesystem::getView(); + if (!isset($this->deletedFiles[$normalized]) && $view instanceof View) { + $this->deletedFiles[$normalized] = $normalized; + if ($filesPath = $view->getRelativePath($normalized)) { + $filesPath = trim($filesPath, '/'); + $result = \OCA\Files_Trashbin\Trashbin::move2trash($filesPath); + } else { + $result = false; + } + unset($this->deletedFiles[$normalized]); + } else { + $result = false; + } + + return $result; + } + + public function getTrashNodeById(IUser $user, int $fileId) { + try { + $userFolder = $this->rootFolder->getUserFolder($user->getUID()); + $trash = $userFolder->getParent()->get('files_trashbin/files'); + $trashFiles = $trash->getById($fileId); + if (!$trashFiles) { + return null; + } + return $trashFiles ? array_pop($trashFiles) : null; + } catch (NotFoundException $e) { + return null; + } + } +} diff --git a/apps/files_trashbin/lib/Trash/TrashItem.php b/apps/files_trashbin/lib/Trash/TrashItem.php new file mode 100644 index 00000000000..cd7079bcf26 --- /dev/null +++ b/apps/files_trashbin/lib/Trash/TrashItem.php @@ -0,0 +1,172 @@ +<?php +/** + * @copyright Copyright (c) 2018 Robin Appelman <robin@icewind.nl> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +namespace OCA\Files_Trashbin\Trash; + +use OCP\Files\FileInfo; +use OCP\IUser; + +class TrashItem implements ITrashItem { + /** @var ITrashBackend */ + private $backend; + /** @var string */ + private $orignalLocation; + /** @var int */ + private $deletedTime; + /** @var string */ + private $trashPath; + /** @var FileInfo */ + private $fileInfo; + /** @var IUser */ + private $user; + + public function __construct( + ITrashBackend $backend, + string $originalLocation, + int $deletedTime, + string $trashPath, + FileInfo $fileInfo, + IUser $user + ) { + $this->backend = $backend; + $this->orignalLocation = $originalLocation; + $this->deletedTime = $deletedTime; + $this->trashPath = $trashPath; + $this->fileInfo = $fileInfo; + $this->user = $user; + } + + public function getTrashBackend(): ITrashBackend { + return $this->backend; + } + + public function getOriginalLocation(): string { + return $this->orignalLocation; + } + + public function getDeletedTime(): int { + return $this->deletedTime; + } + + public function getTrashPath(): string { + return $this->trashPath; + } + + public function isRootItem(): bool { + return substr_count($this->getTrashPath(), '/') === 1; + } + + public function getUser(): IUser { + return $this->user; + } + + public function getEtag() { + return $this->fileInfo->getEtag(); + } + + public function getSize() { + return $this->fileInfo->getSize(); + } + + public function getMtime() { + return $this->fileInfo->getMtime(); + } + + public function getName() { + return $this->fileInfo->getName(); + } + + public function getInternalPath() { + return $this->fileInfo->getInternalPath(); + } + + public function getPath() { + return $this->fileInfo->getPath(); + } + + public function getMimetype() { + return $this->fileInfo->getMimetype(); + } + + public function getMimePart() { + return $this->fileInfo->getMimePart(); + } + + public function getStorage() { + return $this->fileInfo->getStorage(); + } + + public function getId() { + return $this->fileInfo->getId(); + } + + public function isEncrypted() { + return $this->fileInfo->isEncrypted(); + } + + public function getPermissions() { + return $this->fileInfo->getPermissions(); + } + + public function getType() { + return $this->fileInfo->getType(); + } + + public function isReadable() { + return $this->fileInfo->isReadable(); + } + + public function isUpdateable() { + return $this->fileInfo->isUpdateable(); + } + + public function isCreatable() { + return $this->fileInfo->isCreatable(); + } + + public function isDeletable() { + return $this->fileInfo->isDeletable(); + } + + public function isShareable() { + return $this->fileInfo->isShareable(); + } + + public function isShared() { + return $this->fileInfo->isShared(); + } + + public function isMounted() { + return $this->fileInfo->isMounted(); + } + + public function getMountPoint() { + return $this->fileInfo->getMountPoint(); + } + + public function getOwner() { + return $this->fileInfo->getOwner(); + } + + public function getChecksum() { + return $this->fileInfo->getChecksum(); + } +} diff --git a/apps/files_trashbin/lib/Trash/TrashManager.php b/apps/files_trashbin/lib/Trash/TrashManager.php new file mode 100644 index 00000000000..50ab539c210 --- /dev/null +++ b/apps/files_trashbin/lib/Trash/TrashManager.php @@ -0,0 +1,125 @@ +<?php +/** + * @copyright Copyright (c) 2018 Robin Appelman <robin@icewind.nl> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +namespace OCA\Files_Trashbin\Trash; + +use OCP\Files\FileInfo; +use OCP\Files\Mount\IMountPoint; +use OCP\Files\Storage\IStorage; +use OCP\IUser; + +class TrashManager implements ITrashManager { + /** @var ITrashBackend[] */ + private $backends = []; + + private $trashPaused = false; + + public function registerBackend(string $storageType, ITrashBackend $backend) { + $this->backends[$storageType] = $backend; + } + + /** + * @return ITrashBackend[] + */ + private function getBackends(): array { + return $this->backends; + } + + public function listTrashRoot(IUser $user): array { + $items = array_reduce($this->getBackends(), function (array $items, ITrashBackend $backend) use ($user) { + return array_merge($items, $backend->listTrashRoot($user)); + }, []); + usort($items, function (ITrashItem $a, ITrashItem $b) { + return $a->getDeletedTime() - $b->getDeletedTime(); + }); + return $items; + } + + private function getBackendForItem(ITrashItem $item) { + return $item->getTrashBackend(); + } + + public function listTrashFolder(ITrashItem $folder): array { + return $this->getBackendForItem($folder)->listTrashFolder($folder); + } + + public function restoreItem(ITrashItem $item) { + return $this->getBackendForItem($item)->restoreItem($item); + } + + public function removeItem(ITrashItem $item) { + $this->getBackendForItem($item)->removeItem($item); + } + + /** + * @param IStorage $storage + * @return ITrashBackend + * @throws BackendNotFoundException + */ + public function getBackendForStorage(IStorage $storage): ITrashBackend { + $fullType = get_class($storage); + $foundType = array_reduce(array_keys($this->backends), function ($type, $registeredType) use ($storage) { + if ( + $storage->instanceOfStorage($registeredType) && + ($type === '' || is_subclass_of($registeredType, $type)) + ) { + return $registeredType; + } else { + return $type; + } + }, ''); + if ($foundType === '') { + throw new BackendNotFoundException("Trash backend for $fullType not found"); + } else { + return $this->backends[$foundType]; + } + } + + public function moveToTrash(IStorage $storage, string $internalPath): bool { + if ($this->trashPaused) { + return false; + } + try { + $backend = $this->getBackendForStorage($storage); + return $backend->moveToTrash($storage, $internalPath); + } catch (BackendNotFoundException $e) { + return false; + } + } + + public function getTrashNodeById(IUser $user, int $fileId) { + foreach ($this->backends as $backend) { + $item = $backend->getTrashNodeById($user, $fileId); + if ($item !== null) { + return $item; + } + } + return null; + } + + public function pauseTrash() { + $this->trashPaused = true; + } + + public function resumeTrash() { + $this->trashPaused = false; + } +} diff --git a/apps/files_trashbin/lib/Trashbin.php b/apps/files_trashbin/lib/Trashbin.php index b9a8a42a21c..96c6a4c0af7 100644 --- a/apps/files_trashbin/lib/Trashbin.php +++ b/apps/files_trashbin/lib/Trashbin.php @@ -976,8 +976,6 @@ class Trashbin { \OCP\Util::connectHook('OC_Filesystem', 'post_write', 'OCA\Files_Trashbin\Hooks', 'post_write_hook'); // pre and post-rename, disable trash logic for the copy+unlink case \OCP\Util::connectHook('OC_Filesystem', 'delete', 'OCA\Files_Trashbin\Trashbin', 'ensureFileScannedHook'); - \OCP\Util::connectHook('OC_Filesystem', 'rename', 'OCA\Files_Trashbin\Storage', 'preRenameHook'); - \OCP\Util::connectHook('OC_Filesystem', 'post_rename', 'OCA\Files_Trashbin\Storage', 'postRenameHook'); } /** diff --git a/apps/files_trashbin/tests/Controller/PreviewControllerTest.php b/apps/files_trashbin/tests/Controller/PreviewControllerTest.php index 9ceef07e7d4..02bb63fa17a 100644 --- a/apps/files_trashbin/tests/Controller/PreviewControllerTest.php +++ b/apps/files_trashbin/tests/Controller/PreviewControllerTest.php @@ -23,6 +23,7 @@ namespace OCA\Files_Trashbin\Tests\Controller; use OCA\Files_Trashbin\Controller\PreviewController; +use OCA\Files_Trashbin\Trash\ITrashManager; use OCP\AppFramework\Http; use OCP\AppFramework\Http\DataResponse; use OCP\AppFramework\Http\FileDisplayResponse; @@ -31,14 +32,14 @@ use OCP\Files\File; use OCP\Files\Folder; use OCP\Files\IMimeTypeDetector; use OCP\Files\IRootFolder; -use OCP\Files\NotFoundException; use OCP\Files\SimpleFS\ISimpleFile; use OCP\IPreview; use OCP\IRequest; +use OCP\IUser; +use OCP\IUserSession; use Test\TestCase; class PreviewControllerTest extends TestCase { - /** @var IRootFolder|\PHPUnit_Framework_MockObject_MockObject */ private $rootFolder; @@ -57,6 +58,12 @@ class PreviewControllerTest extends TestCase { /** @var PreviewController */ private $controller; + /** @var ITrashManager|\PHPUnit_Framework_MockObject_MockObject */ + private $trashManager; + + /** @var IUserSession|\PHPUnit_Framework_MockObject_MockObject */ + private $userSession; + public function setUp() { parent::setUp(); @@ -65,12 +72,23 @@ class PreviewControllerTest extends TestCase { $this->mimeTypeDetector = $this->createMock(IMimeTypeDetector::class); $this->previewManager = $this->createMock(IPreview::class); $this->time = $this->createMock(ITimeFactory::class); + $this->trashManager = $this->createMock(ITrashManager::class); + $this->userSession = $this->createMock(IUserSession::class); + $user = $this->createMock(IUser::class); + $user->expects($this->any()) + ->method('getUID') + ->willReturn($this->userId); + + $this->userSession->expects($this->any()) + ->method('getUser') + ->willReturn($user); $this->controller = new PreviewController( 'files_versions', $this->createMock(IRequest::class), $this->rootFolder, - $this->userId, + $this->trashManager, + $this->userSession, $this->mimeTypeDetector, $this->previewManager, $this->time @@ -114,11 +132,15 @@ class PreviewControllerTest extends TestCase { ->with($this->equalTo(42)) ->willReturn([$file]); $file->method('getName') - ->willReturn('file.1234'); + ->willReturn('file.d1234'); $file->method('getParent') ->willReturn($trash); + $this->trashManager->expects($this->any()) + ->method('getTrashNodeById') + ->willReturn($file); + $preview = $this->createMock(ISimpleFile::class); $this->previewManager->method('getPreview') ->with($this->equalTo($file), 10, 10, true, IPreview::MODE_FILL, 'myMime') @@ -177,6 +199,9 @@ class PreviewControllerTest extends TestCase { ->willReturn($trash); $folder = $this->createMock(Folder::class); + $this->trashManager->expects($this->any()) + ->method('getTrashNodeById') + ->willReturn($folder); $trash->method('getById') ->with($this->equalTo(43)) ->willReturn([$folder]); diff --git a/apps/files_trashbin/tests/StorageTest.php b/apps/files_trashbin/tests/StorageTest.php index 67e622aa1c9..058d64d1aa0 100644 --- a/apps/files_trashbin/tests/StorageTest.php +++ b/apps/files_trashbin/tests/StorageTest.php @@ -34,6 +34,7 @@ use OC\Files\Storage\Temporary; use OC\Files\Filesystem; use OCA\Files_Trashbin\Events\MoveToTrashEvent; use OCA\Files_Trashbin\Storage; +use OCA\Files_Trashbin\Trash\ITrashManager; use OCP\Files\Cache\ICache; use OCP\Files\IRootFolder; use OCP\Files\Node; @@ -546,6 +547,7 @@ class StorageTest extends \Test\TestCase { ->disableOriginalConstructor()->getMock(); $rootFolder = $this->createMock(IRootFolder::class); $node = $this->getMockBuilder(Node::class)->disableOriginalConstructor()->getMock(); + $trashManager = $this->createMock(ITrashManager::class); $event = $this->getMockBuilder(MoveToTrashEvent::class)->disableOriginalConstructor()->getMock(); $event->expects($this->any())->method('shouldMoveToTrashBin')->willReturn(!$appDisablesTrash); @@ -555,6 +557,7 @@ class StorageTest extends \Test\TestCase { ->setConstructorArgs( [ ['mountPoint' => $mountPoint, 'storage' => $tmpStorage], + $trashManager, $userManager, $logger, $eventDispatcher, |