Completed
Push — master ( 3a41db...6b6d52 )
by Morris
51:20 queued 35:40
created

Scanner::getData()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 5
nc 2
nop 1
dl 0
loc 7
rs 9.4285
c 0
b 0
f 0
1
<?php
2
/**
3
 * @copyright Copyright (c) 2016, ownCloud, Inc.
4
 *
5
 * @author Arthur Schiwon <[email protected]>
6
 * @author Björn Schießle <[email protected]>
7
 * @author Daniel Jagszent <[email protected]>
8
 * @author Joas Schilling <[email protected]>
9
 * @author Jörn Friedrich Dreyer <[email protected]>
10
 * @author Lukas Reschke <[email protected]>
11
 * @author Martin Mattel <[email protected]>
12
 * @author Morris Jobke <[email protected]>
13
 * @author Owen Winkler <[email protected]>
14
 * @author Robin Appelman <[email protected]>
15
 * @author Robin McCorkell <[email protected]>
16
 * @author Thomas Müller <[email protected]>
17
 * @author Vincent Petry <[email protected]>
18
 *
19
 * @license AGPL-3.0
20
 *
21
 * This code is free software: you can redistribute it and/or modify
22
 * it under the terms of the GNU Affero General Public License, version 3,
23
 * as published by the Free Software Foundation.
24
 *
25
 * This program is distributed in the hope that it will be useful,
26
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
27
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
28
 * GNU Affero General Public License for more details.
29
 *
30
 * You should have received a copy of the GNU Affero General Public License, version 3,
31
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
32
 *
33
 */
34
35
namespace OC\Files\Cache;
36
37
use OC\Files\Filesystem;
38
use OC\Hooks\BasicEmitter;
39
use OCP\Files\Cache\IScanner;
40
use OCP\Files\ForbiddenException;
41
use OCP\ILogger;
42
use OCP\Lock\ILockingProvider;
43
44
/**
45
 * Class Scanner
46
 *
47
 * Hooks available in scope \OC\Files\Cache\Scanner:
48
 *  - scanFile(string $path, string $storageId)
49
 *  - scanFolder(string $path, string $storageId)
50
 *  - postScanFile(string $path, string $storageId)
51
 *  - postScanFolder(string $path, string $storageId)
52
 *
53
 * @package OC\Files\Cache
54
 */
