diff options
Diffstat (limited to 'apps/files_external/lib/Command/Scan.php')
-rw-r--r-- | apps/files_external/lib/Command/Scan.php | 166 |
1 files changed, 166 insertions, 0 deletions
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); + } +} |