diff options
Diffstat (limited to 'apps/files_external/lib/Command')
-rw-r--r-- | apps/files_external/lib/Command/Applicable.php | 35 | ||||
-rw-r--r-- | apps/files_external/lib/Command/Backends.php | 26 | ||||
-rw-r--r-- | apps/files_external/lib/Command/Config.php | 31 | ||||
-rw-r--r-- | apps/files_external/lib/Command/Create.php | 31 | ||||
-rw-r--r-- | apps/files_external/lib/Command/Delete.php | 31 | ||||
-rw-r--r-- | apps/files_external/lib/Command/Dependencies.php | 57 | ||||
-rw-r--r-- | apps/files_external/lib/Command/Export.php | 23 | ||||
-rw-r--r-- | apps/files_external/lib/Command/Import.php | 37 | ||||
-rw-r--r-- | apps/files_external/lib/Command/ListCommand.php | 34 | ||||
-rw-r--r-- | apps/files_external/lib/Command/Notify.php | 115 | ||||
-rw-r--r-- | apps/files_external/lib/Command/Option.php | 25 | ||||
-rw-r--r-- | apps/files_external/lib/Command/Scan.php | 166 | ||||
-rw-r--r-- | apps/files_external/lib/Command/StorageAuthBase.php | 114 | ||||
-rw-r--r-- | apps/files_external/lib/Command/Verify.php | 33 |
14 files changed, 422 insertions, 336 deletions
diff --git a/apps/files_external/lib/Command/Applicable.php b/apps/files_external/lib/Command/Applicable.php index dbedc72825c..4d5e264bfaf 100644 --- a/apps/files_external/lib/Command/Applicable.php +++ b/apps/files_external/lib/Command/Applicable.php @@ -1,26 +1,9 @@ <?php + /** - * @copyright Copyright (c) 2016, ownCloud, Inc. - * - * @author Christoph Wurst <christoph@winzerhof-wurst.at> - * @author Joas Schilling <coding@schilljs.com> - * @author Robin Appelman <robin@icewind.nl> - * @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/> - * + * SPDX-FileCopyrightText: 2017-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only */ namespace OCA\Files_External\Command; @@ -28,13 +11,13 @@ use OC\Core\Command\Base; use OCA\Files_External\Lib\StorageConfig; use OCA\Files_External\NotFoundException; use OCA\Files_External\Service\GlobalStoragesService; +use OCP\AppFramework\Http; use OCP\IGroupManager; 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; -use Symfony\Component\HttpFoundation\Response; class Applicable extends Base { public function __construct( @@ -88,10 +71,10 @@ class Applicable extends Base { $mount = $this->globalService->getStorage($mountId); } catch (NotFoundException $e) { $output->writeln('<error>Mount with id "' . $mountId . ' not found, check "occ files_external:list" to get available mounts</error>'); - return Response::HTTP_NOT_FOUND; + return Http::STATUS_NOT_FOUND; } - if ($mount->getType() === StorageConfig::MOUNT_TYPE_PERSONAl) { + if ($mount->getType() === StorageConfig::MOUNT_TYPE_PERSONAL) { $output->writeln('<error>Can\'t change applicables on personal mounts</error>'); return self::FAILURE; } @@ -108,13 +91,13 @@ class Applicable extends Base { foreach ($addUsers as $addUser) { if (!$this->userManager->userExists($addUser)) { $output->writeln('<error>User "' . $addUser . '" not found</error>'); - return Response::HTTP_NOT_FOUND; + return Http::STATUS_NOT_FOUND; } } foreach ($addGroups as $addGroup) { if (!$this->groupManager->groupExists($addGroup)) { $output->writeln('<error>Group "' . $addGroup . '" not found</error>'); - return Response::HTTP_NOT_FOUND; + return Http::STATUS_NOT_FOUND; } } diff --git a/apps/files_external/lib/Command/Backends.php b/apps/files_external/lib/Command/Backends.php index 3a0f26a2803..7fab0477adf 100644 --- a/apps/files_external/lib/Command/Backends.php +++ b/apps/files_external/lib/Command/Backends.php @@ -1,25 +1,9 @@ <?php + /** - * @copyright Copyright (c) 2016, ownCloud, Inc. - * - * @author Christoph Wurst <christoph@winzerhof-wurst.at> - * @author Joas Schilling <coding@schilljs.com> - * @author Robin Appelman <robin@icewind.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/> - * + * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only */ namespace OCA\Files_External\Command; @@ -111,7 +95,7 @@ class Backends extends Base { */ private function formatConfiguration(array $parameters): array { $configuration = array_filter($parameters, function (DefinitionParameter $parameter) { - return $parameter->getType() !== DefinitionParameter::VALUE_HIDDEN; + return $parameter->isFlagSet(DefinitionParameter::FLAG_HIDDEN); }); return array_map(function (DefinitionParameter $parameter) { return $parameter->getTypeName(); diff --git a/apps/files_external/lib/Command/Config.php b/apps/files_external/lib/Command/Config.php index 3b905cc74fb..883b4a2f3e7 100644 --- a/apps/files_external/lib/Command/Config.php +++ b/apps/files_external/lib/Command/Config.php @@ -1,26 +1,9 @@ <?php + /** - * @copyright Copyright (c) 2016, ownCloud, Inc. - * - * @author Ardinis <Ardinis@users.noreply.github.com> - * @author Christoph Wurst <christoph@winzerhof-wurst.at> - * @author Joas Schilling <coding@schilljs.com> - * @author Robin Appelman <robin@icewind.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/> - * + * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only */ namespace OCA\Files_External\Command; @@ -28,10 +11,10 @@ use OC\Core\Command\Base; use OCA\Files_External\Lib\StorageConfig; use OCA\Files_External\NotFoundException; use OCA\Files_External\Service\GlobalStoragesService; +use OCP\AppFramework\Http; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; -use Symfony\Component\HttpFoundation\Response; class Config extends Base { public function __construct( @@ -67,7 +50,7 @@ class Config extends Base { $mount = $this->globalService->getStorage($mountId); } catch (NotFoundException $e) { $output->writeln('<error>Mount with id "' . $mountId . ' not found, check "occ files_external:list" to get available mounts"</error>'); - return Response::HTTP_NOT_FOUND; + return Http::STATUS_NOT_FOUND; } $value = $input->getArgument('value'); @@ -91,7 +74,7 @@ class Config extends Base { if (!is_string($value) && json_decode(json_encode($value)) === $value) { // show bools and objects correctly $value = json_encode($value); } - $output->writeln($value); + $output->writeln((string)$value); } /** diff --git a/apps/files_external/lib/Command/Create.php b/apps/files_external/lib/Command/Create.php index 50f7fb1cb43..3307015518a 100644 --- a/apps/files_external/lib/Command/Create.php +++ b/apps/files_external/lib/Command/Create.php @@ -1,26 +1,9 @@ <?php + /** - * @copyright Copyright (c) 2016, ownCloud, Inc. - * - * @author Christoph Wurst <christoph@winzerhof-wurst.at> - * @author Joas Schilling <coding@schilljs.com> - * @author Robin Appelman <robin@icewind.nl> - * @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/> - * + * SPDX-FileCopyrightText: 2017-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only */ namespace OCA\Files_External\Command; @@ -35,6 +18,7 @@ use OCA\Files_External\Service\BackendService; use OCA\Files_External\Service\GlobalStoragesService; use OCA\Files_External\Service\StoragesService; use OCA\Files_External\Service\UserStoragesService; +use OCP\AppFramework\Http; use OCP\IUserManager; use OCP\IUserSession; use Symfony\Component\Console\Input\ArrayInput; @@ -42,7 +26,6 @@ use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; -use Symfony\Component\HttpFoundation\Response; class Create extends Base { public function __construct( @@ -111,11 +94,11 @@ class Create extends Base { } if (is_null($storageBackend)) { $output->writeln('<error>Storage backend with identifier "' . $storageIdentifier . '" not found (see `occ files_external:backends` for possible values)</error>'); - return Response::HTTP_NOT_FOUND; + return Http::STATUS_NOT_FOUND; } if (is_null($authBackend)) { $output->writeln('<error>Authentication backend with identifier "' . $authIdentifier . '" not found (see `occ files_external:backends` for possible values)</error>'); - return Response::HTTP_NOT_FOUND; + return Http::STATUS_NOT_FOUND; } $supportedSchemes = array_keys($storageBackend->getAuthSchemes()); if (!in_array($authBackend->getScheme(), $supportedSchemes)) { diff --git a/apps/files_external/lib/Command/Delete.php b/apps/files_external/lib/Command/Delete.php index 6b6ae299773..9f121250f7d 100644 --- a/apps/files_external/lib/Command/Delete.php +++ b/apps/files_external/lib/Command/Delete.php @@ -1,25 +1,9 @@ <?php + /** - * @copyright Copyright (c) 2016, ownCloud, Inc. - * - * @author Christoph Wurst <christoph@winzerhof-wurst.at> - * @author Joas Schilling <coding@schilljs.com> - * @author Robin Appelman <robin@icewind.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/> - * + * SPDX-FileCopyrightText: 2017-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only */ namespace OCA\Files_External\Command; @@ -27,15 +11,16 @@ use OC\Core\Command\Base; use OCA\Files_External\NotFoundException; use OCA\Files_External\Service\GlobalStoragesService; use OCA\Files_External\Service\UserStoragesService; +use OCP\AppFramework\Http; use OCP\IUserManager; use OCP\IUserSession; +use Symfony\Component\Console\Helper\QuestionHelper; use Symfony\Component\Console\Input\ArrayInput; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Question\ConfirmationQuestion; -use Symfony\Component\HttpFoundation\Response; class Delete extends Base { public function __construct( @@ -43,6 +28,7 @@ class Delete extends Base { protected UserStoragesService $userService, protected IUserSession $userSession, protected IUserManager $userManager, + protected QuestionHelper $questionHelper, ) { parent::__construct(); } @@ -70,7 +56,7 @@ class Delete extends Base { $mount = $this->globalService->getStorage($mountId); } catch (NotFoundException $e) { $output->writeln('<error>Mount with id "' . $mountId . ' not found, check "occ files_external:list" to get available mounts"</error>'); - return Response::HTTP_NOT_FOUND; + return Http::STATUS_NOT_FOUND; } $noConfirm = $input->getOption('yes'); @@ -81,6 +67,7 @@ class Delete extends Base { $listInput->setOption('output', $input->getOption('output')); $listCommand->listMounts(null, [$mount], $listInput, $output); + /** @var QuestionHelper $questionHelper */ $questionHelper = $this->getHelper('question'); $question = new ConfirmationQuestion('Delete this mount? [y/N] ', false); diff --git a/apps/files_external/lib/Command/Dependencies.php b/apps/files_external/lib/Command/Dependencies.php new file mode 100644 index 00000000000..d2db8a8c9af --- /dev/null +++ b/apps/files_external/lib/Command/Dependencies.php @@ -0,0 +1,57 @@ +<?php + +/** + * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ +namespace OCA\Files_External\Command; + +use OC\Core\Command\Base; +use OCA\Files_External\Service\BackendService; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; + +class Dependencies extends Base { + public function __construct( + private readonly BackendService $backendService, + ) { + parent::__construct(); + } + + protected function configure(): void { + $this + ->setName('files_external:dependencies') + ->setDescription('Show information about the backend dependencies'); + parent::configure(); + } + + protected function execute(InputInterface $input, OutputInterface $output): int { + $storageBackends = $this->backendService->getBackends(); + + $anyMissing = false; + + foreach ($storageBackends as $backend) { + if ($backend->getDeprecateTo() !== null) { + continue; + } + $missingDependencies = $backend->checkDependencies(); + if ($missingDependencies) { + $anyMissing = true; + $output->writeln($backend->getText() . ':'); + foreach ($missingDependencies as $missingDependency) { + if ($missingDependency->getMessage()) { + $output->writeln(" - <comment>{$missingDependency->getDependency()}</comment>: {$missingDependency->getMessage()}"); + } else { + $output->writeln(" - <comment>{$missingDependency->getDependency()}</comment>"); + } + } + } + } + + if (!$anyMissing) { + $output->writeln('<info>All dependencies are met</info>'); + } + + return self::SUCCESS; + } +} diff --git a/apps/files_external/lib/Command/Export.php b/apps/files_external/lib/Command/Export.php index b5f7b67eafb..59484d0e788 100644 --- a/apps/files_external/lib/Command/Export.php +++ b/apps/files_external/lib/Command/Export.php @@ -1,24 +1,9 @@ <?php + /** - * @copyright Copyright (c) 2016, ownCloud, Inc. - * - * @author Joas Schilling <coding@schilljs.com> - * @author Robin Appelman <robin@icewind.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/> - * + * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only */ namespace OCA\Files_External\Command; diff --git a/apps/files_external/lib/Command/Import.php b/apps/files_external/lib/Command/Import.php index a4976b21cb7..a9ed76fbe40 100644 --- a/apps/files_external/lib/Command/Import.php +++ b/apps/files_external/lib/Command/Import.php @@ -1,26 +1,9 @@ <?php + /** - * @copyright Copyright (c) 2016, ownCloud, Inc. - * - * @author Christoph Wurst <christoph@winzerhof-wurst.at> - * @author Joas Schilling <coding@schilljs.com> - * @author Robin Appelman <robin@icewind.nl> - * @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/> - * + * SPDX-FileCopyrightText: 2017-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only */ namespace OCA\Files_External\Command; @@ -77,7 +60,7 @@ class Import extends Base { } protected function execute(InputInterface $input, OutputInterface $output): int { - $user = (string) $input->getOption('user'); + $user = (string)$input->getOption('user'); $path = $input->getArgument('path'); if ($path === '-') { $json = file_get_contents('php://stdin'); @@ -130,12 +113,12 @@ class Import extends Base { foreach ($mounts as $mount) { foreach ($existingMounts as $existingMount) { if ( - $existingMount->getMountPoint() === $mount->getMountPoint() && - $existingMount->getApplicableGroups() === $mount->getApplicableGroups() && - $existingMount->getApplicableUsers() === $mount->getApplicableUsers() && - $existingMount->getBackendOptions() === $mount->getBackendOptions() + $existingMount->getMountPoint() === $mount->getMountPoint() + && $existingMount->getApplicableGroups() === $mount->getApplicableGroups() + && $existingMount->getApplicableUsers() === $mount->getApplicableUsers() + && $existingMount->getBackendOptions() === $mount->getBackendOptions() ) { - $output->writeln("<error>Duplicate mount (" . $mount->getMountPoint() . ")</error>"); + $output->writeln('<error>Duplicate mount (' . $mount->getMountPoint() . ')</error>'); return self::FAILURE; } } diff --git a/apps/files_external/lib/Command/ListCommand.php b/apps/files_external/lib/Command/ListCommand.php index e4a33a99a92..350916b6c2c 100644 --- a/apps/files_external/lib/Command/ListCommand.php +++ b/apps/files_external/lib/Command/ListCommand.php @@ -1,27 +1,9 @@ <?php + /** - * @copyright Copyright (c) 2016, ownCloud, Inc. - * - * @author Christoph Wurst <christoph@winzerhof-wurst.at> - * @author Daniel Kesselberg <mail@danielkesselberg.de> - * @author Joas Schilling <coding@schilljs.com> - * @author Robin Appelman <robin@icewind.nl> - * @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/> - * + * SPDX-FileCopyrightText: 2017-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only */ namespace OCA\Files_External\Command; @@ -104,11 +86,11 @@ class ListCommand extends Base { $output->writeln('[]'); } else { if ($userId === self::ALL) { - $output->writeln("<info>No mounts configured</info>"); + $output->writeln('<info>No mounts configured</info>'); } elseif ($userId) { $output->writeln("<info>No mounts configured by $userId</info>"); } else { - $output->writeln("<info>No admin mounts configured</info>"); + $output->writeln('<info>No admin mounts configured</info>'); } } return; @@ -125,12 +107,12 @@ class ListCommand extends Base { } if (!$input->getOption('show-password')) { - $hideKeys = ['password', 'refresh_token', 'token', 'client_secret', 'public_key', 'private_key']; + $hideKeys = ['key', 'bucket', 'secret', 'password', 'refresh_token', 'token', 'client_secret', 'public_key', 'private_key']; foreach ($mounts as $mount) { $config = $mount->getBackendOptions(); foreach ($config as $key => $value) { if (in_array($key, $hideKeys)) { - $mount->setBackendOption($key, '***'); + $mount->setBackendOption($key, '***REMOVED SENSITIVE VALUE***'); } } } diff --git a/apps/files_external/lib/Command/Notify.php b/apps/files_external/lib/Command/Notify.php index fd3a66a756e..0982aa5598b 100644 --- a/apps/files_external/lib/Command/Notify.php +++ b/apps/files_external/lib/Command/Notify.php @@ -3,36 +3,12 @@ declare(strict_types=1); /** - * @copyright Copyright (c) 2016 Robin Appelman <robin@icewind.nl> - * - * @author Ari Selseng <ari@selseng.net> - * @author Christoph Wurst <christoph@winzerhof-wurst.at> - * @author Joas Schilling <coding@schilljs.com> - * @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/>. - * + * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later */ namespace OCA\Files_External\Command; use Doctrine\DBAL\Exception\DriverException; -use OC\Core\Command\Base; -use OCA\Files_External\Lib\InsufficientDataForMeaningfulAnswerException; -use OCA\Files_External\Lib\StorageConfig; use OCA\Files_External\Service\GlobalStoragesService; use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\Files\Notify\IChange; @@ -40,7 +16,6 @@ use OCP\Files\Notify\INotifyHandler; use OCP\Files\Notify\IRenameChange; use OCP\Files\Storage\INotifyStorage; use OCP\Files\Storage\IStorage; -use OCP\Files\StorageNotAvailableException; use OCP\IDBConnection; use OCP\IUserManager; use Psr\Log\LoggerInterface; @@ -49,14 +24,14 @@ use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; -class Notify extends Base { +class Notify extends StorageAuthBase { public function __construct( - private GlobalStoragesService $globalService, private IDBConnection $connection, private LoggerInterface $logger, - private IUserManager $userManager + GlobalStoragesService $globalService, + IUserManager $userManager, ) { - parent::__construct(); + parent::__construct($globalService, $userManager); } protected function configure(): void { @@ -97,71 +72,12 @@ class Notify extends Base { parent::configure(); } - private function getUserOption(InputInterface $input): ?string { - if ($input->getOption('user')) { - return (string)$input->getOption('user'); - } - - return $_ENV['NOTIFY_USER'] ?? $_SERVER['NOTIFY_USER'] ?? null; - } - - private function getPasswordOption(InputInterface $input): ?string { - if ($input->getOption('password')) { - return (string)$input->getOption('password'); - } - - return $_ENV['NOTIFY_PASSWORD'] ?? $_SERVER['NOTIFY_PASSWORD'] ?? null; - } - protected function execute(InputInterface $input, OutputInterface $output): int { - $mount = $this->globalService->getStorage($input->getArgument('mount_id')); - if (is_null($mount)) { - $output->writeln('<error>Mount not found</error>'); + [$mount, $storage] = $this->createStorage($input, $output); + if ($storage === null) { return self::FAILURE; } - $noAuth = false; - - $userOption = $this->getUserOption($input); - $passwordOption = $this->getPasswordOption($input); - - // if only the user is provided, we get the user object to pass along to the auth backend - // this allows using saved user credentials - $user = ($userOption && !$passwordOption) ? $this->userManager->get($userOption) : null; - - try { - $authBackend = $mount->getAuthMechanism(); - $authBackend->manipulateStorageConfig($mount, $user); - } catch (InsufficientDataForMeaningfulAnswerException $e) { - $noAuth = true; - } catch (StorageNotAvailableException $e) { - $noAuth = true; - } - if ($userOption) { - $mount->setBackendOption('user', $userOption); - } - if ($passwordOption) { - $mount->setBackendOption('password', $passwordOption); - } - - try { - $backend = $mount->getBackend(); - $backend->manipulateStorageConfig($mount, $user); - } catch (InsufficientDataForMeaningfulAnswerException $e) { - $noAuth = true; - } catch (StorageNotAvailableException $e) { - $noAuth = true; - } - - try { - $storage = $this->createStorage($mount); - } catch (\Exception $e) { - $output->writeln('<error>Error while trying to create storage</error>'); - if ($noAuth) { - $output->writeln('<error>Username and/or password required</error>'); - } - return self::FAILURE; - } if (!$storage instanceof INotifyStorage) { $output->writeln('<error>Mount of type "' . $mount->getBackend()->getText() . '" does not support active update notifications</error>'); return self::FAILURE; @@ -179,7 +95,7 @@ class Notify extends Base { $this->selfTest($storage, $notifyHandler, $output); } - $notifyHandler->listen(function (IChange $change) use ($mount, $output, $dryRun) { + $notifyHandler->listen(function (IChange $change) use ($mount, $output, $dryRun): void { $this->logUpdate($change, $output); if ($change instanceof IRenameChange) { $this->markParentAsOutdated($mount->getId(), $change->getTargetPath(), $output, $dryRun); @@ -189,11 +105,6 @@ class Notify extends Base { return self::SUCCESS; } - private function createStorage(StorageConfig $mount): IStorage { - $class = $mount->getBackend()->getStorageClass(); - return new $class($mount->getBackendOptions()); - } - private function markParentAsOutdated($mountId, $path, OutputInterface $output, bool $dryRun): void { $parent = ltrim(dirname($path), '/'); if ($parent === '.') { @@ -225,7 +136,7 @@ class Notify extends Base { $storageIds = array_values(array_unique($storageIds)); if ($dryRun) { - $output->writeln(" dry-run: skipping database write"); + $output->writeln(' dry-run: skipping database write'); } else { $result = $this->updateParent($storageIds, $parent); if ($result === 0) { @@ -257,7 +168,7 @@ class Notify extends Base { } private function getStorageIds(int $mountId, string $path): array { - $pathHash = md5(trim((string)\OC_Util::normalizeUnicode($path), '/')); + $pathHash = md5(trim(\OC_Util::normalizeUnicode($path), '/')); $qb = $this->connection->getQueryBuilder(); return $qb ->select('storage_id', 'user_id') @@ -270,7 +181,7 @@ class Notify extends Base { } private function updateParent(array $storageIds, string $parent): int { - $pathHash = md5(trim((string)\OC_Util::normalizeUnicode($parent), '/')); + $pathHash = md5(trim(\OC_Util::normalizeUnicode($parent), '/')); $qb = $this->connection->getQueryBuilder(); return $qb ->update('filecache') @@ -304,7 +215,7 @@ class Notify extends Base { private function selfTest(IStorage $storage, INotifyHandler $notifyHandler, OutputInterface $output): void { usleep(100 * 1000); //give time for the notify to start if (!$storage->file_put_contents('/.nc_test_file.txt', 'test content')) { - $output->writeln("Failed to create test file for self-test"); + $output->writeln('Failed to create test file for self-test'); return; } $storage->mkdir('/.nc_test_folder'); diff --git a/apps/files_external/lib/Command/Option.php b/apps/files_external/lib/Command/Option.php index 8ef423e2699..3fda3fcb3cf 100644 --- a/apps/files_external/lib/Command/Option.php +++ b/apps/files_external/lib/Command/Option.php @@ -1,24 +1,9 @@ <?php + /** - * @copyright Copyright (c) 2016, ownCloud, Inc. - * - * @author Joas Schilling <coding@schilljs.com> - * @author Robin Appelman <robin@icewind.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/> - * + * SPDX-FileCopyrightText: 2019-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only */ namespace OCA\Files_External\Command; @@ -54,7 +39,7 @@ class Option extends Config { if (!is_string($value)) { // show bools and objects correctly $value = json_encode($value); } - $output->writeln($value); + $output->writeln((string)$value); } /** diff --git a/apps/files_external/lib/Command/Scan.php b/apps/files_external/lib/Command/Scan.php new file mode 100644 index 00000000000..75d98878baa --- /dev/null +++ b/apps/files_external/lib/Command/Scan.php @@ -0,0 +1,166 @@ +<?php + +declare(strict_types=1); +/** + * SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace OCA\Files_External\Command; + +use OC\Files\Cache\Scanner; +use OCA\Files_External\Service\GlobalStoragesService; +use OCP\IUserManager; +use OCP\Lock\LockedException; +use Symfony\Component\Console\Helper\Table; +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 Scan extends StorageAuthBase { + protected float $execTime = 0; + protected int $foldersCounter = 0; + protected int $filesCounter = 0; + + public function __construct( + GlobalStoragesService $globalService, + IUserManager $userManager, + ) { + parent::__construct($globalService, $userManager); + } + + protected function configure(): void { + $this + ->setName('files_external:scan') + ->setDescription('Scan an external storage for changed files') + ->addArgument( + 'mount_id', + InputArgument::REQUIRED, + 'the mount id of the mount to scan' + )->addOption( + 'user', + 'u', + InputOption::VALUE_REQUIRED, + 'The username for the remote mount (required only for some mount configuration that don\'t store credentials)' + )->addOption( + 'password', + 'p', + InputOption::VALUE_REQUIRED, + 'The password for the remote mount (required only for some mount configuration that don\'t store credentials)' + )->addOption( + 'path', + '', + InputOption::VALUE_OPTIONAL, + 'The path in the storage to scan', + '' + )->addOption( + 'unscanned', + '', + InputOption::VALUE_NONE, + 'only scan files which are marked as not fully scanned' + ); + parent::configure(); + } + + protected function execute(InputInterface $input, OutputInterface $output): int { + [, $storage] = $this->createStorage($input, $output); + if ($storage === null) { + return 1; + } + + $path = $input->getOption('path'); + + $this->execTime = -microtime(true); + + /** @var Scanner $scanner */ + $scanner = $storage->getScanner(); + + $scanner->listen('\OC\Files\Cache\Scanner', 'scanFile', function (string $path) use ($output): void { + $output->writeln("\tFile\t<info>$path</info>", OutputInterface::VERBOSITY_VERBOSE); + ++$this->filesCounter; + $this->abortIfInterrupted(); + }); + + $scanner->listen('\OC\Files\Cache\Scanner', 'scanFolder', function (string $path) use ($output): void { + $output->writeln("\tFolder\t<info>$path</info>", OutputInterface::VERBOSITY_VERBOSE); + ++$this->foldersCounter; + $this->abortIfInterrupted(); + }); + + try { + if ($input->getOption('unscanned')) { + if ($path !== '') { + $output->writeln('<error>--unscanned is mutually exclusive with --path</error>'); + return 1; + } + $scanner->backgroundScan(); + } else { + $scanner->scan($path); + } + } catch (LockedException $e) { + if (is_string($e->getReadablePath()) && str_starts_with($e->getReadablePath(), 'scanner::')) { + if ($e->getReadablePath() === 'scanner::') { + $output->writeln('<error>Another process is already scanning this storage</error>'); + } else { + $output->writeln('<error>Another process is already scanning \'' . substr($e->getReadablePath(), strlen('scanner::')) . '\' in this storage</error>'); + } + } else { + throw $e; + } + } + + $this->presentStats($output); + + return 0; + } + + /** + * @param OutputInterface $output + */ + protected function presentStats(OutputInterface $output): void { + // Stop the timer + $this->execTime += microtime(true); + + $headers = [ + 'Folders', 'Files', 'Elapsed time' + ]; + + $this->showSummary($headers, [], $output); + } + + /** + * Shows a summary of operations + * + * @param string[] $headers + * @param string[] $rows + * @param OutputInterface $output + */ + protected function showSummary(array $headers, array $rows, OutputInterface $output): void { + $niceDate = $this->formatExecTime(); + if (!$rows) { + $rows = [ + $this->foldersCounter, + $this->filesCounter, + $niceDate, + ]; + } + $table = new Table($output); + $table + ->setHeaders($headers) + ->setRows([$rows]); + $table->render(); + } + + + /** + * Formats microtime into a human readable format + * + * @return string + */ + protected function formatExecTime(): string { + $secs = round($this->execTime); + # convert seconds into HH:MM:SS form + return sprintf('%02d:%02d:%02d', ($secs / 3600), ($secs / 60 % 60), $secs % 60); + } +} diff --git a/apps/files_external/lib/Command/StorageAuthBase.php b/apps/files_external/lib/Command/StorageAuthBase.php new file mode 100644 index 00000000000..6f830a08a60 --- /dev/null +++ b/apps/files_external/lib/Command/StorageAuthBase.php @@ -0,0 +1,114 @@ +<?php + +declare(strict_types=1); +/** + * SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace OCA\Files_External\Command; + +use OC\Core\Command\Base; +use OCA\Files_External\Lib\InsufficientDataForMeaningfulAnswerException; +use OCA\Files_External\Lib\StorageConfig; +use OCA\Files_External\NotFoundException; +use OCA\Files_External\Service\GlobalStoragesService; +use OCP\Files\Storage\IStorage; +use OCP\Files\StorageNotAvailableException; +use OCP\IUserManager; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; + +abstract class StorageAuthBase extends Base { + public function __construct( + protected GlobalStoragesService $globalService, + protected IUserManager $userManager, + ) { + parent::__construct(); + } + + private function getUserOption(InputInterface $input): ?string { + if ($input->getOption('user')) { + return (string)$input->getOption('user'); + } + + return $_ENV['NOTIFY_USER'] ?? $_SERVER['NOTIFY_USER'] ?? null; + } + + private function getPasswordOption(InputInterface $input): ?string { + if ($input->getOption('password')) { + return (string)$input->getOption('password'); + } + + return $_ENV['NOTIFY_PASSWORD'] ?? $_SERVER['NOTIFY_PASSWORD'] ?? null; + } + + /** + * @param InputInterface $input + * @param OutputInterface $output + * @return array + * @psalm-return array{0: StorageConfig, 1: IStorage}|array{0: null, 1: null} + */ + protected function createStorage(InputInterface $input, OutputInterface $output): array { + try { + /** @var StorageConfig|null $mount */ + $mount = $this->globalService->getStorage((int)$input->getArgument('mount_id')); + } catch (NotFoundException $e) { + $output->writeln('<error>Mount not found</error>'); + return [null, null]; + } + if (is_null($mount)) { + $output->writeln('<error>Mount not found</error>'); + return [null, null]; + } + $noAuth = false; + + $userOption = $this->getUserOption($input); + $passwordOption = $this->getPasswordOption($input); + + // if only the user is provided, we get the user object to pass along to the auth backend + // this allows using saved user credentials + $user = ($userOption && !$passwordOption) ? $this->userManager->get($userOption) : null; + + try { + $authBackend = $mount->getAuthMechanism(); + $authBackend->manipulateStorageConfig($mount, $user); + } catch (InsufficientDataForMeaningfulAnswerException $e) { + $noAuth = true; + } catch (StorageNotAvailableException $e) { + $noAuth = true; + } + + if ($userOption) { + $mount->setBackendOption('user', $userOption); + } + if ($passwordOption) { + $mount->setBackendOption('password', $passwordOption); + } + + try { + $backend = $mount->getBackend(); + $backend->manipulateStorageConfig($mount, $user); + } catch (InsufficientDataForMeaningfulAnswerException $e) { + $noAuth = true; + } catch (StorageNotAvailableException $e) { + $noAuth = true; + } + + try { + $class = $mount->getBackend()->getStorageClass(); + /** @var IStorage $storage */ + $storage = new $class($mount->getBackendOptions()); + if (!$storage->test()) { + throw new \Exception(); + } + return [$mount, $storage]; + } catch (\Exception $e) { + $output->writeln('<error>Error while trying to create storage</error>'); + if ($noAuth) { + $output->writeln('<error>Username and/or password required</error>'); + } + return [null, null]; + } + } +} diff --git a/apps/files_external/lib/Command/Verify.php b/apps/files_external/lib/Command/Verify.php index 590206d2099..ecebbe0f7e6 100644 --- a/apps/files_external/lib/Command/Verify.php +++ b/apps/files_external/lib/Command/Verify.php @@ -1,40 +1,24 @@ <?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> - * - * @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/> - * + * SPDX-FileCopyrightText: 2019-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only */ namespace OCA\Files_External\Command; use OC\Core\Command\Base; use OCA\Files_External\Lib\InsufficientDataForMeaningfulAnswerException; use OCA\Files_External\Lib\StorageConfig; +use OCA\Files_External\MountConfig; use OCA\Files_External\NotFoundException; use OCA\Files_External\Service\GlobalStoragesService; +use OCP\AppFramework\Http; use OCP\Files\StorageNotAvailableException; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; -use Symfony\Component\HttpFoundation\Response; class Verify extends Base { public function __construct( @@ -68,7 +52,7 @@ class Verify extends Base { $mount = $this->globalService->getStorage($mountId); } catch (NotFoundException $e) { $output->writeln('<error>Mount with id "' . $mountId . ' not found, check "occ files_external:list" to get available mounts"</error>'); - return Response::HTTP_NOT_FOUND; + return Http::STATUS_NOT_FOUND; } $this->updateStorageStatus($mount, $configInput, $output); @@ -110,10 +94,9 @@ class Verify extends Base { $backend = $storage->getBackend(); // update status (can be time-consuming) $storage->setStatus( - \OCA\Files_External\MountConfig::getBackendStatus( + MountConfig::getBackendStatus( $backend->getStorageClass(), $storage->getBackendOptions(), - false ) ); } catch (InsufficientDataForMeaningfulAnswerException $e) { |