55
class Scanner extends BasicEmitter implements IScanner {
56
	/**
57
	 * @var \OC\Files\Storage\Storage $storage
58
	 */
59
	protected $storage;
60
61
	/**
62
	 * @var string $storageId
63
	 */
64
	protected $storageId;
65
66
	/**
67
	 * @var \OC\Files\Cache\Cache $cache
68
	 */
69
	protected $cache;
70
71
	/**
72
	 * @var boolean $cacheActive If true, perform cache operations, if false, do not affect cache
73
	 */
74
	protected $cacheActive;
75
76
	/**
77
	 * @var bool $useTransactions whether to use transactions
78
	 */
79
	protected $useTransactions = true;
80
81
	/**
82
	 * @var \OCP\Lock\ILockingProvider
83
	 */
84
	protected $lockingProvider;
85
86
	public function __construct(\OC\Files\Storage\Storage $storage) {
87
		$this->storage = $storage;
88
		$this->storageId = $this->storage->getId();
89
		$this->cache = $storage->getCache();
90
		$this->cacheActive = !\OC::$server->getConfig()->getSystemValue('filesystem_cache_readonly', false);
91
		$this->lockingProvider = \OC::$server->getLockingProvider();
92
	}
93
94
	/**
95
	 * Whether to wrap the scanning of a folder in a database transaction
96
	 * On default transactions are used
97
	 *
98
	 * @param bool $useTransactions
99
	 */
100
	public function setUseTransactions($useTransactions) {
101
		$this->useTransactions = $useTransactions;
102
	}
103
104
	/**
105
	 * get all the metadata of a file or folder
106
	 * *
107
	 *
108
	 * @param string $path
109
	 * @return array an array of metadata of the file
110
	 */
111
	protected function getData($path) {
112
		$data = $this->storage->getMetaData($path);
113
		if (is_null($data)) {
114
			\OCP\Util::writeLog(Scanner::class, "!!! Path '$path' is not accessible or present !!!", ILogger::DEBUG);
115
		}
116
		return $data;
117
	}
118
119
	/**
120
	 * scan a single file and store it in the cache
121
	 *
122
	 * @param string $file
123
	 * @param int $reuseExisting
124
	 * @param int $parentId
125
	 * @param array | null $cacheData existing data in the cache for the file to be scanned
126
	 * @param bool $lock set to false to disable getting an additional read lock during scanning
127
	 * @return array an array of metadata of the scanned file
128
	 * @throws \OC\ServerNotAvailableException
129
	 * @throws \OCP\Lock\LockedException
130
	 */
131
	public function scanFile($file, $reuseExisting = 0, $parentId = -1, $cacheData = null, $lock = true) {
132
		if ($file !== '') {
133
			try {
134
				$this->storage->verifyPath(dirname($file), basename($file));
135
			} catch (\Exception $e) {
136
				return null;
137
			}
138
		}
139
		// only proceed if $file is not a partial file nor a blacklisted file
140
		if (!self::isPartialFile($file) and !Filesystem::isFileBlacklisted($file)) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as and instead of && is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
141
142
			//acquire a lock
143
			if ($lock) {
144
				if ($this->storage->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) {
145
					$this->storage->acquireLock($file, ILockingProvider::LOCK_SHARED, $this->lockingProvider);
146
				}
147
			}
148
149
			try {
150
				$data = $this->getData($file);
151
			} catch (ForbiddenException $e) {
152
				if ($lock) {
153
					if ($this->storage->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) {
154
						$this->storage->releaseLock($file, ILockingProvider::LOCK_SHARED, $this->lockingProvider);
155
					}
156
				}
157
158
				return null;
159
			}
160
161
			try {
162
				if ($data) {
163
164
					// pre-emit only if it was a file. By that we avoid counting/treating folders as files
165 View Code Duplication
					if ($data['mimetype'] !== 'httpd/unix-directory') {
166
						$this->emit('\OC\Files\Cache\Scanner', 'scanFile', array($file, $this->storageId));
167
						\OC_Hook::emit('\OC\Files\Cache\Scanner', 'scan_file', array('path' => $file, 'storage' => $this->storageId));
168
					}
169
170
					$parent = dirname($file);
171
					if ($parent === '.' or $parent === '/') {
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as or instead of || is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
172
						$parent = '';
173
					}
174
					if ($parentId === -1) {
175
						$parentId = $this->cache->getParentId($file);
176
					}
177
178
					// scan the parent if it's not in the cache (id -1) and the current file is not the root folder
179
					if ($file and $parentId === -1) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as and instead of && is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
180
						$parentData = $this->scanFile($parent);
181
						if (!$parentData) {
182
							return null;
183
						}
184
						$parentId = $parentData['fileid'];
185
					}
186
					if ($parent) {
187
						$data['parent'] = $parentId;
188
					}
189
					if (is_null($cacheData)) {
190
						/** @var CacheEntry $cacheData */
191
						$cacheData = $this->cache->get($file);
192
					}
193
					if ($cacheData and $reuseExisting and isset($cacheData['fileid'])) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as and instead of && is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
194
						// prevent empty etag
195
						if (empty($cacheData['etag'])) {
196
							$etag = $data['etag'];
197
						} else {
198
							$etag = $cacheData['etag'];
199
						}
200
						$fileId = $cacheData['fileid'];
201
						$data['fileid'] = $fileId;
202
						// only reuse data if the file hasn't explicitly changed
203
						if (isset($data['storage_mtime']) && isset($cacheData['storage_mtime']) && $data['storage_mtime'] === $cacheData['storage_mtime']) {
204
							$data['mtime'] = $cacheData['mtime'];
205
							if (($reuseExisting & self::REUSE_SIZE) && ($data['size'] === -1)) {
206
								$data['size'] = $cacheData['size'];
207
							}
208
							if ($reuseExisting & self::REUSE_ETAG) {
209
								$data['etag'] = $etag;
210
							}
211
						}
212
						// Only update metadata that has changed
213
						$newData = array_diff_assoc($data, $cacheData->getData());
214
					} else {
215
						$newData = $data;
216
						$fileId = -1;
217
					}
218
					if (!empty($newData)) {
219
						// Reset the checksum if the data has changed
220
						$newData['checksum'] = '';
221
						$data['fileid'] = $this->addToCache($file, $newData, $fileId);
222
					}
223
					if (isset($cacheData['size'])) {
224
						$data['oldSize'] = $cacheData['size'];
225
					} else {
226
						$data['oldSize'] = 0;
227
					}
228
229
					if (isset($cacheData['encrypted'])) {
230
						$data['encrypted'] = $cacheData['encrypted'];
231
					}
232
233
					// post-emit only if it was a file. By that we avoid counting/treating folders as files
234 View Code Duplication
					if ($data['mimetype'] !== 'httpd/unix-directory') {
235
						$this->emit('\OC\Files\Cache\Scanner', 'postScanFile', array($file, $this->storageId));
236
						\OC_Hook::emit('\OC\Files\Cache\Scanner', 'post_scan_file', array('path' => $file, 'storage' => $this->storageId));
237
					}
238
239
				} else {
240
					$this->removeFromCache($file);
241
				}
242
			} catch (\Exception $e) {
243
				if ($lock) {
244
					if ($this->storage->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) {
245
						$this->storage->releaseLock($file, ILockingProvider::LOCK_SHARED, $this->lockingProvider);
246
					}
247
				}
248
				throw $e;
249
			}
