aboutsummaryrefslogtreecommitdiffstats
path: root/apps/files/lib/Command/ScanAppData.php
diff options
context:
space:
mode:
Diffstat (limited to 'apps/files/lib/Command/ScanAppData.php')
-rw-r--r--apps/files/lib/Command/ScanAppData.php243
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);
}
}