migrate therinaldos.com data
Build & Deploy to DigitalOcean Space / build (push) Failing after 2m38s

This commit is contained in:
2024-05-05 15:50:45 -04:00
commit ef1ff240d4
23182 changed files with 3801898 additions and 0 deletions
@@ -0,0 +1,99 @@
<?php
/**
* @copyright Copyright (c) 2016, ownCloud, Inc.
*
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Morris Jobke <hey@morrisjobke.de>
* @author Robin Appelman <robin@icewind.nl>
* @author Roeland Jago Douma <roeland@famdouma.nl>
* @author Victor Dubiniuk <dubiniuk@owncloud.com>
*
* @license AGPL-3.0
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* 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, version 3,
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
namespace OCA\Files_Trashbin\AppInfo;
use OCA\DAV\Connector\Sabre\Principal;
use OCA\Files\Event\LoadAdditionalScriptsEvent;
use OCA\Files_Trashbin\Capabilities;
use OCA\Files_Trashbin\Expiration;
use OCA\Files_Trashbin\Listeners\LoadAdditionalScripts;
use OCA\Files_Trashbin\Trash\ITrashManager;
use OCA\Files_Trashbin\Trash\TrashManager;
use OCA\Files_Trashbin\UserMigration\TrashbinMigrator;
use OCP\App\IAppManager;
use OCP\AppFramework\App;
use OCP\AppFramework\Bootstrap\IBootContext;
use OCP\AppFramework\Bootstrap\IBootstrap;
use OCP\AppFramework\Bootstrap\IRegistrationContext;
use OCP\ILogger;
use OCP\IServerContainer;
class Application extends App implements IBootstrap {
public const APP_ID = 'files_trashbin';
public function __construct(array $urlParams = []) {
parent::__construct(self::APP_ID, $urlParams);
}
public function register(IRegistrationContext $context): void {
$context->registerCapability(Capabilities::class);
$context->registerServiceAlias('Expiration', Expiration::class);
$context->registerServiceAlias(ITrashManager::class, TrashManager::class);
/** Register $principalBackend for the DAV collection */
$context->registerServiceAlias('principalBackend', Principal::class);
$context->registerUserMigrator(TrashbinMigrator::class);
$context->registerEventListener(
LoadAdditionalScriptsEvent::class,
LoadAdditionalScripts::class
);
}
public function boot(IBootContext $context): void {
$context->injectFn([$this, 'registerTrashBackends']);
// create storage wrapper on setup
\OCP\Util::connectHook('OC_Filesystem', 'preSetup', 'OCA\Files_Trashbin\Storage', 'setupStorage');
//Listen to delete user signal
\OCP\Util::connectHook('OC_User', 'pre_deleteUser', 'OCA\Files_Trashbin\Hooks', 'deleteUser_hook');
//Listen to post write hook
\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');
}
public function registerTrashBackends(IServerContainer $serverContainer, ILogger $logger, IAppManager $appManager, ITrashManager $trashManager) {
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 = $serverContainer->query($class);
$trashManager->registerBackend($for, $backendObject);
} catch (\Exception $e) {
$logger->logException($e);
}
}
}
}
}
}
@@ -0,0 +1,100 @@
<?php
/**
* @copyright Copyright (c) 2016, ownCloud, Inc.
*
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Joas Schilling <coding@schilljs.com>
* @author Jörn Friedrich Dreyer <jfd@butonic.de>
* @author Lukas Reschke <lukas@statuscode.ch>
* @author Roeland Jago Douma <roeland@famdouma.nl>
* @author Victor Dubiniuk <dubiniuk@owncloud.com>
*
* @license AGPL-3.0
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* 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, version 3,
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
namespace OCA\Files_Trashbin\BackgroundJob;
use OCA\Files_Trashbin\Expiration;
use OCA\Files_Trashbin\Helper;
use OCA\Files_Trashbin\Trashbin;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\BackgroundJob\TimedJob;
use OCP\IConfig;
use OCP\IUser;
use OCP\IUserManager;
class ExpireTrash extends TimedJob {
private IConfig $config;
private Expiration $expiration;
private IUserManager $userManager;
public function __construct(
IConfig $config,
IUserManager $userManager,
Expiration $expiration,
ITimeFactory $time
) {
parent::__construct($time);
// Run once per 30 minutes
$this->setInterval(60 * 30);
$this->config = $config;
$this->userManager = $userManager;
$this->expiration = $expiration;
}
/**
* @param $argument
* @throws \Exception
*/
protected function run($argument) {
$backgroundJob = $this->config->getAppValue('files_trashbin', 'background_job_expire_trash', 'yes');
if ($backgroundJob === 'no') {
return;
}
$maxAge = $this->expiration->getMaxAgeAsTimestamp();
if (!$maxAge) {
return;
}
$this->userManager->callForSeenUsers(function (IUser $user) {
$uid = $user->getUID();
if (!$this->setupFS($uid)) {
return;
}
$dirContent = Helper::getTrashFiles('/', $uid, 'mtime');
Trashbin::deleteExpiredFiles($dirContent, $uid);
});
\OC_Util::tearDownFS();
}
/**
* Act on behalf on trash item owner
*/
protected function setupFS(string $user): bool {
\OC_Util::tearDownFS();
\OC_Util::setupFS($user);
// Check if this user has a trashbin directory
$view = new \OC\Files\View('/' . $user);
if (!$view->is_dir('/files_trashbin/files')) {
return false;
}
return true;
}
}
@@ -0,0 +1,46 @@
<?php
/**
* @copyright Copyright (c) 2016, ownCloud, Inc.
*
* @author Lukas Reschke <lukas@statuscode.ch>
* @author Roeland Jago Douma <roeland@famdouma.nl>
*
* @license AGPL-3.0
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* 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, version 3,
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
namespace OCA\Files_Trashbin;
use OCP\Capabilities\ICapability;
/**
* Class Capabilities
*
* @package OCA\Files_Trashbin
*/
class Capabilities implements ICapability {
/**
* Return this classes capabilities
*
* @return array{files: array{undelete: bool}}
*/
public function getCapabilities() {
return [
'files' => [
'undelete' => true
]
];
}
}
@@ -0,0 +1,148 @@
<?php
/**
* @copyright Copyright (c) 2016, ownCloud, Inc.
*
* @author Björn Schießle <bjoern@schiessle.org>
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Joas Schilling <coding@schilljs.com>
* @author Liam Dennehy <liam@wiemax.net>
* @author Roeland Jago Douma <roeland@famdouma.nl>
*
* @license AGPL-3.0
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* 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, version 3,
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
namespace OCA\Files_Trashbin\Command;
use OCP\Files\IRootFolder;
use OCP\IDBConnection;
use OCP\IUserBackend;
use OCP\IUserManager;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Exception\InvalidOptionException;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
class CleanUp extends Command {
/** @var IUserManager */
protected $userManager;
/** @var IRootFolder */
protected $rootFolder;
/** @var \OCP\IDBConnection */
protected $dbConnection;
/**
* @param IRootFolder $rootFolder
* @param IUserManager $userManager
* @param IDBConnection $dbConnection
*/
public function __construct(IRootFolder $rootFolder, IUserManager $userManager, IDBConnection $dbConnection) {
parent::__construct();
$this->userManager = $userManager;
$this->rootFolder = $rootFolder;
$this->dbConnection = $dbConnection;
}
protected function configure() {
$this
->setName('trashbin:cleanup')
->setDescription('Remove deleted files')
->addArgument(
'user_id',
InputArgument::OPTIONAL | InputArgument::IS_ARRAY,
'remove deleted files of the given user(s)'
)
->addOption(
'all-users',
null,
InputOption::VALUE_NONE,
'run action on all users'
);
}
protected function execute(InputInterface $input, OutputInterface $output): int {
$users = $input->getArgument('user_id');
$verbose = $input->getOption('verbose');
if ((!empty($users)) and ($input->getOption('all-users'))) {
throw new InvalidOptionException('Either specify a user_id or --all-users');
} elseif (!empty($users)) {
foreach ($users as $user) {
if ($this->userManager->userExists($user)) {
$output->writeln("Remove deleted files of <info>$user</info>");
$this->removeDeletedFiles($user, $output, $verbose);
} else {
$output->writeln("<error>Unknown user $user</error>");
return 1;
}
}
} elseif ($input->getOption('all-users')) {
$output->writeln('Remove deleted files for all users');
foreach ($this->userManager->getBackends() as $backend) {
$name = get_class($backend);
if ($backend instanceof IUserBackend) {
$name = $backend->getBackendName();
}
$output->writeln("Remove deleted files for users on backend <info>$name</info>");
$limit = 500;
$offset = 0;
do {
$users = $backend->getUsers('', $limit, $offset);
foreach ($users as $user) {
$output->writeln(" <info>$user</info>");
$this->removeDeletedFiles($user, $output, $verbose);
}
$offset += $limit;
} while (count($users) >= $limit);
}
} else {
throw new InvalidOptionException('Either specify a user_id or --all-users');
}
return 0;
}
/**
* remove deleted files for the given user
*/
protected function removeDeletedFiles(string $uid, OutputInterface $output, bool $verbose): void {
\OC_Util::tearDownFS();
\OC_Util::setupFS($uid);
$path = '/' . $uid . '/files_trashbin';
if ($this->rootFolder->nodeExists($path)) {
$node = $this->rootFolder->get($path);
if ($verbose) {
$output->writeln("Deleting <info>" . \OC_Helper::humanFileSize($node->getSize()) . "</info> in trash for <info>$uid</info>.");
}
$node->delete();
if ($this->rootFolder->nodeExists($path)) {
$output->writeln("<error>Trash folder sill exists after attempting to delete it</error>");
return;
}
$query = $this->dbConnection->getQueryBuilder();
$query->delete('files_trash')
->where($query->expr()->eq('user', $query->createParameter('uid')))
->setParameter('uid', $uid);
$query->execute();
} else {
if ($verbose) {
$output->writeln("No trash found for <info>$uid</info>");
}
}
}
}
@@ -0,0 +1,60 @@
<?php
/**
* @copyright Copyright (c) 2016, ownCloud, Inc.
*
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Joas Schilling <coding@schilljs.com>
* @author Morris Jobke <hey@morrisjobke.de>
* @author Robin Appelman <robin@icewind.nl>
* @author Thomas Müller <thomas.mueller@tmit.eu>
* @author Vincent Petry <vincent@nextcloud.com>
*
* @license AGPL-3.0
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* 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, version 3,
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
namespace OCA\Files_Trashbin\Command;
use OC\Command\FileAccess;
use OCA\Files_Trashbin\Trashbin;
use OCP\Command\ICommand;
class Expire implements ICommand {
use FileAccess;
/**
* @var string
*/
private $user;
/**
* @param string $user
*/
public function __construct($user) {
$this->user = $user;
}
public function handle() {
$userManager = \OC::$server->getUserManager();
if (!$userManager->userExists($this->user)) {
// User has been deleted already
return;
}
\OC_Util::tearDownFS();
\OC_Util::setupFS($this->user);
Trashbin::expire($this->user);
\OC_Util::tearDownFS();
}
}
@@ -0,0 +1,132 @@
<?php
/**
* @copyright Copyright (c) 2016, ownCloud GmbH.
*
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Joas Schilling <coding@schilljs.com>
* @author Jörn Friedrich Dreyer <jfd@butonic.de>
* @author Roeland Jago Douma <roeland@famdouma.nl>
* @author Thomas Müller <thomas.mueller@tmit.eu>
*
* @license AGPL-3.0
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* 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, version 3,
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
namespace OCA\Files_Trashbin\Command;
use OCA\Files_Trashbin\Expiration;
use OCA\Files_Trashbin\Helper;
use OCA\Files_Trashbin\Trashbin;
use OCP\IUser;
use OCP\IUserManager;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Helper\ProgressBar;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class ExpireTrash extends Command {
/**
* @var Expiration
*/
private $expiration;
/**
* @var IUserManager
*/
private $userManager;
/**
* @param IUserManager|null $userManager
* @param Expiration|null $expiration
*/
public function __construct(IUserManager $userManager = null,
Expiration $expiration = null) {
parent::__construct();
$this->userManager = $userManager;
$this->expiration = $expiration;
}
protected function configure() {
$this
->setName('trashbin:expire')
->setDescription('Expires the users trashbin')
->addArgument(
'user_id',
InputArgument::OPTIONAL | InputArgument::IS_ARRAY,
'expires the trashbin of the given user(s), if no user is given the trash for all users will be expired'
);
}
protected function execute(InputInterface $input, OutputInterface $output): int {
$maxAge = $this->expiration->getMaxAgeAsTimestamp();
if (!$maxAge) {
$output->writeln("Auto expiration is configured - keeps files and folders in the trash bin for 30 days and automatically deletes anytime after that if space is needed (note: files may not be deleted if space is not needed)");
return 1;
}
$users = $input->getArgument('user_id');
if (!empty($users)) {
foreach ($users as $user) {
if ($this->userManager->userExists($user)) {
$output->writeln("Remove deleted files of <info>$user</info>");
$userObject = $this->userManager->get($user);
$this->expireTrashForUser($userObject);
} else {
$output->writeln("<error>Unknown user $user</error>");
return 1;
}
}
} else {
$p = new ProgressBar($output);
$p->start();
$this->userManager->callForSeenUsers(function (IUser $user) use ($p) {
$p->advance();
$this->expireTrashForUser($user);
});
$p->finish();
$output->writeln('');
}
return 0;
}
public function expireTrashForUser(IUser $user) {
$uid = $user->getUID();
if (!$this->setupFS($uid)) {
return;
}
$dirContent = Helper::getTrashFiles('/', $uid, 'mtime');
Trashbin::deleteExpiredFiles($dirContent, $uid);
}
/**
* Act on behalf on trash item owner
* @param string $user
* @return boolean
*/
protected function setupFS($user) {
\OC_Util::tearDownFS();
\OC_Util::setupFS($user);
// Check if this user has a trashbin directory
$view = new \OC\Files\View('/' . $user);
if (!$view->is_dir('/files_trashbin/files')) {
return false;
}
return true;
}
}
@@ -0,0 +1,293 @@
<?php
/**
* @copyright Copyright (c) 2021, Caitlin Hogan (cahogan16@gmail.com)
* @license AGPL-3.0
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* 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, version 3,
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
namespace OCA\Files_Trashbin\Command;
use OC\Core\Command\Base;
use OCA\Files_Trashbin\Trash\ITrashManager;
use OCP\Files\IRootFolder;
use OCP\IDBConnection;
use OCP\IL10N;
use OCP\IUserBackend;
use OCP\IUserManager;
use OCP\L10N\IFactory;
use Symfony\Component\Console\Exception\InvalidOptionException;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
class RestoreAllFiles extends Base {
private const SCOPE_ALL = 0;
private const SCOPE_USER = 1;
private const SCOPE_GROUPFOLDERS = 2;
private static array $SCOPE_MAP = [
'user' => self::SCOPE_USER,
'groupfolders' => self::SCOPE_GROUPFOLDERS,
'all' => self::SCOPE_ALL
];
/** @var IUserManager */
protected $userManager;
/** @var IRootFolder */
protected $rootFolder;
/** @var \OCP\IDBConnection */
protected $dbConnection;
protected ITrashManager $trashManager;
/** @var IL10N */
protected $l10n;
/**
* @param IRootFolder $rootFolder
* @param IUserManager $userManager
* @param IDBConnection $dbConnection
* @param ITrashManager $trashManager
* @param IFactory $l10nFactory
*/
public function __construct(IRootFolder $rootFolder, IUserManager $userManager, IDBConnection $dbConnection, ITrashManager $trashManager, IFactory $l10nFactory) {
parent::__construct();
$this->userManager = $userManager;
$this->rootFolder = $rootFolder;
$this->dbConnection = $dbConnection;
$this->trashManager = $trashManager;
$this->l10n = $l10nFactory->get('files_trashbin');
}
protected function configure(): void {
parent::configure();
$this
->setName('trashbin:restore')
->setDescription('Restore all deleted files according to the given filters')
->addArgument(
'user_id',
InputArgument::OPTIONAL | InputArgument::IS_ARRAY,
'restore all deleted files of the given user(s)'
)
->addOption(
'all-users',
null,
InputOption::VALUE_NONE,
'run action on all users'
)
->addOption(
'scope',
's',
InputOption::VALUE_OPTIONAL,
'Restore files from the given scope. Possible values are "user", "groupfolders" or "all"',
'user'
)
->addOption(
'since',
null,
InputOption::VALUE_OPTIONAL,
'Only restore files deleted after the given timestamp'
)
->addOption(
'until',
null,
InputOption::VALUE_OPTIONAL,
'Only restore files deleted before the given timestamp'
)
->addOption(
'dry-run',
'd',
InputOption::VALUE_NONE,
'Only show which files would be restored but do not perform any action'
);
}
protected function execute(InputInterface $input, OutputInterface $output): int {
/** @var string[] $users */
$users = $input->getArgument('user_id');
if ((!empty($users)) && ($input->getOption('all-users'))) {
throw new InvalidOptionException('Either specify a user_id or --all-users');
}
[$scope, $since, $until, $dryRun] = $this->parseArgs($input);
if (!empty($users)) {
foreach ($users as $user) {
$output->writeln("Restoring deleted files for user <info>$user</info>");
$this->restoreDeletedFiles($user, $scope, $since, $until, $dryRun, $output);
}
} elseif ($input->getOption('all-users')) {
$output->writeln('Restoring deleted files for all users');
foreach ($this->userManager->getBackends() as $backend) {
$name = get_class($backend);
if ($backend instanceof IUserBackend) {
$name = $backend->getBackendName();
}
$output->writeln("Restoring deleted files for users on backend <info>$name</info>");
$limit = 500;
$offset = 0;
do {
$users = $backend->getUsers('', $limit, $offset);
foreach ($users as $user) {
$output->writeln("<info>$user</info>");
$this->restoreDeletedFiles($user, $scope, $since, $until, $dryRun, $output);
}
$offset += $limit;
} while (count($users) >= $limit);
}
} else {
throw new InvalidOptionException('Either specify a user_id or --all-users');
}
return 0;
}
/**
* Restore deleted files for the given user according to the given filters
*/
protected function restoreDeletedFiles(string $uid, int $scope, ?int $since, ?int $until, bool $dryRun, OutputInterface $output): void {
\OC_Util::tearDownFS();
\OC_Util::setupFS($uid);
\OC_User::setUserId($uid);
$user = $this->userManager->get($uid);
if ($user === null) {
$output->writeln("<error>Unknown user $uid</error>");
return;
}
$userTrashItems = $this->filterTrashItems(
$this->trashManager->listTrashRoot($user),
$scope,
$since,
$until,
$output);
$trashCount = count($userTrashItems);
if ($trashCount == 0) {
$output->writeln("User has no deleted files in the trashbin matching the given filters");
return;
}
$prepMsg = $dryRun ? 'Would restore' : 'Preparing to restore';
$output->writeln("$prepMsg <info>$trashCount</info> files...");
$count = 0;
foreach($userTrashItems as $trashItem) {
$filename = $trashItem->getName();
$humanTime = $this->l10n->l('datetime', $trashItem->getDeletedTime());
// We use getTitle() here instead of getOriginalLocation() because
// for groupfolders this contains the groupfolder name itself as prefix
// which makes it more human readable
$location = $trashItem->getTitle();
if ($dryRun) {
$output->writeln("Would restore <info>$filename</info> originally deleted at <info>$humanTime</info> to <info>/$location</info>");
continue;
}
$output->write("File <info>$filename</info> originally deleted at <info>$humanTime</info> restoring to <info>/$location</info>:");
try {
$trashItem->getTrashBackend()->restoreItem($trashItem);
} catch (\Throwable $e) {
$output->writeln(" <error>Failed: " . $e->getMessage() . "</error>");
$output->writeln(" <error>" . $e->getTraceAsString() . "</error>", OutputInterface::VERBOSITY_VERY_VERBOSE);
continue;
}
$count++;
$output->writeln(" <info>success</info>");
}
if (!$dryRun) {
$output->writeln("Successfully restored <info>$count</info> out of <info>$trashCount</info> files.");
}
}
protected function parseArgs(InputInterface $input): array {
$since = $this->parseTimestamp($input->getOption('since'));
$until = $this->parseTimestamp($input->getOption('until'));
if ($since !== null && $until !== null && $since > $until) {
throw new InvalidOptionException('since must be before until');
}
return [
$this->parseScope($input->getOption('scope')),
$since,
$until,
$input->getOption('dry-run')
];
}
protected function parseScope(string $scope): int {
if (isset(self::$SCOPE_MAP[$scope])) {
return self::$SCOPE_MAP[$scope];
}
throw new InvalidOptionException("Invalid scope '$scope'");
}
protected function parseTimestamp(?string $timestamp): ?int {
if ($timestamp === null) {
return null;
}
$timestamp = strtotime($timestamp);
if ($timestamp === false) {
throw new InvalidOptionException("Invalid timestamp '$timestamp'");
}
return $timestamp;
}
protected function filterTrashItems(array $trashItems, int $scope, ?int $since, ?int $until, OutputInterface $output): array {
$filteredTrashItems = [];
foreach ($trashItems as $trashItem) {
$trashItemClass = get_class($trashItem);
// Check scope with exact class name for locally deleted files
if ($scope === self::SCOPE_USER && $trashItemClass !== \OCA\Files_Trashbin\Trash\TrashItem::class) {
$output->writeln("Skipping <info>" . $trashItem->getName() . "</info> because it is not a user trash item", OutputInterface::VERBOSITY_VERBOSE);
continue;
}
/**
* Check scope for groupfolders by string because the groupfolders app might not be installed.
* That's why PSALM doesn't know the class GroupTrashItem.
* @psalm-suppress RedundantCondition
*/
if ($scope === self::SCOPE_GROUPFOLDERS && $trashItemClass !== 'OCA\GroupFolders\Trash\GroupTrashItem') {
$output->writeln("Skipping <info>" . $trashItem->getName() . "</info> because it is not a groupfolders trash item", OutputInterface::VERBOSITY_VERBOSE);
continue;
}
// Check left timestamp boundary
if ($since !== null && $trashItem->getDeletedTime() <= $since) {
$output->writeln("Skipping <info>" . $trashItem->getName() . "</info> because it was deleted before the 'since' timestamp", OutputInterface::VERBOSITY_VERBOSE);
continue;
}
// Check right timestamp boundary
if ($until !== null && $trashItem->getDeletedTime() >= $until) {
$output->writeln("Skipping <info>" . $trashItem->getName() . "</info> because it was deleted after the 'until' timestamp", OutputInterface::VERBOSITY_VERBOSE);
continue;
}
$filteredTrashItems[] = $trashItem;
}
return $filteredTrashItems;
}
}
@@ -0,0 +1,148 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2020 Robin Appelman <robin@icewind.nl>
*
* @author 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\Command;
use OC\Core\Command\Base;
use OCP\Command\IBus;
use OCP\IConfig;
use OCP\IUser;
use OCP\IUserManager;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
class Size extends Base {
private $config;
private $userManager;
private $commandBus;
public function __construct(
IConfig $config,
IUserManager $userManager,
IBus $commandBus
) {
parent::__construct();
$this->config = $config;
$this->userManager = $userManager;
$this->commandBus = $commandBus;
}
protected function configure() {
parent::configure();
$this
->setName('trashbin:size')
->setDescription('Configure the target trashbin size')
->addOption('user', 'u', InputOption::VALUE_REQUIRED, 'configure the target size for the provided user, if no user is given the default size is configured')
->addArgument(
'size',
InputArgument::OPTIONAL,
'the target size for the trashbin, if not provided the current trashbin size will be returned'
);
}
protected function execute(InputInterface $input, OutputInterface $output): int {
$user = $input->getOption('user');
$size = $input->getArgument('size');
if ($size) {
$parsedSize = \OC_Helper::computerFileSize($size);
if ($parsedSize === false) {
$output->writeln("<error>Failed to parse input size</error>");
return -1;
}
if ($user) {
$this->config->setUserValue($user, 'files_trashbin', 'trashbin_size', (string)$parsedSize);
$this->commandBus->push(new Expire($user));
} else {
$this->config->setAppValue('files_trashbin', 'trashbin_size', (string)$parsedSize);
$output->writeln("<info>Warning: changing the default trashbin size will automatically trigger cleanup of existing trashbins,</info>");
$output->writeln("<info>a users trashbin can exceed the configured size until they move a new file to the trashbin.</info>");
}
} else {
$this->printTrashbinSize($input, $output, $user);
}
return 0;
}
private function printTrashbinSize(InputInterface $input, OutputInterface $output, ?string $user) {
$globalSize = (int)$this->config->getAppValue('files_trashbin', 'trashbin_size', '-1');
if ($globalSize < 0) {
$globalHumanSize = "default (50% of available space)";
} else {
$globalHumanSize = \OC_Helper::humanFileSize($globalSize);
}
if ($user) {
$userSize = (int)$this->config->getUserValue($user, 'files_trashbin', 'trashbin_size', '-1');
if ($userSize < 0) {
$userHumanSize = ($globalSize < 0) ? $globalHumanSize : "default($globalHumanSize)";
} else {
$userHumanSize = \OC_Helper::humanFileSize($userSize);
}
if ($input->getOption('output') == self::OUTPUT_FORMAT_PLAIN) {
$output->writeln($userHumanSize);
} else {
$userValue = ($userSize < 0) ? 'default' : $userSize;
$globalValue = ($globalSize < 0) ? 'default' : $globalSize;
$this->writeArrayInOutputFormat($input, $output, [
'user_size' => $userValue,
'global_size' => $globalValue,
'effective_size' => ($userSize < 0) ? $globalValue : $userValue,
]);
}
} else {
$users = [];
$this->userManager->callForSeenUsers(function (IUser $user) use (&$users) {
$users[] = $user->getUID();
});
$userValues = $this->config->getUserValueForUsers('files_trashbin', 'trashbin_size', $users);
if ($input->getOption('output') == self::OUTPUT_FORMAT_PLAIN) {
$output->writeln("Default size: $globalHumanSize");
$output->writeln("");
if (count($userValues)) {
$output->writeln("Per-user sizes:");
$this->writeArrayInOutputFormat($input, $output, array_map(function ($size) {
return \OC_Helper::humanFileSize($size);
}, $userValues));
} else {
$output->writeln("No per-user sizes configured");
}
} else {
$globalValue = ($globalSize < 0) ? 'default' : $globalSize;
$this->writeArrayInOutputFormat($input, $output, [
'global_size' => $globalValue,
'user_sizes' => $userValues,
]);
}
}
}
}
@@ -0,0 +1,145 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2016, Roeland Jago Douma <roeland@famdouma.nl>
*
* @author Jakub Onderka <ahoj@jakubonderka.cz>
* @author Morris Jobke <hey@morrisjobke.de>
* @author Robin Appelman <robin@icewind.nl>
* @author Roeland Jago Douma <roeland@famdouma.nl>
* @author simonspa <1677436+simonspa@users.noreply.github.com>
*
* @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\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\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 ITrashManager */
private $trashManager;
/** @var IUserSession */
private $userSession;
/** @var IMimeTypeDetector */
private $mimeTypeDetector;
/** @var IPreview */
private $previewManager;
/** @var ITimeFactory */
private $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->userSession = $userSession;
$this->mimeTypeDetector = $mimeTypeDetector;
$this->previewManager = $previewManager;
$this->time = $time;
}
/**
* @NoAdminRequired
* @NoCSRFRequired
*
* Get the preview for a file
*
* @param int $fileId ID of the file
* @param int $x Width of the preview
* @param int $y Height of the preview
* @param bool $a Whether to not crop the preview
*
* @return Http\FileDisplayResponse<Http::STATUS_OK, array{Content-Type: string}>|DataResponse<Http::STATUS_BAD_REQUEST|Http::STATUS_NOT_FOUND, array<empty>, array{}>
*
* 200: Preview returned
* 400: Getting preview is not possible
* 404: Preview not found
*/
public function getPreview(
int $fileId = -1,
int $x = 32,
int $y = 32,
bool $a = false,
) {
if ($fileId === -1 || $x === 0 || $y === 0) {
return new DataResponse([], Http::STATUS_BAD_REQUEST);
}
try {
$file = $this->trashManager->getTrashNodeById($this->userSession->getUser(), $fileId);
if ($file === null) {
return new DataResponse([], Http::STATUS_NOT_FOUND);
}
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 (preg_match('/d\d+/', $extension)) {
$mimeType = $this->mimeTypeDetector->detectPath($fileName);
} else {
$mimeType = $this->mimeTypeDetector->detectPath($file->getName());
}
$f = $this->previewManager->getPreview($file, $x, $y, !$a, IPreview::MODE_FILL, $mimeType);
$response = new Http\FileDisplayResponse($f, Http::STATUS_OK, ['Content-Type' => $f->getMimeType()]);
// Cache previews for 24H
$response->cacheFor(3600 * 24);
return $response;
} catch (NotFoundException $e) {
return new DataResponse([], Http::STATUS_NOT_FOUND);
} catch (\InvalidArgumentException $e) {
return new DataResponse([], Http::STATUS_BAD_REQUEST);
}
}
}
@@ -0,0 +1,52 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2023 Louis Chemineau <louis@chmn.me>
*
* @author Louis Chemineau <louis@chmn.me>
*
* @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\Events;
use Exception;
use OCP\Files\Events\Node\AbstractNodesEvent;
use OCP\Files\Node;
/**
* @since 28.0.0
*/
class BeforeNodeRestoredEvent extends AbstractNodesEvent {
public function __construct(Node $source, Node $target, private bool &$run) {
parent::__construct($source, $target);
}
/**
* @return never
*/
public function abortOperation(\Throwable $ex = null) {
$this->stopPropagation();
$this->run = false;
if ($ex !== null) {
throw $ex;
} else {
throw new Exception('Operation aborted');
}
}
}
@@ -0,0 +1,75 @@
<?php
/**
* @copyright Copyright (c) 2017 Bjoern Schiessle <bjoern@schiessle.org>
*
* @author Arthur Schiwon <blizzz@arthur-schiwon.de>
* @author Bjoern Schiessle <bjoern@schiessle.org>
* @author Roeland Jago Douma <roeland@famdouma.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\Events;
use OCP\EventDispatcher\Event;
use OCP\Files\Node;
/**
* Class MoveToTrashEvent
*
* Event to allow other apps to disable the trash bin for specific files
*
* @package OCA\Files_Trashbin\Events
* @since 28.0.0 Dispatched as a typed event
*/
class MoveToTrashEvent extends Event {
/** @var bool */
private $moveToTrashBin;
/** @var Node */
private $node;
public function __construct(Node $node) {
$this->moveToTrashBin = true;
$this->node = $node;
}
/**
* get Node which will be deleted
*
* @return Node
*/
public function getNode() {
return $this->node;
}
/**
* disable trash bin for this operation
*/
public function disableTrashBin() {
$this->moveToTrashBin = false;
}
/**
* should the file be moved to the trash bin?
*
* @return bool
*/
public function shouldMoveToTrashBin() {
return $this->moveToTrashBin;
}
}
@@ -0,0 +1,38 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2023 Louis Chemineau <louis@chmn.me>
*
* @author Louis Chemineau <louis@chmn.me>
*
* @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\Events;
use OCP\Files\Events\Node\AbstractNodesEvent;
use OCP\Files\Node;
/**
* @since 28.0.0
*/
class NodeRestoredEvent extends AbstractNodesEvent {
public function __construct(Node $source, Node $target) {
parent::__construct($source, $target);
}
}
@@ -0,0 +1,25 @@
<?php
/**
* @copyright Copyright (c) 2016, ownCloud, Inc.
*
* @author Björn Schießle <bjoern@schiessle.org>
*
* @license AGPL-3.0
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* 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, version 3,
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
namespace OCA\Files_Trashbin\Exceptions;
class CopyRecursiveException extends \Exception {
}
@@ -0,0 +1,173 @@
<?php
/**
* @copyright Copyright (c) 2016, ownCloud, Inc.
*
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Lukas Reschke <lukas@statuscode.ch>
* @author Morris Jobke <hey@morrisjobke.de>
* @author Robin Appelman <robin@icewind.nl>
* @author Roeland Jago Douma <roeland@famdouma.nl>
* @author Victor Dubiniuk <dubiniuk@owncloud.com>
*
* @license AGPL-3.0
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* 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, version 3,
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
namespace OCA\Files_Trashbin;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\IConfig;
class Expiration {
// how long do we keep files in the trash bin if no other value is defined in the config file (unit: days)
public const DEFAULT_RETENTION_OBLIGATION = 30;
public const NO_OBLIGATION = -1;
/** @var ITimeFactory */
private $timeFactory;
/** @var string */
private $retentionObligation;
/** @var int */
private $minAge;
/** @var int */
private $maxAge;
/** @var bool */
private $canPurgeToSaveSpace;
public function __construct(IConfig $config, ITimeFactory $timeFactory) {
$this->timeFactory = $timeFactory;
$this->setRetentionObligation($config->getSystemValue('trashbin_retention_obligation', 'auto'));
}
public function setRetentionObligation(string $obligation) {
$this->retentionObligation = $obligation;
if ($this->retentionObligation !== 'disabled') {
$this->parseRetentionObligation();
}
}
/**
* Is trashbin expiration enabled
* @return bool
*/
public function isEnabled() {
return $this->retentionObligation !== 'disabled';
}
/**
* Check if given timestamp in expiration range
* @param int $timestamp
* @param bool $quotaExceeded
* @return bool
*/
public function isExpired($timestamp, $quotaExceeded = false) {
// No expiration if disabled
if (!$this->isEnabled()) {
return false;
}
// Purge to save space (if allowed)
if ($quotaExceeded && $this->canPurgeToSaveSpace) {
return true;
}
$time = $this->timeFactory->getTime();
// Never expire dates in future e.g. misconfiguration or negative time
// adjustment
if ($time < $timestamp) {
return false;
}
// Purge as too old
if ($this->maxAge !== self::NO_OBLIGATION) {
$maxTimestamp = $time - ($this->maxAge * 86400);
$isOlderThanMax = $timestamp < $maxTimestamp;
} else {
$isOlderThanMax = false;
}
if ($this->minAge !== self::NO_OBLIGATION) {
// older than Min obligation and we are running out of quota?
$minTimestamp = $time - ($this->minAge * 86400);
$isMinReached = ($timestamp < $minTimestamp) && $quotaExceeded;
} else {
$isMinReached = false;
}
return $isOlderThanMax || $isMinReached;
}
/**
* @return bool|int
*/
public function getMaxAgeAsTimestamp() {
$maxAge = false;
if ($this->isEnabled() && $this->maxAge !== self::NO_OBLIGATION) {
$time = $this->timeFactory->getTime();
$maxAge = $time - ($this->maxAge * 86400);
}
return $maxAge;
}
private function parseRetentionObligation() {
$splitValues = explode(',', $this->retentionObligation);
if (!isset($splitValues[0])) {
$minValue = self::DEFAULT_RETENTION_OBLIGATION;
} else {
$minValue = trim($splitValues[0]);
}
if (!isset($splitValues[1]) && $minValue === 'auto') {
$maxValue = 'auto';
} elseif (!isset($splitValues[1])) {
$maxValue = self::DEFAULT_RETENTION_OBLIGATION;
} else {
$maxValue = trim($splitValues[1]);
}
if ($minValue === 'auto' && $maxValue === 'auto') {
// Default: Keep for 30 days but delete anytime if space needed
$this->minAge = self::DEFAULT_RETENTION_OBLIGATION;
$this->maxAge = self::NO_OBLIGATION;
$this->canPurgeToSaveSpace = true;
} elseif ($minValue !== 'auto' && $maxValue === 'auto') {
// Keep for X days but delete anytime if space needed
$this->minAge = (int)$minValue;
$this->maxAge = self::NO_OBLIGATION;
$this->canPurgeToSaveSpace = true;
} elseif ($minValue === 'auto' && $maxValue !== 'auto') {
// Delete anytime if space needed, Delete all older than max automatically
$this->minAge = self::NO_OBLIGATION;
$this->maxAge = (int)$maxValue;
$this->canPurgeToSaveSpace = true;
} elseif ($minValue !== 'auto' && $maxValue !== 'auto') {
// Delete all older than max OR older than min if space needed
// Max < Min as per https://github.com/owncloud/core/issues/16300
if ($maxValue < $minValue) {
$maxValue = $minValue;
}
$this->minAge = (int)$minValue;
$this->maxAge = (int)$maxValue;
$this->canPurgeToSaveSpace = false;
}
}
}
@@ -0,0 +1,129 @@
<?php
/**
* @copyright Copyright (c) 2016, ownCloud, Inc.
*
* @author Bjoern Schiessle <bjoern@schiessle.org>
* @author Björn Schießle <bjoern@schiessle.org>
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Joas Schilling <coding@schilljs.com>
* @author Jörn Friedrich Dreyer <jfd@butonic.de>
* @author Morris Jobke <hey@morrisjobke.de>
* @author Robin Appelman <robin@icewind.nl>
* @author Roeland Jago Douma <roeland@famdouma.nl>
* @author Victor Dubiniuk <dubiniuk@owncloud.com>
* @author Vincent Petry <vincent@nextcloud.com>
*
* @license AGPL-3.0
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* 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, version 3,
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
namespace OCA\Files_Trashbin;
use OC\Files\FileInfo;
use OCP\Constants;
use OCP\Files\Cache\ICacheEntry;
class Helper {
/**
* Retrieves the contents of a trash bin directory.
*
* @param string $dir path to the directory inside the trashbin
* or empty to retrieve the root of the trashbin
* @param string $user
* @param string $sortAttribute attribute to sort on or empty to disable sorting
* @param bool $sortDescending true for descending sort, false otherwise
* @return \OCP\Files\FileInfo[]
*/
public static function getTrashFiles($dir, $user, $sortAttribute = '', $sortDescending = false) {
$result = [];
$timestamp = null;
$view = new \OC\Files\View('/' . $user . '/files_trashbin/files');
if (ltrim($dir, '/') !== '' && !$view->is_dir($dir)) {
throw new \Exception('Directory does not exists');
}
$mount = $view->getMount($dir);
$storage = $mount->getStorage();
$absoluteDir = $view->getAbsolutePath($dir);
$internalPath = $mount->getInternalPath($absoluteDir);
$originalLocations = \OCA\Files_Trashbin\Trashbin::getLocations($user);
$dirContent = $storage->getCache()->getFolderContents($mount->getInternalPath($view->getAbsolutePath($dir)));
foreach ($dirContent as $entry) {
$entryName = $entry->getName();
$name = $entryName;
if ($dir === '' || $dir === '/') {
$pathparts = pathinfo($entryName);
$timestamp = substr($pathparts['extension'], 1);
$name = $pathparts['filename'];
} elseif ($timestamp === null) {
// for subfolders we need to calculate the timestamp only once
$parts = explode('/', ltrim($dir, '/'));
$timestamp = substr(pathinfo($parts[0], PATHINFO_EXTENSION), 1);
}
$originalPath = '';
$originalName = substr($entryName, 0, -strlen($timestamp) - 2);
if (isset($originalLocations[$originalName][$timestamp])) {
$originalPath = $originalLocations[$originalName][$timestamp];
if (substr($originalPath, -1) === '/') {
$originalPath = substr($originalPath, 0, -1);
}
}
$type = $entry->getMimeType() === ICacheEntry::DIRECTORY_MIMETYPE ? 'dir' : 'file';
$i = [
'name' => $name,
'mtime' => $timestamp,
'mimetype' => $type === 'dir' ? 'httpd/unix-directory' : \OC::$server->getMimeTypeDetector()->detectPath($name),
'type' => $type,
'directory' => ($dir === '/') ? '' : $dir,
'size' => $entry->getSize(),
'etag' => '',
'permissions' => Constants::PERMISSION_ALL - Constants::PERMISSION_SHARE,
'fileid' => $entry->getId(),
];
if ($originalPath) {
if ($originalPath !== '.') {
$i['extraData'] = $originalPath . '/' . $originalName;
} else {
$i['extraData'] = $originalName;
}
}
$result[] = new FileInfo($absoluteDir . '/' . $i['name'], $storage, $internalPath . '/' . $i['name'], $i, $mount);
}
if ($sortAttribute !== '') {
return \OCA\Files\Helper::sortFiles($result, $sortAttribute, $sortDescending);
}
return $result;
}
/**
* Format file infos for JSON
*
* @param \OCP\Files\FileInfo[] $fileInfos file infos
*/
public static function formatFileInfos($fileInfos) {
$files = [];
foreach ($fileInfos as $i) {
$entry = \OCA\Files\Helper::formatFileInfo($i);
$entry['id'] = $i->getId();
$entry['etag'] = $entry['mtime']; // add fake etag, it is only needed to identify the preview image
$entry['permissions'] = \OCP\Constants::PERMISSION_READ;
$files[] = $entry;
}
return $files;
}
}
@@ -0,0 +1,48 @@
<?php
/**
* @copyright Copyright (c) 2016, ownCloud, Inc.
*
* @author Björn Schießle <bjoern@schiessle.org>
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Morris Jobke <hey@morrisjobke.de>
* @author Robin McCorkell <robin@mccorkell.me.uk>
* @author Vincent Petry <vincent@nextcloud.com>
*
* @license AGPL-3.0
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* 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, version 3,
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
namespace OCA\Files_Trashbin;
class Hooks {
/**
* clean up user specific settings if user gets deleted
* @param array $params array with uid
*
* This function is connected to the pre_deleteUser signal of OC_Users
* to remove the used space for the trash bin stored in the database
*/
public static function deleteUser_hook($params) {
$uid = $params['uid'];
Trashbin::deleteUser($uid);
}
public static function post_write_hook($params) {
$user = \OC_User::getUser();
if (!empty($user)) {
Trashbin::resizeTrash($user);
}
}
}
@@ -0,0 +1,42 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2022, John Molakvoæ <skjnldsv@protonmail.com>
*
* @author John Molakvoæ <skjnldsv@protonmail.com>
*
* @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\Listeners;
use OCA\Files\Event\LoadAdditionalScriptsEvent;
use OCA\Files_Trashbin\AppInfo\Application;
use OCP\EventDispatcher\Event;
use OCP\EventDispatcher\IEventListener;
use OCP\Util;
class LoadAdditionalScripts implements IEventListener {
public function handle(Event $event): void {
if (!($event instanceof LoadAdditionalScriptsEvent)) {
return;
}
Util::addScript(Application::APP_ID, 'main');
}
}
@@ -0,0 +1,88 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2020 Joas Schilling <coding@schilljs.com>
*
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Joas Schilling <coding@schilljs.com>
* @author Vincent Petry <vincent@nextcloud.com>
*
* @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\Migration;
use Closure;
use OCP\DB\ISchemaWrapper;
use OCP\DB\Types;
use OCP\Migration\IOutput;
use OCP\Migration\SimpleMigrationStep;
class Version1010Date20200630192639 extends SimpleMigrationStep {
/**
* @param IOutput $output
* @param Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
* @param array $options
* @return null|ISchemaWrapper
*/
public function changeSchema(IOutput $output, Closure $schemaClosure, array $options) {
/** @var ISchemaWrapper $schema */
$schema = $schemaClosure();
if (!$schema->hasTable('files_trash')) {
$table = $schema->createTable('files_trash');
$table->addColumn('auto_id', Types::BIGINT, [
'autoincrement' => true,
'notnull' => true,
]);
$table->addColumn('id', Types::STRING, [
'notnull' => true,
'length' => 250,
'default' => '',
]);
$table->addColumn('user', Types::STRING, [
'notnull' => true,
'length' => 64,
'default' => '',
]);
$table->addColumn('timestamp', Types::STRING, [
'notnull' => true,
'length' => 12,
'default' => '',
]);
$table->addColumn('location', Types::STRING, [
'notnull' => true,
'length' => 512,
'default' => '',
]);
$table->addColumn('type', Types::STRING, [
'notnull' => false,
'length' => 4,
]);
$table->addColumn('mime', Types::STRING, [
'notnull' => false,
'length' => 255,
]);
$table->setPrimaryKey(['auto_id']);
$table->addIndex(['id'], 'id_index');
$table->addIndex(['timestamp'], 'timestamp_index');
$table->addIndex(['user'], 'user_index');
}
return $schema;
}
}
@@ -0,0 +1,100 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2018 Robin Appelman <robin@icewind.nl>
*
* @author 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 OCA\Files_Trashbin\Trash\ITrashManager;
use OCP\Files\FileInfo;
abstract class AbstractTrash implements ITrash {
/** @var ITrashItem */
protected $data;
/** @var ITrashManager */
protected $trashManager;
public function __construct(ITrashManager $trashManager, ITrashItem $data) {
$this->trashManager = $trashManager;
$this->data = $data;
}
public function getFilename(): string {
return $this->data->getName();
}
public function getDeletionTime(): int {
return $this->data->getDeletedTime();
}
public function getFileId(): int {
return $this->data->getId();
}
public function getFileInfo(): FileInfo {
return $this->data;
}
/**
* @psalm-suppress ImplementedReturnTypeMismatch \Sabre\DAV\IFile::getSize signature does not support 32bit
* @return int|float
*/
public function getSize(): int|float {
return $this->data->getSize();
}
public function getLastModified(): int {
return $this->data->getMtime();
}
public function getContentType(): string {
return $this->data->getMimetype();
}
public function getETag(): string {
return $this->data->getEtag();
}
public function getName(): string {
return $this->data->getName();
}
public function getOriginalLocation(): string {
return $this->data->getOriginalLocation();
}
public function getTitle(): string {
return $this->data->getTitle();
}
public function delete() {
$this->trashManager->removeItem($this->data);
}
public function restore(): bool {
$this->trashManager->restoreItem($this->data);
return true;
}
}
@@ -0,0 +1,40 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2018 Robin Appelman <robin@icewind.nl>
*
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author 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();
}
}
@@ -0,0 +1,80 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2018 Robin Appelman <robin@icewind.nl>
*
* @author 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();
}
}
@@ -0,0 +1,47 @@
<?php
declare(strict_types=1);
/**
* @copyright 2018, Roeland Jago Douma <roeland@famdouma.nl>
*
* @author Robin Appelman <robin@icewind.nl>
* @author Roeland Jago Douma <roeland@famdouma.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 OCP\Files\FileInfo;
interface ITrash {
public function restore(): bool;
public function getFilename(): string;
public function getOriginalLocation(): string;
public function getTitle(): string;
public function getDeletionTime(): int;
public function getSize(): int|float;
public function getFileId(): int;
public function getFileInfo(): FileInfo;
}
@@ -0,0 +1,77 @@
<?php
declare(strict_types=1);
/**
* @copyright 2018, Roeland Jago Douma <roeland@famdouma.nl>
*
* @author Roeland Jago Douma <roeland@famdouma.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\ICollection;
use Sabre\DAV\IMoveTarget;
use Sabre\DAV\INode;
class RestoreFolder implements ICollection, IMoveTarget {
public function createFile($name, $data = null) {
throw new Forbidden();
}
public function createDirectory($name) {
throw new Forbidden();
}
public function getChild($name) {
return null;
}
public function delete() {
throw new Forbidden();
}
public function getName() {
return 'restore';
}
public function setName($name) {
throw new Forbidden();
}
public function getLastModified(): int {
return 0;
}
public function getChildren(): array {
return [];
}
public function childExists($name): bool {
return false;
}
public function moveInto($targetName, $sourcePath, INode $sourceNode): bool {
if (!($sourceNode instanceof ITrash)) {
return false;
}
return $sourceNode->restore();
}
}
@@ -0,0 +1,72 @@
<?php
declare(strict_types=1);
/**
* @copyright 2018, Roeland Jago Douma <roeland@famdouma.nl>
*
* @author Robin Appelman <robin@icewind.nl>
* @author Roeland Jago Douma <roeland@famdouma.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\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(
ITrashManager $trashManager,
PrincipalBackend\BackendInterface $principalBackend,
IConfig $config
) {
parent::__construct($principalBackend, 'principals/users');
$this->trashManager = $trashManager;
$this->disableListing = !$config->getSystemValue('debug', false);
}
/**
* This method returns a node for a principal.
*
* The passed array contains principal information, and is guaranteed to
* at least contain a uri item. Other properties may or may not be
* supplied by the authentication backend.
*
* @param array $principalInfo
* @return INode
*/
public function getChildForPrincipal(array $principalInfo): TrashHome {
[, $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, $this->trashManager, $user);
}
public function getName(): string {
return 'trashbin';
}
}
@@ -0,0 +1,39 @@
<?php
declare(strict_types=1);
/**
* @copyright 2018, Roeland Jago Douma <roeland@famdouma.nl>
*
* @author Robin Appelman <robin@icewind.nl>
* @author Roeland Jago Douma <roeland@famdouma.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\Trashbin;
class TrashFile extends AbstractTrashFile {
public function get() {
return $this->data->getStorage()->fopen(Trashbin::getTrashFilename($this->data->getInternalPath(), $this->getLastModified()), 'rb');
}
public function getName(): string {
return Trashbin::getTrashFilename($this->data->getName(), $this->getLastModified());
}
}
@@ -0,0 +1,35 @@
<?php
declare(strict_types=1);
/**
* @copyright 2018, Roeland Jago Douma <roeland@famdouma.nl>
*
* @author Robin Appelman <robin@icewind.nl>
* @author Roeland Jago Douma <roeland@famdouma.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\Trashbin;
class TrashFolder extends AbstractTrashFolder {
public function getName(): string {
return Trashbin::getTrashFilename($this->data->getName(), $this->getLastModified());
}
}
@@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
/**
* @copyright 2018, Roeland Jago Douma <roeland@famdouma.nl>
*
* @author Robin Appelman <robin@icewind.nl>
* @author Roeland Jago Douma <roeland@famdouma.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;
class TrashFolderFile extends AbstractTrashFile {
public function get() {
return $this->data->getStorage()->fopen($this->data->getInternalPath(), 'rb');
}
}
@@ -0,0 +1,30 @@
<?php
declare(strict_types=1);
/**
* @copyright 2018, Roeland Jago Douma <roeland@famdouma.nl>
*
* @author Robin Appelman <robin@icewind.nl>
* @author Roeland Jago Douma <roeland@famdouma.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;
class TrashFolderFolder extends AbstractTrashFolder {
}
@@ -0,0 +1,101 @@
<?php
declare(strict_types=1);
/**
* @copyright 2018, Roeland Jago Douma <roeland@famdouma.nl>
*
* @author Robin Appelman <robin@icewind.nl>
* @author Roeland Jago Douma <roeland@famdouma.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\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;
/** @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() {
throw new Forbidden();
}
public function getName(): string {
[, $name] = \Sabre\Uri\split($this->principalInfo['uri']);
return $name;
}
public function setName($name) {
throw new Forbidden('Permission denied to rename this trashbin');
}
public function createFile($name, $data = null) {
throw new Forbidden('Not allowed to create files in the trashbin');
}
public function createDirectory($name) {
throw new Forbidden('Not allowed to create folders in the trashbin');
}
public function getChild($name) {
if ($name === 'restore') {
return new RestoreFolder();
}
if ($name === 'trash') {
return new TrashRoot($this->user, $this->trashManager);
}
throw new NotFound();
}
public function getChildren(): array {
return [
new RestoreFolder(),
new TrashRoot($this->user, $this->trashManager)
];
}
public function childExists($name): bool {
return $name === 'restore' || $name === 'trash';
}
public function getLastModified(): int {
return 0;
}
}
@@ -0,0 +1,111 @@
<?php
declare(strict_types=1);
/**
* @copyright 2018, Roeland Jago Douma <roeland@famdouma.nl>
*
* @author Julius Härtl <jus@bitgrid.net>
* @author Robin Appelman <robin@icewind.nl>
* @author Roeland Jago Douma <roeland@famdouma.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 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 IUser */
private $user;
/** @var ITrashManager */
private $trashManager;
public function __construct(IUser $user, ITrashManager $trashManager) {
$this->user = $user;
$this->trashManager = $trashManager;
}
public function delete() {
\OCA\Files_Trashbin\Trashbin::deleteAll();
foreach ($this->trashManager->listTrashRoot($this->user) as $trashItem) {
$this->trashManager->removeItem($trashItem);
}
}
public function getName(): string {
return 'trash';
}
public function setName($name) {
throw new Forbidden('Permission denied to rename this trashbin');
}
public function createFile($name, $data = null) {
throw new Forbidden('Not allowed to create files in the trashbin');
}
public function createDirectory($name) {
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 = $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 getLastModified(): int {
return 0;
}
}
@@ -0,0 +1,130 @@
<?php
declare(strict_types=1);
/**
* @copyright 2018, Roeland Jago Douma <roeland@famdouma.nl>
*
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Robin Appelman <robin@icewind.nl>
* @author Roeland Jago Douma <roeland@famdouma.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\DAV\Connector\Sabre\FilesPlugin;
use OCP\IPreview;
use Sabre\DAV\INode;
use Sabre\DAV\PropFind;
use Sabre\DAV\Server;
use Sabre\DAV\ServerPlugin;
use Sabre\HTTP\RequestInterface;
use Sabre\HTTP\ResponseInterface;
class TrashbinPlugin extends ServerPlugin {
public const TRASHBIN_FILENAME = '{http://nextcloud.org/ns}trashbin-filename';
public const TRASHBIN_ORIGINAL_LOCATION = '{http://nextcloud.org/ns}trashbin-original-location';
public const TRASHBIN_DELETION_TIME = '{http://nextcloud.org/ns}trashbin-deletion-time';
public const TRASHBIN_TITLE = '{http://nextcloud.org/ns}trashbin-title';
/** @var Server */
private $server;
/** @var IPreview */
private $previewManager;
public function __construct(
IPreview $previewManager
) {
$this->previewManager = $previewManager;
}
public function initialize(Server $server) {
$this->server = $server;
$this->server->on('propFind', [$this, 'propFind']);
$this->server->on('afterMethod:GET', [$this,'httpGet']);
}
public function propFind(PropFind $propFind, INode $node) {
if (!($node instanceof ITrash)) {
return;
}
$propFind->handle(self::TRASHBIN_FILENAME, function () use ($node) {
return $node->getFilename();
});
$propFind->handle(self::TRASHBIN_ORIGINAL_LOCATION, function () use ($node) {
return $node->getOriginalLocation();
});
$propFind->handle(self::TRASHBIN_TITLE, function () use ($node) {
return $node->getTitle();
});
$propFind->handle(self::TRASHBIN_DELETION_TIME, function () use ($node) {
return $node->getDeletionTime();
});
$propFind->handle(FilesPlugin::SIZE_PROPERTYNAME, function () use ($node) {
return $node->getSize();
});
$propFind->handle(FilesPlugin::FILEID_PROPERTYNAME, function () use ($node) {
return $node->getFileId();
});
$propFind->handle(FilesPlugin::PERMISSIONS_PROPERTYNAME, function () {
return 'GD'; // read + delete
});
$propFind->handle(FilesPlugin::GETETAG_PROPERTYNAME, function () use ($node) {
// add fake etag, it is only needed to identify the preview image
return $node->getLastModified();
});
$propFind->handle(FilesPlugin::INTERNAL_FILEID_PROPERTYNAME, function () use ($node) {
// add fake etag, it is only needed to identify the preview image
return $node->getFileId();
});
$propFind->handle(FilesPlugin::HAS_PREVIEW_PROPERTYNAME, function () use ($node) {
return $this->previewManager->isAvailable($node->getFileInfo());
});
$propFind->handle(FilesPlugin::MOUNT_TYPE_PROPERTYNAME, function () {
return '';
});
}
/**
* Set real filename on trashbin download
*
* @param RequestInterface $request
* @param ResponseInterface $response
*/
public function httpGet(RequestInterface $request, ResponseInterface $response): void {
$path = $request->getPath();
$node = $this->server->tree->getNodeForPath($path);
if ($node instanceof ITrash) {
$response->addHeader('Content-Disposition', 'attachment; filename="' . $node->getFilename() . '"');
}
}
}
@@ -0,0 +1,257 @@
<?php
/**
* @copyright Copyright (c) 2016, ownCloud, Inc.
*
* @author Bjoern Schiessle <bjoern@schiessle.org>
* @author Björn Schießle <bjoern@schiessle.org>
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Julius Härtl <jus@bitgrid.net>
* @author Morris Jobke <hey@morrisjobke.de>
* @author Robin Appelman <robin@icewind.nl>
* @author Roeland Jago Douma <roeland@famdouma.nl>
* @author Vincent Petry <vincent@nextcloud.com>
*
* @license AGPL-3.0
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* 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, version 3,
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
namespace OCA\Files_Trashbin;
use OC\Files\Filesystem;
use OC\Files\Storage\Wrapper\Wrapper;
use OCA\Files_Trashbin\Events\MoveToTrashEvent;
use OCA\Files_Trashbin\Trash\ITrashManager;
use OCP\Encryption\Exceptions\GenericEncryptionException;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\Files\IRootFolder;
use OCP\Files\Node;
use OCP\Files\Storage\IStorage;
use OCP\IUserManager;
use Psr\Log\LoggerInterface;
class Storage extends Wrapper {
private string $mountPoint;
private IUserManager$userManager;
private LoggerInterface $logger;
private IEventDispatcher $eventDispatcher;
private IRootFolder $rootFolder;
private ITrashManager $trashManager;
private bool $trashEnabled = true;
/**
* Storage constructor.
*
* @param array $parameters
* @param ITrashManager|null $trashManager
* @param IUserManager|null $userManager
* @param LoggerInterface|null $logger
* @param IEventDispatcher|null $eventDispatcher
* @param IRootFolder|null $rootFolder
*/
public function __construct(
$parameters,
ITrashManager $trashManager = null,
IUserManager $userManager = null,
LoggerInterface $logger = null,
IEventDispatcher $eventDispatcher = null,
IRootFolder $rootFolder = null
) {
$this->mountPoint = $parameters['mountPoint'];
$this->trashManager = $trashManager;
$this->userManager = $userManager;
$this->logger = $logger;
$this->eventDispatcher = $eventDispatcher;
$this->rootFolder = $rootFolder;
parent::__construct($parameters);
}
/**
* Deletes the given file by moving it into the trashbin.
*
* @param string $path path of file or folder to delete
*
* @return bool true if the operation succeeded, false otherwise
*/
public function unlink($path) {
if ($this->trashEnabled) {
try {
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 .
" to the trash bin, therefore it was deleted right away");
return $this->storage->unlink($path);
}
} else {
return $this->storage->unlink($path);
}
}
/**
* Deletes the given folder by moving it into the trashbin.
*
* @param string $path path of folder to delete
*
* @return bool true if the operation succeeded, false otherwise
*/
public function rmdir($path) {
if ($this->trashEnabled) {
return $this->doDelete($path, 'rmdir');
} else {
return $this->storage->rmdir($path);
}
}
/**
* check if it is a file located in data/user/files only files in the
* 'files' directory should be moved to the trash
*
* @param $path
* @return bool
*/
protected function shouldMoveToTrash($path) {
$normalized = Filesystem::normalizePath($this->mountPoint . '/' . $path);
$parts = explode('/', $normalized);
if (count($parts) < 4 || strpos($normalized, '/appdata_') === 0) {
return false;
}
// check if there is a app which want to disable the trash bin for this file
$fileId = $this->storage->getCache()->getId($path);
$owner = $this->storage->getOwner($path);
if ($owner === false || $this->storage->instanceOfStorage(\OCA\Files_Sharing\External\Storage::class)) {
$nodes = $this->rootFolder->getById($fileId);
} else {
$nodes = $this->rootFolder->getUserFolder($owner)->getById($fileId);
}
foreach ($nodes as $node) {
$event = $this->createMoveToTrashEvent($node);
$this->eventDispatcher->dispatchTyped($event);
$this->eventDispatcher->dispatch('OCA\Files_Trashbin::moveToTrash', $event);
if ($event->shouldMoveToTrashBin() === false) {
return false;
}
}
if ($parts[2] === 'files' && $this->userManager->userExists($parts[1])) {
return true;
}
return false;
}
/**
* get move to trash event
*
* @param Node $node
* @return MoveToTrashEvent
*/
protected function createMoveToTrashEvent(Node $node) {
return new MoveToTrashEvent($node);
}
/**
* Run the delete operation with the given method
*
* @param string $path path of file or folder to delete
* @param string $method either "unlink" or "rmdir"
*
* @return bool true if the operation succeeded, false otherwise
*/
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([$this->storage, $method], $path);
}
// check permissions before we continue, this is especially important for
// shared files
if (!$this->isDeletable($path)) {
return false;
}
$isMovedToTrash = $this->trashManager->moveToTrash($this, $path);
if (!$isMovedToTrash) {
return call_user_func([$this->storage, $method], $path);
} else {
return true;
}
}
/**
* Setup the storage wrapper callback
*/
public static function setupStorage() {
$trashManager = \OC::$server->get(ITrashManager::class);
$userManager = \OC::$server->get(IUserManager::class);
$logger = \OC::$server->get(LoggerInterface::class);
$eventDispatcher = \OC::$server->get(IEventDispatcher::class);
$rootFolder = \OC::$server->get(IRootFolder::class);
Filesystem::addStorageWrapper(
'oc_trashbin',
function (string $mountPoint, IStorage $storage) use ($trashManager, $userManager, $logger, $eventDispatcher, $rootFolder) {
return new Storage(
['storage' => $storage, 'mountPoint' => $mountPoint],
$trashManager,
$userManager,
$logger,
$eventDispatcher,
$rootFolder,
);
},
1);
}
public function getMountPoint() {
return $this->mountPoint;
}
public function moveFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) {
$sourceIsTrashbin = $sourceStorage->instanceOfStorage(Storage::class);
try {
// the fallback for moving between storage involves a copy+delete
// we don't want to trigger the trashbin when doing the delete
if ($sourceIsTrashbin) {
/** @var Storage $sourceStorage */
$sourceStorage->disableTrash();
}
$result = parent::moveFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
if ($sourceIsTrashbin) {
/** @var Storage $sourceStorage */
$sourceStorage->enableTrash();
}
return $result;
} catch (\Exception $e) {
if ($sourceIsTrashbin) {
/** @var Storage $sourceStorage */
$sourceStorage->enableTrash();
}
throw $e;
}
}
protected function disableTrash() {
$this->trashEnabled = false;
}
protected function enableTrash() {
$this->trashEnabled = true;
}
}
@@ -0,0 +1,26 @@
<?php
/**
* @copyright Copyright (c) 2018 Robin Appelman <robin@icewind.nl>
*
* @author 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 {
}
@@ -0,0 +1,83 @@
<?php
/**
* @copyright Copyright (c) 2018 Robin Appelman <robin@icewind.nl>
*
* @author 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\Node;
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);
}
@@ -0,0 +1,81 @@
<?php
/**
* @copyright Copyright (c) 2018 Robin Appelman <robin@icewind.nl>
*
* @author 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;
public function getTitle(): string;
}
@@ -0,0 +1,57 @@
<?php
/**
* @copyright Copyright (c) 2018 Robin Appelman <robin@icewind.nl>
*
* @author 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();
}
@@ -0,0 +1,133 @@
<?php
/**
* @copyright Copyright (c) 2018 Robin Appelman <robin@icewind.nl>
*
* @author 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) {
$originalLocation = $isRoot ? $file['extraData'] : $parent->getOriginalLocation() . '/' . $file->getName();
if (!$originalLocation) {
$originalLocation = $file->getName();
}
$trashFilename = Trashbin::getTrashFilename($file->getName(), $file->getMtime());
return new TrashItem(
$this,
$originalLocation,
$file->getMTime(),
$parentTrashPath . '/' . ($isRoot ? $trashFilename : $file->getName()),
$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;
}
}
}
@@ -0,0 +1,202 @@
<?php
/**
* @copyright Copyright (c) 2018 Robin Appelman <robin@icewind.nl>
*
* @author Maxence Lange <maxence@artificial-owl.com>
* @author 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($includeMounts = true) {
return $this->fileInfo->getSize($includeMounts);
}
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();
}
public function getExtension(): string {
return $this->fileInfo->getExtension();
}
public function getTitle(): string {
return $this->getOriginalLocation();
}
public function getCreationTime(): int {
return $this->fileInfo->getCreationTime();
}
public function getUploadTime(): int {
return $this->fileInfo->getUploadTime();
}
public function getParentId(): int {
return $this->fileInfo->getParentId();
}
/**
* @inheritDoc
* @return array<string, int|string|bool|float|string[]|int[]>
*/
public function getMetadata(): array {
return $this->fileInfo->getMetadata();
}
}
@@ -0,0 +1,127 @@
<?php
/**
* @copyright Copyright (c) 2018 Robin Appelman <robin@icewind.nl>
*
* @author 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\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 $b->getDeletedTime() - $a->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);
$this->trashPaused = true;
$result = $backend->moveToTrash($storage, $internalPath);
$this->trashPaused = false;
return $result;
} 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;
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,181 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2022 Côme Chilliet <come.chilliet@nextcloud.com>
*
* @author Côme Chilliet <come.chilliet@nextcloud.com>
*
* @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\UserMigration;
use OCA\Files_Trashbin\AppInfo\Application;
use OCP\Files\Folder;
use OCP\Files\IRootFolder;
use OCP\Files\NotFoundException;
use OCP\IDBConnection;
use OCP\IL10N;
use OCP\IUser;
use OCP\UserMigration\IExportDestination;
use OCP\UserMigration\IImportSource;
use OCP\UserMigration\IMigrator;
use OCP\UserMigration\ISizeEstimationMigrator;
use OCP\UserMigration\TMigratorBasicVersionHandling;
use OCP\UserMigration\UserMigrationException;
use Symfony\Component\Console\Output\OutputInterface;
class TrashbinMigrator implements IMigrator, ISizeEstimationMigrator {
use TMigratorBasicVersionHandling;
protected const PATH_FILES_FOLDER = Application::APP_ID.'/files';
protected const PATH_LOCATIONS_FILE = Application::APP_ID.'/locations.json';
protected IRootFolder $root;
protected IDBConnection $dbc;
protected IL10N $l10n;
public function __construct(
IRootFolder $rootFolder,
IDBConnection $dbc,
IL10N $l10n
) {
$this->root = $rootFolder;
$this->dbc = $dbc;
$this->l10n = $l10n;
}
/**
* {@inheritDoc}
*/
public function getEstimatedExportSize(IUser $user): int|float {
$uid = $user->getUID();
try {
$trashbinFolder = $this->root->get('/'.$uid.'/files_trashbin');
if (!$trashbinFolder instanceof Folder) {
return 0;
}
return ceil($trashbinFolder->getSize() / 1024);
} catch (\Throwable $e) {
return 0;
}
}
/**
* {@inheritDoc}
*/
public function export(IUser $user, IExportDestination $exportDestination, OutputInterface $output): void {
$output->writeln('Exporting trashbin into ' . Application::APP_ID . '…');
$uid = $user->getUID();
try {
$trashbinFolder = $this->root->get('/'.$uid.'/files_trashbin');
if (!$trashbinFolder instanceof Folder) {
throw new UserMigrationException('/'.$uid.'/files_trashbin is not a folder');
}
$output->writeln("Exporting trashbin files…");
$exportDestination->copyFolder($trashbinFolder, static::PATH_FILES_FOLDER);
$originalLocations = \OCA\Files_Trashbin\Trashbin::getLocations($uid);
$exportDestination->addFileContents(static::PATH_LOCATIONS_FILE, json_encode($originalLocations));
} catch (NotFoundException $e) {
$output->writeln("No trashbin to export…");
} catch (\Throwable $e) {
throw new UserMigrationException("Could not export trashbin: ".$e->getMessage(), 0, $e);
}
}
/**
* {@inheritDoc}
*/
public function import(IUser $user, IImportSource $importSource, OutputInterface $output): void {
if ($importSource->getMigratorVersion($this->getId()) === null) {
$output->writeln('No version for ' . static::class . ', skipping import…');
return;
}
$output->writeln('Importing trashbin from ' . Application::APP_ID . '…');
$uid = $user->getUID();
if ($importSource->pathExists(static::PATH_FILES_FOLDER)) {
try {
$trashbinFolder = $this->root->get('/'.$uid.'/files_trashbin');
if (!$trashbinFolder instanceof Folder) {
throw new UserMigrationException('Could not import trashbin, /'.$uid.'/files_trashbin is not a folder');
}
} catch (NotFoundException $e) {
$trashbinFolder = $this->root->newFolder('/'.$uid.'/files_trashbin');
}
$output->writeln("Importing trashbin files…");
try {
$importSource->copyToFolder($trashbinFolder, static::PATH_FILES_FOLDER);
} catch (\Throwable $e) {
throw new UserMigrationException("Could not import trashbin.", 0, $e);
}
$locations = json_decode($importSource->getFileContents(static::PATH_LOCATIONS_FILE), true, 512, JSON_THROW_ON_ERROR);
$qb = $this->dbc->getQueryBuilder();
$qb->insert('files_trash')
->values([
'id' => $qb->createParameter('id'),
'timestamp' => $qb->createParameter('timestamp'),
'location' => $qb->createParameter('location'),
'user' => $qb->createNamedParameter($uid),
]);
foreach ($locations as $id => $fileLocations) {
foreach ($fileLocations as $timestamp => $location) {
$qb
->setParameter('id', $id)
->setParameter('timestamp', $timestamp)
->setParameter('location', $location)
;
$qb->executeStatement();
}
}
} else {
$output->writeln("No trashbin to import…");
}
}
/**
* {@inheritDoc}
*/
public function getId(): string {
return 'trashbin';
}
/**
* {@inheritDoc}
*/
public function getDisplayName(): string {
return $this->l10n->t('Deleted files');
}
/**
* {@inheritDoc}
*/
public function getDescription(): string {
return $this->l10n->t('Deleted files and folders in the trash bin (may expire during export if you are low on storage space)');
}
}