250
251
			//release the acquired lock
252
			if ($lock) {
253
				if ($this->storage->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) {
254
					$this->storage->releaseLock($file, ILockingProvider::LOCK_SHARED, $this->lockingProvider);
255
				}
256
			}
257
258
			if ($data && !isset($data['encrypted'])) {
259
				$data['encrypted'] = false;
260
			}
261
			return $data;
262
		}
263
264
		return null;
265
	}
266
267
	protected function removeFromCache($path) {
268
		\OC_Hook::emit('Scanner', 'removeFromCache', array('file' => $path));
269
		$this->emit('\OC\Files\Cache\Scanner', 'removeFromCache', array($path));
270
		if ($this->cacheActive) {
271
			$this->cache->remove($path);
272
		}
273
	}
274
275
	/**
276
	 * @param string $path
277
	 * @param array $data
278
	 * @param int $fileId
279
	 * @return int the id of the added file
280
	 */
281
	protected function addToCache($path, $data, $fileId = -1) {
282
		if (isset($data['scan_permissions'])) {
283
			$data['permissions'] = $data['scan_permissions'];
284
		}
285
		\OC_Hook::emit('Scanner', 'addToCache', array('file' => $path, 'data' => $data));
286
		$this->emit('\OC\Files\Cache\Scanner', 'addToCache', array($path, $this->storageId, $data));
287 View Code Duplication
		if ($this->cacheActive) {
288
			if ($fileId !== -1) {
289
				$this->cache->update($fileId, $data);
290
				return $fileId;
291
			} else {
292
				return $this->cache->put($path, $data);
293
			}
294
		} else {
295
			return -1;
296
		}
297
	}
298
299
	/**
300
	 * @param string $path
301
	 * @param array $data
302
	 * @param int $fileId
303
	 */
304
	protected function updateCache($path, $data, $fileId = -1) {
305
		\OC_Hook::emit('Scanner', 'addToCache', array('file' => $path, 'data' => $data));
306
		$this->emit('\OC\Files\Cache\Scanner', 'updateCache', array($path, $this->storageId, $data));
307 View Code Duplication
		if ($this->cacheActive) {
308
			if ($fileId !== -1) {
309
				$this->cache->update($fileId, $data);
310
			} else {
311
				$this->cache->put($path, $data);
312
			}
313
		}
314
	}
315
316
	/**
317
	 * scan a folder and all it's children
318
	 *
319
	 * @param string $path
320
	 * @param bool $recursive
321
	 * @param int $reuse
322
	 * @param bool $lock set to false to disable getting an additional read lock during scanning
323
	 * @return array an array of the meta data of the scanned file or folder
324
	 */
325
	public function scan($path, $recursive = self::SCAN_RECURSIVE, $reuse = -1, $lock = true) {
326 View Code Duplication
		if ($reuse === -1) {
327
			$reuse = ($recursive === self::SCAN_SHALLOW) ? self::REUSE_ETAG | self::REUSE_SIZE : self::REUSE_ETAG;
328
		}
329 View Code Duplication
		if ($lock) {
330
			if ($this->storage->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) {
331
				$this->storage->acquireLock('scanner::' . $path, ILockingProvider::LOCK_EXCLUSIVE, $this->lockingProvider);
332
				$this->storage->acquireLock($path, ILockingProvider::LOCK_SHARED, $this->lockingProvider);
333
			}
334
		}
335
		try {
336
			$data = $this->scanFile($path, $reuse, -1, null, $lock);
337
			if ($data and $data['mimetype'] === 'httpd/unix-directory') {
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as and instead of && is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
338
				$size = $this->scanChildren($path, $recursive, $reuse, $data['fileid'], $lock);
339
				$data['size'] = $size;
340
			}
341
		} finally {
342 View Code Duplication
			if ($lock) {
343
				if ($this->storage->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) {
344
					$this->storage->releaseLock($path, ILockingProvider::LOCK_SHARED, $this->lockingProvider);
345
					$this->storage->releaseLock('scanner::' . $path, ILockingProvider::LOCK_EXCLUSIVE, $this->lockingProvider);
346
				}
347
			}
348
		}
349
		return $data;
350
	}
