Merge pull request #11439 from nextcloud/trash-modular-api

Modular trashbin api
This commit is contained in:
Morris Jobke 2018-10-23 17:11:16 +02:00 committed by GitHub
commit 4ad27260a9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
33 changed files with 1076 additions and 486 deletions

View File

@ -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>

View File

@ -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',
);

View File

@ -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',
);

View File

@ -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);
}
}
}
}
}
}

View File

@ -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

View File

@ -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;
}
}

View File

@ -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();
}
}

View File

@ -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();
}
}

View File

@ -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();
}

View File

@ -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 {

View File

@ -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();
}
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'];
}
}

View File

@ -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'];
}
}

View File

@ -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();
}
}

View File

@ -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);
}
public function getOriginalLocation(): string {
return $this->location . '/' . $this->getFilename();
}
class TrashFolderFolder extends AbstractTrashFolder {
}

View File

@ -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)
];
}

View File

@ -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 getChildren(): array {
$entries = $this->trashManager->listTrashRoot($this->user);
$children = array_map(function (ITrashItem $entry) {
if ($entry->getType() === FileInfo::TYPE_FOLDER) {
return new TrashFolder($this->trashManager, $entry);
}
return new TrashFile($this->trashManager, $entry);
}, $entries);
return $children;
}
public function getChild($name): ITrash {
$entries = \OCA\Files_Trashbin\Helper::getTrashFiles('/', $this->userId);
$entries = $this->getChildren();
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);
if ($entry->getName() === $name) {
return $entry;
}
}
throw new NotFound();
}
public function getChildren(): array {
$entries = \OCA\Files_Trashbin\Helper::getTrashFiles('/', $this->userId);
$children = array_map(function (FileInfo $entry) {
if ($entry->getType() === FileInfo::TYPE_FOLDER) {
return new TrashFolder('/', $this->userId, $entry);
}
return new TrashFile($this->userId, $entry);
}, $entries);
return $children;
}
public function childExists($name): bool {
$entries = \OCA\Files_Trashbin\Helper::getTrashFiles('/', $this->userId);
foreach ($entries as $entry) {
if ($entry->getName() . '.d'.$entry->getMtime() === $name) {
return true;
}
try {
$this->getChild($name);
return true;
} catch (NotFound $e) {
return false;
}
return false;
}
public function getLastModified(): int {

View File

@ -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;
@ -94,81 +86,6 @@ class Storage extends Wrapper {
parent::__construct($parameters);
}
/**
* @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.
*
@ -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;
}
}

View File

@ -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 {
}

View File

@ -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);
}

View File

@ -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;
}

View File

@ -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();
}

View File

@ -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;
}
}
}

View File

@ -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();
}
}

View File

@ -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;
}
}

View File

@ -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');
}
/**

View File

@ -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]);

View File

@ -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,

View File

@ -187,6 +187,12 @@ class CacheWrapper extends Cache {
$this->getCache()->move($source, $target);
}
protected function getMoveInfo($path) {
/** @var Cache $cache */
$cache = $this->getCache();
return $cache->getMoveInfo($path);
}
public function moveFromCache(ICache $sourceCache, $sourcePath, $targetPath) {
$this->getCache()->moveFromCache($sourceCache, $sourcePath, $targetPath);
}

View File

@ -175,7 +175,7 @@ class FileInfo implements \OCP\Files\FileInfo, \ArrayAccess {
* @return string
*/
public function getName() {
return basename($this->getPath());
return isset($this->data['name']) ? $this->data['name'] : basename($this->getPath());
}
/**

View File

@ -65,6 +65,7 @@ use OC\Files\Storage\StorageFactory;
use OC\Lockdown\Filesystem\NullStorage;
use OCP\Files\Config\IMountProvider;
use OCP\Files\NotFoundException;
use OCP\Files\Storage\IStorageFactory;
use OCP\ILogger;
use OCP\IUserManager;
@ -246,11 +247,11 @@ class Filesystem {
/**
* Returns the storage factory
*
* @return \OCP\Files\Storage\IStorageFactory
* @return IStorageFactory
*/
public static function getLoader() {
if (!self::$loader) {
self::$loader = new StorageFactory();
self::$loader = \OC::$server->query(IStorageFactory::class);
}
return self::$loader;
}

View File

@ -80,6 +80,7 @@ use OC\Files\Mount\ObjectHomeMountProvider;
use OC\Files\Node\HookConnector;
use OC\Files\Node\LazyRoot;
use OC\Files\Node\Root;
use OC\Files\Storage\StorageFactory;
use OC\Files\View;
use OC\Http\Client\ClientService;
use OC\IntegrityCheck\Checker;
@ -135,6 +136,7 @@ use OCP\Federation\ICloudFederationProviderManager;
use OCP\Federation\ICloudIdManager;
use OCP\Authentication\LoginCredentials\IStore;
use OCP\Files\NotFoundException;
use OCP\Files\Storage\IStorageFactory;
use OCP\GlobalScale\IConfig;
use OCP\ICacheFactory;
use OCP\IDBConnection;
@ -1174,6 +1176,10 @@ class Server extends ServerContainer implements IServerContainer {
});
$this->registerAlias(IContactsStore::class, ContactsStore::class);
$this->registerService(IStorageFactory::class, function() {
return new StorageFactory();
});
$this->registerAlias(IDashboardManager::class, Dashboard\DashboardManager::class);
$this->connectDispatcher();
@ -2024,4 +2030,11 @@ class Server extends ServerContainer implements IServerContainer {
public function getRemoteInstanceFactory() {
return $this->query(IInstanceFactory::class);
}
/**
* @return IStorageFactory
*/
public function getStorageFactory() {
return $this->query(IStorageFactory::class);
}
}

View File

@ -584,4 +584,10 @@ interface IServerContainer extends IContainer {
* @since 13.0.0
*/
public function getRemoteInstanceFactory();
/**
* @return \OCP\Files\Storage\IStorageFactory
* @since 15.0.0
*/
public function getStorageFactory();
}

View File

@ -61,6 +61,8 @@
maxOccurs="1" />
<xs:element name="sabre" type="sabre" minOccurs="0"
maxOccurs="1" />
<xs:element name="trash" type="trash" minOccurs="0"
maxOccurs="1" />
</xs:sequence>
</xs:complexType>
<xs:unique name="uniqueNameL10n">
@ -653,11 +655,25 @@
</xs:sequence>
</xs:complexType>
<xs:complexType name="trash">
<xs:sequence>
<xs:element name="backend" type="trash-backend" minOccurs="1"
maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="trash-backend">
<xs:simpleContent>
<xs:extension base="php-class">
<xs:attribute name="for" type="php-class" use="required"/>
</xs:extension>
</xs:simpleContent>
</xs:complexType>
<xs:simpleType name="php-class">
<xs:restriction base="xs:string">
<xs:pattern
value="[a-zA-Z_][0-9a-zA-Z_]*(\\[a-zA-Z_][0-9a-zA-Z_]*)*"/>
</xs:restriction>
</xs:simpleType>
</xs:schema>