]> source.dussan.org Git - nextcloud-server.git/commitdiff
Statistics output for ./occ files:scan
authormartin.mattel@diemattels.at <martin.mattel@diemattels.at>
Tue, 15 Dec 2015 17:45:49 +0000 (18:45 +0100)
committermartin.mattel@diemattels.at <martin.mattel@diemattels.at>
Tue, 15 Dec 2015 18:05:05 +0000 (19:05 +0100)
apps/files/command/scan.php

index 31ae555e0419e61ce88d78c5a789017781e31fba..7e00d8a2312686b7646ff66727881ee970c83489 100644 (file)
@@ -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   <info>$path</info>");
+                               $this->filesCounter += 1;
                        });
                        $scanner->listen('\OC\Files\Utils\Scanner', 'scanFolder', function ($path) use ($output) {
                                $output->writeln("Scanning folder <info>$path</info>");
+                               $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("<error>Please specify the user id to scan, \"--all\" to scan for all users or \"--path=...\"</error>");
                        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("<error>Unknown user $user</error>");
                        }
                }
+
+               # 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;
+       }
+
 }