351
352
	/**
353
	 * Get the children currently in the cache
354
	 *
355
	 * @param int $folderId
356
	 * @return array[]
357
	 */
358
	protected function getExistingChildren($folderId) {
359
		$existingChildren = array();
360
		$children = $this->cache->getFolderContentsById($folderId);
361
		foreach ($children as $child) {
362
			$existingChildren[$child['name']] = $child;
363
		}
364
		return $existingChildren;
365
	}
366
367
	/**
368
	 * Get the children from the storage
369
	 *
370
	 * @param string $folder
371
	 * @return string[]
372
	 */
373
	protected function getNewChildren($folder) {
374
		$children = array();
375
		if ($dh = $this->storage->opendir($folder)) {
376
			if (is_resource($dh)) {
377
				while (($file = readdir($dh)) !== false) {
378
					if (!Filesystem::isIgnoredDir($file)) {
379
						$children[] = trim(\OC\Files\Filesystem::normalizePath($file), '/');
380
					}
381
				}
382
			}
383
		}
384
		return $children;
385
	}
386
387
	/**
388
	 * scan all the files and folders in a folder
389
	 *
390
	 * @param string $path
391
	 * @param bool $recursive
392
	 * @param int $reuse
393
	 * @param int $folderId id for the folder to be scanned
394
	 * @param bool $lock set to false to disable getting an additional read lock during scanning
395
	 * @return int the size of the scanned folder or -1 if the size is unknown at this stage
396
	 */
397
	protected function scanChildren($path, $recursive = self::SCAN_RECURSIVE, $reuse = -1, $folderId = null, $lock = true) {
398 View Code Duplication
		if ($reuse === -1) {
399
			$reuse = ($recursive === self::SCAN_SHALLOW) ? self::REUSE_ETAG | self::REUSE_SIZE : self::REUSE_ETAG;
400
		}
401
		$this->emit('\OC\Files\Cache\Scanner', 'scanFolder', array($path, $this->storageId));
402
		$size = 0;
403
		if (!is_null($folderId)) {
404
			$folderId = $this->cache->getId($path);
405
		}
406
		$childQueue = $this->handleChildren($path, $recursive, $reuse, $folderId, $lock, $size);
407
408
		foreach ($childQueue as $child => $childId) {
409
			$childSize = $this->scanChildren($child, $recursive, $reuse, $childId, $lock);
410
			if ($childSize === -1) {
411
				$size = -1;
412
			} else if ($size !== -1) {
413
				$size += $childSize;
414
			}
415
		}
416
		if ($this->cacheActive) {
417
			$this->cache->update($folderId, array('size' => $size));
418
		}
419
		$this->emit('\OC\Files\Cache\Scanner', 'postScanFolder', array($path, $this->storageId));
420
		return $size;
421
	}
422
423
	private function handleChildren($path, $recursive, $reuse, $folderId, $lock, &$size) {
424
		// we put this in it's own function so it cleans up the memory before we start recursing
425
		$existingChildren = $this->getExistingChildren($folderId);
426
		$newChildren = $this->getNewChildren($path);
427
428
		if ($this->useTransactions) {
429
			\OC::$server->getDatabaseConnection()->beginTransaction();
430
		}
431
432
		$exceptionOccurred = false;
433
		$childQueue = [];
434
		foreach ($newChildren as $file) {
435
			$child = $path ? $path . '/' . $file : $file;
436
			try {
437
				$existingData = isset($existingChildren[$file]) ? $existingChildren[$file] : null;
438
				$data = $this->scanFile($child, $reuse, $folderId, $existingData, $lock);
439
				if ($data) {
440
					if ($data['mimetype'] === 'httpd/unix-directory' and $recursive === self::SCAN_RECURSIVE) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as and instead of && is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
441
						$childQueue[$child] = $data['fileid'];
442
					} else if ($data['mimetype'] === 'httpd/unix-directory' and $recursive === self::SCAN_RECURSIVE_INCOMPLETE and $data['size'] === -1) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as and instead of && is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
443
						// only recurse into folders which aren't fully scanned
444
						$childQueue[$child] = $data['fileid'];
445
					} else if ($data['size'] === -1) {
446
						$size = -1;
447
					} else if ($size !== -1) {
448
						$size += $data['size'];
449
					}
450
				}
451
			} catch (\Doctrine\DBAL\DBALException $ex) {
0 ignored issues
show
Bug introduced by
The class Doctrine\DBAL\DBALException does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
452
				// might happen if inserting duplicate while a scanning
453
				// process is running in parallel
454
				// log and ignore
455
				if ($this->useTransactions) {
456
					\OC::$server->getDatabaseConnection()->rollback();
457
					\OC::$server->getDatabaseConnection()->beginTransaction();
458
				}
459
				\OC::$server->getLogger()->logException($ex, [
460
					'message' => 'Exception while scanning file "' . $child . '"',
461
					'level' => ILogger::DEBUG,
462
					'app' => 'core',
463
				]);
464
				$exceptionOccurred = true;
465
			} catch (\OCP\Lock\LockedException $e) {
466
				if ($this->useTransactions) {
467
					\OC::$server->getDatabaseConnection()->rollback();
468
				}
469
				throw $e;
470
			}
