Completed
Push — master ( 1f210b...f38d36 )
by Lukas
33:45 queued 21:22
created

ScanAppData::execute()   B

Complexity

Conditions 4
Paths 8

Size

Total Lines 29
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 12
nc 8
nop 2
dl 0
loc 29
rs 8.5806
c 0
b 0
f 0
1
<?php
2
3
4
namespace OCA\Files\Command;
5
6
use Doctrine\DBAL\Connection;
7
use OC\Core\Command\Base;
8
use OC\Core\Command\InterruptedException;
9
use OC\ForbiddenException;
10
use OCP\Files\IRootFolder;
11
use OCP\Files\NotFoundException;
12
use OCP\Files\StorageNotAvailableException;
13
use OCP\IConfig;
14
use OCP\IDBConnection;
15
use OCP\IUserManager;
16
use Symfony\Component\Console\Input\InputArgument;
17
use Symfony\Component\Console\Input\InputInterface;
18
use Symfony\Component\Console\Input\InputOption;
19
use Symfony\Component\Console\Output\OutputInterface;
20
use Symfony\Component\Console\Helper\Table;
21
22
class ScanAppData extends Base {
23
24
	/** @var IRootFolder */
25
	protected $root;
26
	/** @var IConfig */
27
	protected $config;
28
	/** @var float */
29
	protected $execTime = 0;
30
	/** @var int */
31
	protected $foldersCounter = 0;
32
	/** @var int */
33
	protected $filesCounter = 0;
34
35
	public function __construct(IRootFolder $rootFolder, IConfig $config) {
36
		parent::__construct();
37
38
		$this->root = $rootFolder;
39
		$this->config = $config;
40
	}
41
42
	protected function configure() {
43
		parent::configure();
44
45
		$this
46
			->setName('files:scan-app-data')
47
			->setDescription('rescan the AppData folder')
48
			->addOption(
49
				'quiet',
50
				'q',
51
				InputOption::VALUE_NONE,
52
				'suppress any output'
53
			)
54
			->addOption(
55
				'verbose',
56
				'-v|vv|vvv',
57
				InputOption::VALUE_NONE,
58
				'verbose the output'
59
			);
60
	}
61
62 View Code Duplication
	public function checkScanWarning($fullPath, OutputInterface $output) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
63
		$normalizedPath = basename(\OC\Files\Filesystem::normalizePath($fullPath));
64
		$path = basename($fullPath);
65
66
		if ($normalizedPath !== $path) {
67
			$output->writeln("\t<error>Entry \"" . $fullPath . '" will not be accessible due to incompatible encoding</error>');
68
		}
69
	}
70
71
	protected function scanFiles($verbose, OutputInterface $output) {
72
		try {
73
			$appData = $this->getAppDataFolder();
74
		} catch (NotFoundException $e) {
75
			$output->writeln('NoAppData folder found');
76
			return;
77
		}
78
79
		$connection = $this->reconnectToDatabase($output);
80
		$scanner = new \OC\Files\Utils\Scanner(null, $connection, \OC::$server->getLogger());
81
		# check on each file/folder if there was a user interrupt (ctrl-c) and throw an exception
82
		# printout and count
83 View Code Duplication
		if ($verbose) {
84
			$scanner->listen('\OC\Files\Utils\Scanner', 'scanFile', function ($path) use ($output) {
85
				$output->writeln("\tFile   <info>$path</info>");
86
				$this->filesCounter += 1;
87
				if ($this->hasBeenInterrupted()) {
88
					throw new InterruptedException();
89
				}
90
			});
91
			$scanner->listen('\OC\Files\Utils\Scanner', 'scanFolder', function ($path) use ($output) {
92
				$output->writeln("\tFolder <info>$path</info>");
93
				$this->foldersCounter += 1;
94
				if ($this->hasBeenInterrupted()) {
95
					throw new InterruptedException();
96
				}
97
			});
98
			$scanner->listen('\OC\Files\Utils\Scanner', 'StorageNotAvailable', function (StorageNotAvailableException $e) use ($output) {
99
				$output->writeln("Error while scanning, storage not available (" . $e->getMessage() . ")");
100
			});
101
			# count only
102
		} else {
103
			$scanner->listen('\OC\Files\Utils\Scanner', 'scanFile', function () use ($output) {
104
				$this->filesCounter += 1;
105
				if ($this->hasBeenInterrupted()) {
106
					throw new InterruptedException();
107
				}
108
			});
109
			$scanner->listen('\OC\Files\Utils\Scanner', 'scanFolder', function () use ($output) {
110
				$this->foldersCounter += 1;
111
				if ($this->hasBeenInterrupted()) {
112
					throw new InterruptedException();
113
				}
114
			});
115
		}
116
		$scanner->listen('\OC\Files\Utils\Scanner', 'scanFile', function($path) use ($output) {
117
			$this->checkScanWarning($path, $output);
118
		});
119
		$scanner->listen('\OC\Files\Utils\Scanner', 'scanFolder', function($path) use ($output) {
120
			$this->checkScanWarning($path, $output);
121
		});
122
123
		try {
124
			$scanner->scan($appData->getPath());
125
		} catch (ForbiddenException $e) {
126
			$output->writeln("<error>Storage not writable</error>");
127
			$output->writeln("Make sure you're running the scan command only as the user the web server runs as");
128
		} catch (InterruptedException $e) {
129
			# exit the function if ctrl-c has been pressed
130
			$output->writeln('Interrupted by user');
131
		} catch (\Exception $e) {
132
			$output->writeln('<error>Exception during scan: ' . $e->getMessage() . '</error>');
133
			$output->writeln('<error>' . $e->getTraceAsString() . '</error>');
134
		}
135
	}
