@@ -19,148 +19,148 @@ |
||
| 19 | 19 | use Symfony\Component\Console\Output\OutputInterface; |
| 20 | 20 | |
| 21 | 21 | class Scan extends StorageAuthBase { |
| 22 | - protected float $execTime = 0; |
|
| 23 | - protected int $foldersCounter = 0; |
|
| 24 | - protected int $filesCounter = 0; |
|
| 25 | - |
|
| 26 | - public function __construct( |
|
| 27 | - GlobalStoragesService $globalService, |
|
| 28 | - IUserManager $userManager, |
|
| 29 | - ) { |
|
| 30 | - parent::__construct($globalService, $userManager); |
|
| 31 | - } |
|
| 32 | - |
|
| 33 | - protected function configure(): void { |
|
| 34 | - $this |
|
| 35 | - ->setName('files_external:scan') |
|
| 36 | - ->setDescription('Scan an external storage for changed files') |
|
| 37 | - ->addArgument( |
|
| 38 | - 'mount_id', |
|
| 39 | - InputArgument::REQUIRED, |
|
| 40 | - 'the mount id of the mount to scan' |
|
| 41 | - )->addOption( |
|
| 42 | - 'user', |
|
| 43 | - 'u', |
|
| 44 | - InputOption::VALUE_REQUIRED, |
|
| 45 | - 'The username for the remote mount (required only for some mount configuration that don\'t store credentials)' |
|
| 46 | - )->addOption( |
|
| 47 | - 'password', |
|
| 48 | - 'p', |
|
| 49 | - InputOption::VALUE_REQUIRED, |
|
| 50 | - 'The password for the remote mount (required only for some mount configuration that don\'t store credentials)' |
|
| 51 | - )->addOption( |
|
| 52 | - 'path', |
|
| 53 | - '', |
|
| 54 | - InputOption::VALUE_OPTIONAL, |
|
| 55 | - 'The path in the storage to scan', |
|
| 56 | - '' |
|
| 57 | - )->addOption( |
|
| 58 | - 'unscanned', |
|
| 59 | - '', |
|
| 60 | - InputOption::VALUE_NONE, |
|
| 61 | - 'only scan files which are marked as not fully scanned' |
|
| 62 | - ); |
|
| 63 | - parent::configure(); |
|
| 64 | - } |
|
| 65 | - |
|
| 66 | - protected function execute(InputInterface $input, OutputInterface $output): int { |
|
| 67 | - [, $storage] = $this->createStorage($input, $output); |
|
| 68 | - if ($storage === null) { |
|
| 69 | - return 1; |
|
| 70 | - } |
|
| 71 | - |
|
| 72 | - $path = $input->getOption('path'); |
|
| 73 | - |
|
| 74 | - $this->execTime = -microtime(true); |
|
| 75 | - |
|
| 76 | - /** @var Scanner $scanner */ |
|
| 77 | - $scanner = $storage->getScanner(); |
|
| 78 | - |
|
| 79 | - $scanner->listen('\OC\Files\Cache\Scanner', 'scanFile', function (string $path) use ($output): void { |
|
| 80 | - $output->writeln("\tFile\t<info>$path</info>", OutputInterface::VERBOSITY_VERBOSE); |
|
| 81 | - ++$this->filesCounter; |
|
| 82 | - $this->abortIfInterrupted(); |
|
| 83 | - }); |
|
| 84 | - |
|
| 85 | - $scanner->listen('\OC\Files\Cache\Scanner', 'scanFolder', function (string $path) use ($output): void { |
|
| 86 | - $output->writeln("\tFolder\t<info>$path</info>", OutputInterface::VERBOSITY_VERBOSE); |
|
| 87 | - ++$this->foldersCounter; |
|
| 88 | - $this->abortIfInterrupted(); |
|
| 89 | - }); |
|
| 90 | - |
|
| 91 | - try { |
|
| 92 | - if ($input->getOption('unscanned')) { |
|
| 93 | - if ($path !== '') { |
|
| 94 | - $output->writeln('<error>--unscanned is mutually exclusive with --path</error>'); |
|
| 95 | - return 1; |
|
| 96 | - } |
|
| 97 | - $scanner->backgroundScan(); |
|
| 98 | - } else { |
|
| 99 | - $scanner->scan($path); |
|
| 100 | - } |
|
| 101 | - } catch (LockedException $e) { |
|
| 102 | - if (is_string($e->getReadablePath()) && str_starts_with($e->getReadablePath(), 'scanner::')) { |
|
| 103 | - if ($e->getReadablePath() === 'scanner::') { |
|
| 104 | - $output->writeln('<error>Another process is already scanning this storage</error>'); |
|
| 105 | - } else { |
|
| 106 | - $output->writeln('<error>Another process is already scanning \'' . substr($e->getReadablePath(), strlen('scanner::')) . '\' in this storage</error>'); |
|
| 107 | - } |
|
| 108 | - } else { |
|
| 109 | - throw $e; |
|
| 110 | - } |
|
| 111 | - } |
|
| 112 | - |
|
| 113 | - $this->presentStats($output); |
|
| 114 | - |
|
| 115 | - return 0; |
|
| 116 | - } |
|
| 117 | - |
|
| 118 | - /** |
|
| 119 | - * @param OutputInterface $output |
|
| 120 | - */ |
|
| 121 | - protected function presentStats(OutputInterface $output): void { |
|
| 122 | - // Stop the timer |
|
| 123 | - $this->execTime += microtime(true); |
|
| 124 | - |
|
| 125 | - $headers = [ |
|
| 126 | - 'Folders', 'Files', 'Elapsed time' |
|
| 127 | - ]; |
|
| 128 | - |
|
| 129 | - $this->showSummary($headers, [], $output); |
|
| 130 | - } |
|
| 131 | - |
|
| 132 | - /** |
|
| 133 | - * Shows a summary of operations |
|
| 134 | - * |
|
| 135 | - * @param string[] $headers |
|
| 136 | - * @param string[] $rows |
|
| 137 | - * @param OutputInterface $output |
|
| 138 | - */ |
|
| 139 | - protected function showSummary(array $headers, array $rows, OutputInterface $output): void { |
|
| 140 | - $niceDate = $this->formatExecTime(); |
|
| 141 | - if (!$rows) { |
|
| 142 | - $rows = [ |
|
| 143 | - $this->foldersCounter, |
|
| 144 | - $this->filesCounter, |
|
| 145 | - $niceDate, |
|
| 146 | - ]; |
|
| 147 | - } |
|
| 148 | - $table = new Table($output); |
|
| 149 | - $table |
|
| 150 | - ->setHeaders($headers) |
|
| 151 | - ->setRows([$rows]); |
|
| 152 | - $table->render(); |
|
| 153 | - } |
|
| 154 | - |
|
| 155 | - |
|
| 156 | - /** |
|
| 157 | - * Formats microtime into a human readable format |
|
| 158 | - * |
|
| 159 | - * @return string |
|
| 160 | - */ |
|
| 161 | - protected function formatExecTime(): string { |
|
| 162 | - $secs = round($this->execTime); |
|
| 163 | - # convert seconds into HH:MM:SS form |
|
| 164 | - return sprintf('%02d:%02d:%02d', ($secs / 3600), ($secs / 60 % 60), $secs % 60); |
|
| 165 | - } |
|
| 22 | + protected float $execTime = 0; |
|
| 23 | + protected int $foldersCounter = 0; |
|
| 24 | + protected int $filesCounter = 0; |
|
| 25 | + |
|
| 26 | + public function __construct( |
|
| 27 | + GlobalStoragesService $globalService, |
|
| 28 | + IUserManager $userManager, |
|
| 29 | + ) { |
|
| 30 | + parent::__construct($globalService, $userManager); |
|
| 31 | + } |
|
| 32 | + |
|
| 33 | + protected function configure(): void { |
|
| 34 | + $this |
|
| 35 | + ->setName('files_external:scan') |
|
| 36 | + ->setDescription('Scan an external storage for changed files') |
|
| 37 | + ->addArgument( |
|
| 38 | + 'mount_id', |
|
| 39 | + InputArgument::REQUIRED, |
|
| 40 | + 'the mount id of the mount to scan' |
|
| 41 | + )->addOption( |
|
| 42 | + 'user', |
|
| 43 | + 'u', |
|
| 44 | + InputOption::VALUE_REQUIRED, |
|
| 45 | + 'The username for the remote mount (required only for some mount configuration that don\'t store credentials)' |
|
| 46 | + )->addOption( |
|
| 47 | + 'password', |
|
| 48 | + 'p', |
|
| 49 | + InputOption::VALUE_REQUIRED, |
|
| 50 | + 'The password for the remote mount (required only for some mount configuration that don\'t store credentials)' |
|
| 51 | + )->addOption( |
|
| 52 | + 'path', |
|
| 53 | + '', |
|
| 54 | + InputOption::VALUE_OPTIONAL, |
|
| 55 | + 'The path in the storage to scan', |
|
| 56 | + '' |
|
| 57 | + )->addOption( |
|
| 58 | + 'unscanned', |
|
| 59 | + '', |
|
| 60 | + InputOption::VALUE_NONE, |
|
| 61 | + 'only scan files which are marked as not fully scanned' |
|
| 62 | + ); |
|
| 63 | + parent::configure(); |
|
| 64 | + } |
|
| 65 | + |
|
| 66 | + protected function execute(InputInterface $input, OutputInterface $output): int { |
|
| 67 | + [, $storage] = $this->createStorage($input, $output); |
|
| 68 | + if ($storage === null) { |
|
| 69 | + return 1; |
|
| 70 | + } |
|
| 71 | + |
|
| 72 | + $path = $input->getOption('path'); |
|
| 73 | + |
|
| 74 | + $this->execTime = -microtime(true); |
|
| 75 | + |
|
| 76 | + /** @var Scanner $scanner */ |
|
| 77 | + $scanner = $storage->getScanner(); |
|
| 78 | + |
|
| 79 | + $scanner->listen('\OC\Files\Cache\Scanner', 'scanFile', function (string $path) use ($output): void { |
|
| 80 | + $output->writeln("\tFile\t<info>$path</info>", OutputInterface::VERBOSITY_VERBOSE); |
|
| 81 | + ++$this->filesCounter; |
|
| 82 | + $this->abortIfInterrupted(); |
|
| 83 | + }); |
|
| 84 | + |
|
| 85 | + $scanner->listen('\OC\Files\Cache\Scanner', 'scanFolder', function (string $path) use ($output): void { |
|
| 86 | + $output->writeln("\tFolder\t<info>$path</info>", OutputInterface::VERBOSITY_VERBOSE); |
|
| 87 | + ++$this->foldersCounter; |
|
| 88 | + $this->abortIfInterrupted(); |
|
| 89 | + }); |
|
| 90 | + |
|
| 91 | + try { |
|
| 92 | + if ($input->getOption('unscanned')) { |
|
| 93 | + if ($path !== '') { |
|
| 94 | + $output->writeln('<error>--unscanned is mutually exclusive with --path</error>'); |
|
| 95 | + return 1; |
|
| 96 | + } |
|
| 97 | + $scanner->backgroundScan(); |
|
| 98 | + } else { |
|
| 99 | + $scanner->scan($path); |
|
| 100 | + } |
|
| 101 | + } catch (LockedException $e) { |
|
| 102 | + if (is_string($e->getReadablePath()) && str_starts_with($e->getReadablePath(), 'scanner::')) { |
|
| 103 | + if ($e->getReadablePath() === 'scanner::') { |
|
| 104 | + $output->writeln('<error>Another process is already scanning this storage</error>'); |
|
| 105 | + } else { |
|
| 106 | + $output->writeln('<error>Another process is already scanning \'' . substr($e->getReadablePath(), strlen('scanner::')) . '\' in this storage</error>'); |
|
| 107 | + } |
|
| 108 | + } else { |
|
| 109 | + throw $e; |
|
| 110 | + } |
|
| 111 | + } |
|
| 112 | + |
|
| 113 | + $this->presentStats($output); |
|
| 114 | + |
|
| 115 | + return 0; |
|
| 116 | + } |
|
| 117 | + |
|
| 118 | + /** |
|
| 119 | + * @param OutputInterface $output |
|
| 120 | + */ |
|
| 121 | + protected function presentStats(OutputInterface $output): void { |
|
| 122 | + // Stop the timer |
|
| 123 | + $this->execTime += microtime(true); |
|
| 124 | + |
|
| 125 | + $headers = [ |
|
| 126 | + 'Folders', 'Files', 'Elapsed time' |
|
| 127 | + ]; |
|
| 128 | + |
|
| 129 | + $this->showSummary($headers, [], $output); |
|
| 130 | + } |
|
| 131 | + |
|
| 132 | + /** |
|
| 133 | + * Shows a summary of operations |
|
| 134 | + * |
|
| 135 | + * @param string[] $headers |
|
| 136 | + * @param string[] $rows |
|
| 137 | + * @param OutputInterface $output |
|
| 138 | + */ |
|
| 139 | + protected function showSummary(array $headers, array $rows, OutputInterface $output): void { |
|
| 140 | + $niceDate = $this->formatExecTime(); |
|
| 141 | + if (!$rows) { |
|
| 142 | + $rows = [ |
|
| 143 | + $this->foldersCounter, |
|
| 144 | + $this->filesCounter, |
|
| 145 | + $niceDate, |
|
| 146 | + ]; |
|
| 147 | + } |
|
| 148 | + $table = new Table($output); |
|
| 149 | + $table |
|
| 150 | + ->setHeaders($headers) |
|
| 151 | + ->setRows([$rows]); |
|
| 152 | + $table->render(); |
|
| 153 | + } |
|
| 154 | + |
|
| 155 | + |
|
| 156 | + /** |
|
| 157 | + * Formats microtime into a human readable format |
|
| 158 | + * |
|
| 159 | + * @return string |
|
| 160 | + */ |
|
| 161 | + protected function formatExecTime(): string { |
|
| 162 | + $secs = round($this->execTime); |
|
| 163 | + # convert seconds into HH:MM:SS form |
|
| 164 | + return sprintf('%02d:%02d:%02d', ($secs / 3600), ($secs / 60 % 60), $secs % 60); |
|
| 165 | + } |
|
| 166 | 166 | } |