471
		}
472
		$removedChildren = \array_diff(array_keys($existingChildren), $newChildren);
473
		foreach ($removedChildren as $childName) {
474
			$child = $path ? $path . '/' . $childName : $childName;
475
			$this->removeFromCache($child);
476
		}
477
		if ($this->useTransactions) {
478
			\OC::$server->getDatabaseConnection()->commit();
479
		}
480
		if ($exceptionOccurred) {
481
			// It might happen that the parallel scan process has already
482
			// inserted mimetypes but those weren't available yet inside the transaction
483
			// To make sure to have the updated mime types in such cases,
484
			// we reload them here
485
			\OC::$server->getMimeTypeLoader()->reset();
486
		}
487
		return $childQueue;
488
	}
489
490
	/**
491
	 * check if the file should be ignored when scanning
492
	 * NOTE: files with a '.part' extension are ignored as well!
493
	 *       prevents unfinished put requests to be scanned
494
	 *
495
	 * @param string $file
496
	 * @return boolean
497
	 */
498
	public static function isPartialFile($file) {
499
		if (pathinfo($file, PATHINFO_EXTENSION) === 'part') {
500
			return true;
501
		}
502
		if (strpos($file, '.part/') !== false) {
503
			return true;
504
		}
505
506
		return false;
507
	}
508
509
	/**
510
	 * walk over any folders that are not fully scanned yet and scan them
511
	 */
512
	public function backgroundScan() {
513
		if (!$this->cache->inCache('')) {
514
			$this->runBackgroundScanJob(function () {
515
				$this->scan('', self::SCAN_RECURSIVE, self::REUSE_ETAG);
516
			}, '');
517
		} else {
518
			$lastPath = null;
519
			while (($path = $this->cache->getIncomplete()) !== false && $path !== $lastPath) {
520
				$this->runBackgroundScanJob(function () use ($path) {
521
					$this->scan($path, self::SCAN_RECURSIVE_INCOMPLETE, self::REUSE_ETAG | self::REUSE_SIZE);
0 ignored issues
show
Bug introduced by
It seems like $path defined by $this->cache->getIncomplete() on line 519 can also be of type boolean; however, OC\Files\Cache\Scanner::scan() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
Documentation introduced by
self::SCAN_RECURSIVE_INCOMPLETE is of type integer, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
522
				}, $path);
523
				// FIXME: this won't proceed with the next item, needs revamping of getIncomplete()
524
				// to make this possible
525
				$lastPath = $path;
526
			}
527
		}
528
	}
529
530
	private function runBackgroundScanJob(callable $callback, $path) {
531
		try {
532
			$callback();
533
			\OC_Hook::emit('Scanner', 'correctFolderSize', array('path' => $path));
534
			if ($this->cacheActive && $this->cache instanceof Cache) {
535
				$this->cache->correctFolderSize($path);
536
			}
537
		} catch (\OCP\Files\StorageInvalidException $e) {
538
			// skip unavailable storages
539
		} catch (\OCP\Files\StorageNotAvailableException $e) {
540
			// skip unavailable storages
541
		} catch (\OCP\Files\ForbiddenException $e) {
542
			// skip forbidden storages
543
		} catch (\OCP\Lock\LockedException $e) {
544
			// skip unavailable storages
545
		}
546
	}
547
548
	/**
549
	 * Set whether the cache is affected by scan operations
550
	 *
551
	 * @param boolean $active The active state of the cache
552
	 */
553
	public function setCacheActive($active) {
554
		$this->cacheActive = $active;
555
	}
556
}
557