136
137
138
	protected function execute(InputInterface $input, OutputInterface $output) {
139
		# no messaging level option means: no full printout but statistics
140
		# $quiet   means no print at all
141
		# $verbose means full printout including statistics
142
		# -q	-v	full	stat
143
		#  0	 0	no	yes
144
		#  0	 1	yes	yes
145
		#  1	--	no	no  (quiet overrules verbose)
146
		$verbose = $input->getOption('verbose');
147
		$quiet = $input->getOption('quiet');
148
		# restrict the verbosity level to VERBOSITY_VERBOSE
149
		if ($output->getVerbosity() > OutputInterface::VERBOSITY_VERBOSE) {
150
			$output->setVerbosity(OutputInterface::VERBOSITY_VERBOSE);
151
		}
152
		if ($quiet) {
153
			$verbose = false;
154
		}
155
156
		$output->writeln("\nScanning AppData for files");
157
158
		$this->initTools();
159
160
		$this->scanFiles($verbose, $output);
161
162
		# stat: printout statistics if $quiet was not set
163
		if (!$quiet) {
164
			$this->presentStats($output);
165
		}
166
	}
167
168
	/**
169
	 * Initialises some useful tools for the Command
170
	 */
171
	protected function initTools() {
172
		// Start the timer
173
		$this->execTime = -microtime(true);
0 ignored issues
show
Documentation Bug introduced by
The property $execTime was declared of type double, but -microtime(true) is of type integer. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
174
		// Convert PHP errors to exceptions
175
		set_error_handler([$this, 'exceptionErrorHandler'], E_ALL);
176
	}
177
178
	/**
179
	 * Processes PHP errors as exceptions in order to be able to keep track of problems
180
	 *
181
	 * @see https://secure.php.net/manual/en/function.set-error-handler.php
182
	 *
183
	 * @param int $severity the level of the error raised
184
	 * @param string $message
185
	 * @param string $file the filename that the error was raised in
186
	 * @param int $line the line number the error was raised
187
	 *
188
	 * @throws \ErrorException
189
	 */
190
	public function exceptionErrorHandler($severity, $message, $file, $line) {
191
		if (!(error_reporting() & $severity)) {
192
			// This error code is not included in error_reporting
193
			return;
194
		}
195
		throw new \ErrorException($message, 0, $severity, $file, $line);
196
	}
197
198
	/**
199
	 * @param OutputInterface $output
200
	 */
201 View Code Duplication
	protected function presentStats(OutputInterface $output) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
202
		// Stop the timer
203
		$this->execTime += microtime(true);
204
		$output->writeln("");
205
206
		$headers = [
207
			'Folders', 'Files', 'Elapsed time'
208
		];
209
210
		$this->showSummary($headers, null, $output);
211
	}
212
213
	/**
214
	 * Shows a summary of operations
215
	 *
216
	 * @param string[] $headers
217
	 * @param string[] $rows
218
	 * @param OutputInterface $output
219
	 */
220 View Code Duplication
	protected function showSummary($headers, $rows, OutputInterface $output) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
221
		$niceDate = $this->formatExecTime();
222
		if (!$rows) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $rows of type string[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
223
			$rows = [
224
				$this->foldersCounter,
225
				$this->filesCounter,
226
				$niceDate,
227
			];
228
		}
229
		$table = new Table($output);
230
		$table
231
			->setHeaders($headers)
232
			->setRows([$rows]);
233
		$table->render();
234
	}
235
236
237
	/**
238
	 * Formats microtime into a human readable format
239
	 *
240
	 * @return string
241
	 */
242
	protected function formatExecTime() {
243
		list($secs, $tens) = explode('.', sprintf("%.1f", ($this->execTime)));
0 ignored issues
show
Unused Code introduced by
The assignment to $tens is unused. Consider omitting it like so list($first,,$third).

This checks looks for assignemnts to variables using the list(...) function, where not all assigned variables are subsequently used.

Consider the following code example.

<?php

function returnThreeValues() {
    return array('a', 'b', 'c');
}

list($a, $b, $c) = returnThreeValues();

print $a . " - " . $c;

Only the variables $a and $c are used. There was no need to assign $b.

Instead, the list call could have been.

list($a,, $c) = returnThreeValues();
Loading history...
244
245
		# if you want to have microseconds add this:   . '.' . $tens;
246
		return date('H:i:s', $secs);
247
	}
248
249
	/**
250
	 * @return \OCP\IDBConnection
251
	 */
252 View Code Duplication
	protected function reconnectToDatabase(OutputInterface $output) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
253
		/** @var Connection | IDBConnection $connection*/
254
		$connection = \OC::$server->getDatabaseConnection();
255
		try {
256
			$connection->close();
257
		} catch (\Exception $ex) {
258
			$output->writeln("<info>Error while disconnecting from database: {$ex->getMessage()}</info>");
259
		}
260
		while (!$connection->isConnected()) {
0 ignored issues
show
Bug introduced by
The method isConnected() does not exist on OCP\IDBConnection. Did you maybe mean connect()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
261
			try {
262
				$connection->connect();
263
			} catch (\Exception $ex) {
264
				$output->writeln("<info>Error while re-connecting to database: {$ex->getMessage()}</info>");
265
				sleep(60);
266
			}
267
		}
268
		return $connection;
269
	}
270
271
	/**
272
	 * @return \OCP\Files\Folder
273
	 * @throws NotFoundException
274
	 */
275
	private function getAppDataFolder() {
276
		$instanceId = $this->config->getSystemValue('instanceid', null);
277
278
		if ($instanceId === null) {
279
			throw new NotFoundException();
280
		}
281
282
		return $this->root->get('appdata_'.$instanceId);
283
	}
284
}
285