diff options
Diffstat (limited to 'apps/files/lib/Command/ScanAppData.php')
-rw-r--r-- | apps/files/lib/Command/ScanAppData.php | 243 |
1 files changed, 93 insertions, 150 deletions
diff --git a/apps/files/lib/Command/ScanAppData.php b/apps/files/lib/Command/ScanAppData.php index f347cb868b1..0e08c6a8cfe 100644 --- a/apps/files/lib/Command/ScanAppData.php +++ b/apps/files/lib/Command/ScanAppData.php @@ -1,194 +1,149 @@ <?php + /** - * - * - * @author Morris Jobke <hey@morrisjobke.de> - * @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\Command; -use Doctrine\DBAL\Connection; use OC\Core\Command\Base; use OC\Core\Command\InterruptedException; +use OC\DB\Connection; +use OC\DB\ConnectionAdapter; +use OC\Files\Utils\Scanner; use OC\ForbiddenException; +use OCP\EventDispatcher\IEventDispatcher; +use OCP\Files\Folder; use OCP\Files\IRootFolder; +use OCP\Files\Node; use OCP\Files\NotFoundException; use OCP\Files\StorageNotAvailableException; use OCP\IConfig; -use OCP\IDBConnection; +use OCP\Server; +use Psr\Log\LoggerInterface; +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; -use Symfony\Component\Console\Helper\Table; class ScanAppData extends Base { + protected float $execTime = 0; - /** @var IRootFolder */ - protected $root; - /** @var IConfig */ - protected $config; - /** @var float */ - protected $execTime = 0; - /** @var int */ - protected $foldersCounter = 0; - /** @var int */ - protected $filesCounter = 0; - - public function __construct(IRootFolder $rootFolder, IConfig $config) { - parent::__construct(); + protected int $foldersCounter = 0; + + protected int $filesCounter = 0; - $this->root = $rootFolder; - $this->config = $config; + public function __construct( + protected IRootFolder $rootFolder, + protected IConfig $config, + ) { + parent::__construct(); } - protected function configure() { + protected function configure(): void { parent::configure(); $this ->setName('files:scan-app-data') - ->setDescription('rescan the AppData folder') - ->addOption( - 'quiet', - 'q', - InputOption::VALUE_NONE, - 'suppress any output' - ) - ->addOption( - 'verbose', - '-v|vv|vvv', - InputOption::VALUE_NONE, - 'verbose the output' - ); - } + ->setDescription('rescan the AppData folder'); - public function checkScanWarning($fullPath, OutputInterface $output) { - $normalizedPath = basename(\OC\Files\Filesystem::normalizePath($fullPath)); - $path = basename($fullPath); - - if ($normalizedPath !== $path) { - $output->writeln("\t<error>Entry \"" . $fullPath . '" will not be accessible due to incompatible encoding</error>'); - } + $this->addArgument('folder', InputArgument::OPTIONAL, 'The appdata subfolder to scan', ''); } - protected function scanFiles($verbose, OutputInterface $output) { + protected function scanFiles(OutputInterface $output, string $folder): int { try { + /** @var Folder $appData */ $appData = $this->getAppDataFolder(); } catch (NotFoundException $e) { - $output->writeln('NoAppData folder found'); - return; + $output->writeln('<error>NoAppData folder found</error>'); + return self::FAILURE; + } + + if ($folder !== '') { + try { + $appData = $appData->get($folder); + } catch (NotFoundException $e) { + $output->writeln('<error>Could not find folder: ' . $folder . '</error>'); + return self::FAILURE; + } } $connection = $this->reconnectToDatabase($output); - $scanner = new \OC\Files\Utils\Scanner(null, $connection, \OC::$server->getLogger()); + $scanner = new Scanner( + null, + new ConnectionAdapter($connection), + Server::get(IEventDispatcher::class), + Server::get(LoggerInterface::class) + ); + # check on each file/folder if there was a user interrupt (ctrl-c) and throw an exception - # printout and count - if ($verbose) { - $scanner->listen('\OC\Files\Utils\Scanner', 'scanFile', function ($path) use ($output) { - $output->writeln("\tFile <info>$path</info>"); - $this->filesCounter += 1; - if ($this->hasBeenInterrupted()) { - throw new InterruptedException(); - } - }); - $scanner->listen('\OC\Files\Utils\Scanner', 'scanFolder', function ($path) use ($output) { - $output->writeln("\tFolder <info>$path</info>"); - $this->foldersCounter += 1; - if ($this->hasBeenInterrupted()) { - throw new InterruptedException(); - } - }); - $scanner->listen('\OC\Files\Utils\Scanner', 'StorageNotAvailable', function (StorageNotAvailableException $e) use ($output) { - $output->writeln("Error while scanning, storage not available (" . $e->getMessage() . ")"); - }); - # count only - } else { - $scanner->listen('\OC\Files\Utils\Scanner', 'scanFile', function () use ($output) { - $this->filesCounter += 1; - if ($this->hasBeenInterrupted()) { - throw new InterruptedException(); - } - }); - $scanner->listen('\OC\Files\Utils\Scanner', 'scanFolder', function () use ($output) { - $this->foldersCounter += 1; - if ($this->hasBeenInterrupted()) { - throw new InterruptedException(); - } - }); - } - $scanner->listen('\OC\Files\Utils\Scanner', 'scanFile', function($path) use ($output) { - $this->checkScanWarning($path, $output); + $scanner->listen('\OC\Files\Utils\Scanner', 'scanFile', function ($path) use ($output): void { + $output->writeln("\tFile <info>$path</info>", OutputInterface::VERBOSITY_VERBOSE); + ++$this->filesCounter; + $this->abortIfInterrupted(); }); - $scanner->listen('\OC\Files\Utils\Scanner', 'scanFolder', function($path) use ($output) { - $this->checkScanWarning($path, $output); + + $scanner->listen('\OC\Files\Utils\Scanner', 'scanFolder', function ($path) use ($output): void { + $output->writeln("\tFolder <info>$path</info>", OutputInterface::VERBOSITY_VERBOSE); + ++$this->foldersCounter; + $this->abortIfInterrupted(); + }); + + $scanner->listen('\OC\Files\Utils\Scanner', 'StorageNotAvailable', function (StorageNotAvailableException $e) use ($output): void { + $output->writeln('Error while scanning, storage not available (' . $e->getMessage() . ')', OutputInterface::VERBOSITY_VERBOSE); + }); + + $scanner->listen('\OC\Files\Utils\Scanner', 'normalizedNameMismatch', function ($fullPath) use ($output): void { + $output->writeln("\t<error>Entry \"" . $fullPath . '" will not be accessible due to incompatible encoding</error>'); }); try { $scanner->scan($appData->getPath()); } catch (ForbiddenException $e) { - $output->writeln("<error>Storage not writable</error>"); - $output->writeln("Make sure you're running the scan command only as the user the web server runs as"); + $output->writeln('<error>Storage not writable</error>'); + $output->writeln('<info>Make sure you\'re running the scan command only as the user the web server runs as</info>'); + return self::FAILURE; } catch (InterruptedException $e) { # exit the function if ctrl-c has been pressed - $output->writeln('Interrupted by user'); + $output->writeln('<info>Interrupted by user</info>'); + return self::FAILURE; } catch (NotFoundException $e) { $output->writeln('<error>Path not found: ' . $e->getMessage() . '</error>'); + return self::FAILURE; } catch (\Exception $e) { $output->writeln('<error>Exception during scan: ' . $e->getMessage() . '</error>'); $output->writeln('<error>' . $e->getTraceAsString() . '</error>'); + return self::FAILURE; } + + return self::SUCCESS; } - protected function execute(InputInterface $input, OutputInterface $output) { - # no messaging level option means: no full printout but statistics - # $quiet means no print at all - # $verbose means full printout including statistics - # -q -v full stat - # 0 0 no yes - # 0 1 yes yes - # 1 -- no no (quiet overrules verbose) - $verbose = $input->getOption('verbose'); - $quiet = $input->getOption('quiet'); + protected function execute(InputInterface $input, OutputInterface $output): int { # restrict the verbosity level to VERBOSITY_VERBOSE if ($output->getVerbosity() > OutputInterface::VERBOSITY_VERBOSE) { $output->setVerbosity(OutputInterface::VERBOSITY_VERBOSE); } - if ($quiet) { - $verbose = false; - } - $output->writeln("\nScanning AppData for files"); + $output->writeln('Scanning AppData for files'); + $output->writeln(''); - $this->initTools(); + $folder = $input->getArgument('folder'); - $this->scanFiles($verbose, $output); + $this->initTools(); - # stat: printout statistics if $quiet was not set - if (!$quiet) { + $exitCode = $this->scanFiles($output, $folder); + if ($exitCode === 0) { $this->presentStats($output); } + return $exitCode; } /** * Initialises some useful tools for the Command */ - protected function initTools() { + protected function initTools(): void { // Start the timer $this->execTime = -microtime(true); // Convert PHP errors to exceptions @@ -198,7 +153,7 @@ class ScanAppData extends Base { /** * Processes PHP errors as exceptions in order to be able to keep track of problems * - * @see https://secure.php.net/manual/en/function.set-error-handler.php + * @see https://www.php.net/manual/en/function.set-error-handler.php * * @param int $severity the level of the error raised * @param string $message @@ -215,13 +170,9 @@ class ScanAppData extends Base { throw new \ErrorException($message, 0, $severity, $file, $line); } - /** - * @param OutputInterface $output - */ - protected function presentStats(OutputInterface $output) { + protected function presentStats(OutputInterface $output): void { // Stop the timer $this->execTime += microtime(true); - $output->writeln(""); $headers = [ 'Folders', 'Files', 'Elapsed time' @@ -235,9 +186,8 @@ class ScanAppData extends Base { * * @param string[] $headers * @param string[] $rows - * @param OutputInterface $output */ - protected function showSummary($headers, $rows, OutputInterface $output) { + protected function showSummary($headers, $rows, OutputInterface $output): void { $niceDate = $this->formatExecTime(); if (!$rows) { $rows = [ @@ -255,23 +205,17 @@ class ScanAppData extends Base { /** - * Formats microtime into a human readable format - * - * @return string + * Formats microtime into a human-readable format */ - protected function formatExecTime() { - list($secs, ) = explode('.', sprintf("%.1f", ($this->execTime))); - - # if you want to have microseconds add this: . '.' . $tens; - return date('H:i:s', $secs); + protected function formatExecTime(): string { + $secs = round($this->execTime); + # convert seconds into HH:MM:SS form + return sprintf('%02d:%02d:%02d', (int)($secs / 3600), ((int)($secs / 60) % 60), (int)$secs % 60); } - /** - * @return \OCP\IDBConnection - */ - protected function reconnectToDatabase(OutputInterface $output) { - /** @var Connection | IDBConnection $connection*/ - $connection = \OC::$server->getDatabaseConnection(); + protected function reconnectToDatabase(OutputInterface $output): Connection { + /** @var Connection $connection */ + $connection = Server::get(Connection::class); try { $connection->close(); } catch (\Exception $ex) { @@ -289,16 +233,15 @@ class ScanAppData extends Base { } /** - * @return \OCP\Files\Folder * @throws NotFoundException */ - private function getAppDataFolder() { + private function getAppDataFolder(): Node { $instanceId = $this->config->getSystemValue('instanceid', null); if ($instanceId === null) { throw new NotFoundException(); } - return $this->root->get('appdata_'.$instanceId); + return $this->rootFolder->get('appdata_' . $instanceId); } } |