From b7e8258d99732e59161046ef77133b8a42148674 Mon Sep 17 00:00:00 2001 From: "martin.mattel@diemattels.at" Date: Tue, 15 Dec 2015 18:45:49 +0100 Subject: [PATCH] Statistics output for ./occ files:scan --- apps/files/command/scan.php | 190 ++++++++++++++++++++++++++++++++++-- 1 file changed, 184 insertions(+), 6 deletions(-) diff --git a/apps/files/command/scan.php b/apps/files/command/scan.php index 31ae555e041..7e00d8a2312 100644 --- a/apps/files/command/scan.php +++ b/apps/files/command/scan.php @@ -32,6 +32,7 @@ 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 Scan extends Command { @@ -39,6 +40,15 @@ class Scan extends Command { * @var \OC\User\Manager $userManager */ private $userManager; + /** @var float */ + protected $execTime = 0; + /** @var int */ + protected $foldersCounter = 0; + /** @var int */ + protected $filesCounter = 0; + /** @var bool */ + protected $interrupted = false; + public function __construct(\OC\User\Manager $userManager) { $this->userManager = $userManager; @@ -64,7 +74,13 @@ class Scan extends Command { 'quiet', 'q', InputOption::VALUE_NONE, - 'suppress output' + 'suppress any output' + ) + ->addOption( + 'verbose', + '-v|vv|vvv', + InputOption::VALUE_NONE, + 'verbose the output' ) ->addOption( 'all', @@ -74,19 +90,31 @@ class Scan extends Command { ); } - protected function scanFiles($user, $path, $quiet, OutputInterface $output) { + protected function scanFiles($user, $path, $verbose, OutputInterface $output) { $scanner = new \OC\Files\Utils\Scanner($user, \OC::$server->getDatabaseConnection(), \OC::$server->getLogger()); - if (!$quiet) { + # printout and count + if ($verbose) { $scanner->listen('\OC\Files\Utils\Scanner', 'scanFile', function ($path) use ($output) { $output->writeln("Scanning file $path"); + $this->filesCounter += 1; }); $scanner->listen('\OC\Files\Utils\Scanner', 'scanFolder', function ($path) use ($output) { $output->writeln("Scanning folder $path"); + $this->foldersCounter += 1; }); $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 ($path) use ($output) { + $this->filesCounter += 1; + }); + $scanner->listen('\OC\Files\Utils\Scanner', 'scanFolder', function ($path) use ($output) { + $this->foldersCounter += 1; + }); } + try { $scanner->scan($path); } catch (ForbiddenException $e) { @@ -95,6 +123,7 @@ class Scan extends Command { } } + protected function execute(InputInterface $input, OutputInterface $output) { $inputPath = $input->getOption('path'); if ($inputPath) { @@ -106,24 +135,173 @@ class Scan extends Command { } else { $users = $input->getArgument('user_id'); } - $quiet = $input->getOption('quiet'); - if (count($users) === 0) { $output->writeln("Please specify the user id to scan, \"--all\" to scan for all users or \"--path=...\""); return; } + # 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'); + # restrict the verbosity level to VERBOSITY_VERBOSE + if ($output->getVerbosity()>OutputInterface::VERBOSITY_VERBOSE) { + $output->setVerbosity(OutputInterface::VERBOSITY_VERBOSE); + } + if ($quiet) { + $verbose = false; + } + + $this->initTools(); + foreach ($users as $user) { if (is_object($user)) { $user = $user->getUID(); } $path = $inputPath ? $inputPath : '/' . $user; if ($this->userManager->userExists($user)) { - $this->scanFiles($user, $path, $quiet, $output); + # full: printout data if $verbose was set + $this->scanFiles($user, $path, $verbose, $output); } else { $output->writeln("Unknown user $user"); } } + + # stat: printout statistics if $quiet was not set + if (!$quiet) { + $this->presentStats($output); + } + + } + + + /** + * Checks if the command was interrupted by ctrl-c + */ + protected function checkForInterruption($output) { + if ($this->hasBeenInterrupted()) { + $this->presentResults($output); + exit; + } + } + + + /** + * Initialises some useful tools for the Command + */ + protected function initTools() { + // Start the timer + $this->execTime = -microtime(true); + // Convert PHP errors to exceptions + set_error_handler([$this, 'exceptionErrorHandler'], E_ALL); + + // Collect interrupts and notify the running command + pcntl_signal(SIGTERM, [$this, 'cancelOperation']); + pcntl_signal(SIGINT, [$this, 'cancelOperation']); + } + + + /** + * Changes the status of the command to "interrupted" + * + * Gives a chance to the command to properly terminate what it's doing + */ + private function cancelOperation() { + $this->interrupted = true; + } + + + /** + * 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 + * + * @param int $severity the level of the error raised + * @param string $message + * @param string $file the filename that the error was raised in + * @param int $line the line number the error was raised + * + * @throws \ErrorException + */ + public function exceptionErrorHandler($severity, $message, $file, $line) { + if (!(error_reporting() & $severity)) { + // This error code is not included in error_reporting + return; + } + throw new \ErrorException($message, 0, $severity, $file, $line); } + + + /** + * @return bool + */ + protected function hasBeenInterrupted() { + $cancelled = false; + pcntl_signal_dispatch(); + if ($this->interrupted) { + $cancelled = true; + } + + return $cancelled; + } + + + /** + * @param OutputInterface $output + */ + protected function presentStats(OutputInterface $output) { + // Stop the timer + $this->execTime += microtime(true); + $output->writeln(""); + + $headers = [ + 'Folders', 'Files', 'Elapsed time' + ]; + + $this->showSummary($headers, null, $output); + } + + + /** + * Shows a summary of operations + * + * @param string[] $headers + * @param string[] $rows + * @param OutputInterface $output + */ + protected function showSummary($headers, $rows, OutputInterface $output) { + $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() { + list($secs, $tens) = explode('.', sprintf("%.1f", ($this->execTime))); + $niceDate = date('H:i:s', $secs) . '.' . $tens; + + return $niceDate; + } + } -- 2.39.5