Passed
Push — master ( 6b4ab0...6fae59 )
by Robin
14:23 queued 11s
created
lib/private/Files/Storage/Local.php 2 patches
Indentation   +534 added lines, -534 removed lines patch added patch discarded remove patch
@@ -56,538 +56,538 @@
 block discarded – undo
56 56
  * for local filestore, we only have to map the paths
57 57
  */
58 58
 class Local extends \OC\Files\Storage\Common {
59
-	protected $datadir;
60
-
61
-	protected $dataDirLength;
62
-
63
-	protected $realDataDir;
64
-
65
-	private IConfig $config;
66
-
67
-	private IMimeTypeDetector $mimeTypeDetector;
68
-
69
-	public function __construct($arguments) {
70
-		if (!isset($arguments['datadir']) || !is_string($arguments['datadir'])) {
71
-			throw new \InvalidArgumentException('No data directory set for local storage');
72
-		}
73
-		$this->datadir = str_replace('//', '/', $arguments['datadir']);
74
-		// some crazy code uses a local storage on root...
75
-		if ($this->datadir === '/') {
76
-			$this->realDataDir = $this->datadir;
77
-		} else {
78
-			$realPath = realpath($this->datadir) ?: $this->datadir;
79
-			$this->realDataDir = rtrim($realPath, '/') . '/';
80
-		}
81
-		if (substr($this->datadir, -1) !== '/') {
82
-			$this->datadir .= '/';
83
-		}
84
-		$this->dataDirLength = strlen($this->realDataDir);
85
-		$this->config = \OC::$server->get(IConfig::class);
86
-		$this->mimeTypeDetector = \OC::$server->get(IMimeTypeDetector::class);
87
-	}
88
-
89
-	public function __destruct() {
90
-	}
91
-
92
-	public function getId() {
93
-		return 'local::' . $this->datadir;
94
-	}
95
-
96
-	public function mkdir($path) {
97
-		$sourcePath = $this->getSourcePath($path);
98
-		$oldMask = umask(022);
99
-		$result = @mkdir($sourcePath, 0777, true);
100
-		umask($oldMask);
101
-		return $result;
102
-	}
103
-
104
-	public function rmdir($path) {
105
-		if (!$this->isDeletable($path)) {
106
-			return false;
107
-		}
108
-		try {
109
-			$it = new \RecursiveIteratorIterator(
110
-				new \RecursiveDirectoryIterator($this->getSourcePath($path)),
111
-				\RecursiveIteratorIterator::CHILD_FIRST
112
-			);
113
-			/**
114
-			 * RecursiveDirectoryIterator on an NFS path isn't iterable with foreach
115
-			 * This bug is fixed in PHP 5.5.9 or before
116
-			 * See #8376
117
-			 */
118
-			$it->rewind();
119
-			while ($it->valid()) {
120
-				/**
121
-				 * @var \SplFileInfo $file
122
-				 */
123
-				$file = $it->current();
124
-				clearstatcache(true, $this->getSourcePath($file));
125
-				if (in_array($file->getBasename(), ['.', '..'])) {
126
-					$it->next();
127
-					continue;
128
-				} elseif ($file->isDir()) {
129
-					rmdir($file->getPathname());
130
-				} elseif ($file->isFile() || $file->isLink()) {
131
-					unlink($file->getPathname());
132
-				}
133
-				$it->next();
134
-			}
135
-			clearstatcache(true, $this->getSourcePath($path));
136
-			return rmdir($this->getSourcePath($path));
137
-		} catch (\UnexpectedValueException $e) {
138
-			return false;
139
-		}
140
-	}
141
-
142
-	public function opendir($path) {
143
-		return opendir($this->getSourcePath($path));
144
-	}
145
-
146
-	public function is_dir($path) {
147
-		if (substr($path, -1) == '/') {
148
-			$path = substr($path, 0, -1);
149
-		}
150
-		return is_dir($this->getSourcePath($path));
151
-	}
152
-
153
-	public function is_file($path) {
154
-		return is_file($this->getSourcePath($path));
155
-	}
156
-
157
-	public function stat($path) {
158
-		$fullPath = $this->getSourcePath($path);
159
-		clearstatcache(true, $fullPath);
160
-		$statResult = @stat($fullPath);
161
-		if (PHP_INT_SIZE === 4 && $statResult && !$this->is_dir($path)) {
162
-			$filesize = $this->filesize($path);
163
-			$statResult['size'] = $filesize;
164
-			$statResult[7] = $filesize;
165
-		}
166
-		if (is_array($statResult)) {
167
-			$statResult['full_path'] = $fullPath;
168
-		}
169
-		return $statResult;
170
-	}
171
-
172
-	/**
173
-	 * @inheritdoc
174
-	 */
175
-	public function getMetaData($path) {
176
-		$stat = $this->stat($path);
177
-		if (!$stat) {
178
-			return null;
179
-		}
180
-
181
-		$permissions = Constants::PERMISSION_SHARE;
182
-		$statPermissions = $stat['mode'];
183
-		$isDir = ($statPermissions & 0x4000) === 0x4000 && !($statPermissions & 0x8000);
184
-		if ($statPermissions & 0x0100) {
185
-			$permissions += Constants::PERMISSION_READ;
186
-		}
187
-		if ($statPermissions & 0x0080) {
188
-			$permissions += Constants::PERMISSION_UPDATE;
189
-			if ($isDir) {
190
-				$permissions += Constants::PERMISSION_CREATE;
191
-			}
192
-		}
193
-
194
-		if (!($path === '' || $path === '/')) { // deletable depends on the parents unix permissions
195
-			$parent = dirname($stat['full_path']);
196
-			if (is_writable($parent)) {
197
-				$permissions += Constants::PERMISSION_DELETE;
198
-			}
199
-		}
200
-
201
-		$data = [];
202
-		$data['mimetype'] = $isDir ? 'httpd/unix-directory' : $this->mimeTypeDetector->detectPath($path);
203
-		$data['mtime'] = $stat['mtime'];
204
-		if ($data['mtime'] === false) {
205
-			$data['mtime'] = time();
206
-		}
207
-		if ($isDir) {
208
-			$data['size'] = -1; //unknown
209
-		} else {
210
-			$data['size'] = $stat['size'];
211
-		}
212
-		$data['etag'] = $this->calculateEtag($path, $stat);
213
-		$data['storage_mtime'] = $data['mtime'];
214
-		$data['permissions'] = $permissions;
215
-		$data['name'] = basename($path);
216
-
217
-		return $data;
218
-	}
219
-
220
-	public function filetype($path) {
221
-		$filetype = filetype($this->getSourcePath($path));
222
-		if ($filetype == 'link') {
223
-			$filetype = filetype(realpath($this->getSourcePath($path)));
224
-		}
225
-		return $filetype;
226
-	}
227
-
228
-	public function filesize($path) {
229
-		if (!$this->is_file($path)) {
230
-			return 0;
231
-		}
232
-		$fullPath = $this->getSourcePath($path);
233
-		if (PHP_INT_SIZE === 4) {
234
-			$helper = new \OC\LargeFileHelper;
235
-			return $helper->getFileSize($fullPath);
236
-		}
237
-		return filesize($fullPath);
238
-	}
239
-
240
-	public function isReadable($path) {
241
-		return is_readable($this->getSourcePath($path));
242
-	}
243
-
244
-	public function isUpdatable($path) {
245
-		return is_writable($this->getSourcePath($path));
246
-	}
247
-
248
-	public function file_exists($path) {
249
-		return file_exists($this->getSourcePath($path));
250
-	}
251
-
252
-	public function filemtime($path) {
253
-		$fullPath = $this->getSourcePath($path);
254
-		clearstatcache(true, $fullPath);
255
-		if (!$this->file_exists($path)) {
256
-			return false;
257
-		}
258
-		if (PHP_INT_SIZE === 4) {
259
-			$helper = new \OC\LargeFileHelper();
260
-			return $helper->getFileMtime($fullPath);
261
-		}
262
-		return filemtime($fullPath);
263
-	}
264
-
265
-	public function touch($path, $mtime = null) {
266
-		// sets the modification time of the file to the given value.
267
-		// If mtime is nil the current time is set.
268
-		// note that the access time of the file always changes to the current time.
269
-		if ($this->file_exists($path) and !$this->isUpdatable($path)) {
270
-			return false;
271
-		}
272
-		$oldMask = umask(022);
273
-		if (!is_null($mtime)) {
274
-			$result = @touch($this->getSourcePath($path), $mtime);
275
-		} else {
276
-			$result = @touch($this->getSourcePath($path));
277
-		}
278
-		umask($oldMask);
279
-		if ($result) {
280
-			clearstatcache(true, $this->getSourcePath($path));
281
-		}
282
-
283
-		return $result;
284
-	}
285
-
286
-	public function file_get_contents($path) {
287
-		return file_get_contents($this->getSourcePath($path));
288
-	}
289
-
290
-	public function file_put_contents($path, $data) {
291
-		$oldMask = umask(022);
292
-		$result = file_put_contents($this->getSourcePath($path), $data);
293
-		umask($oldMask);
294
-		return $result;
295
-	}
296
-
297
-	public function unlink($path) {
298
-		if ($this->is_dir($path)) {
299
-			return $this->rmdir($path);
300
-		} elseif ($this->is_file($path)) {
301
-			return unlink($this->getSourcePath($path));
302
-		} else {
303
-			return false;
304
-		}
305
-	}
306
-
307
-	private function checkTreeForForbiddenItems(string $path) {
308
-		$iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($path));
309
-		foreach ($iterator as $file) {
310
-			/** @var \SplFileInfo $file */
311
-			if (Filesystem::isFileBlacklisted($file->getBasename())) {
312
-				throw new ForbiddenException('Invalid path: ' . $file->getPathname(), false);
313
-			}
314
-		}
315
-	}
316
-
317
-	public function rename($path1, $path2) {
318
-		$srcParent = dirname($path1);
319
-		$dstParent = dirname($path2);
320
-
321
-		if (!$this->isUpdatable($srcParent)) {
322
-			\OCP\Util::writeLog('core', 'unable to rename, source directory is not writable : ' . $srcParent, ILogger::ERROR);
323
-			return false;
324
-		}
325
-
326
-		if (!$this->isUpdatable($dstParent)) {
327
-			\OCP\Util::writeLog('core', 'unable to rename, destination directory is not writable : ' . $dstParent, ILogger::ERROR);
328
-			return false;
329
-		}
330
-
331
-		if (!$this->file_exists($path1)) {
332
-			\OCP\Util::writeLog('core', 'unable to rename, file does not exists : ' . $path1, ILogger::ERROR);
333
-			return false;
334
-		}
335
-
336
-		if ($this->is_dir($path2)) {
337
-			$this->rmdir($path2);
338
-		} elseif ($this->is_file($path2)) {
339
-			$this->unlink($path2);
340
-		}
341
-
342
-		if ($this->is_dir($path1)) {
343
-			// we can't move folders across devices, use copy instead
344
-			$stat1 = stat(dirname($this->getSourcePath($path1)));
345
-			$stat2 = stat(dirname($this->getSourcePath($path2)));
346
-			if ($stat1['dev'] !== $stat2['dev']) {
347
-				$result = $this->copy($path1, $path2);
348
-				if ($result) {
349
-					$result &= $this->rmdir($path1);
350
-				}
351
-				return $result;
352
-			}
353
-
354
-			$this->checkTreeForForbiddenItems($this->getSourcePath($path1));
355
-		}
356
-
357
-		return rename($this->getSourcePath($path1), $this->getSourcePath($path2));
358
-	}
359
-
360
-	public function copy($path1, $path2) {
361
-		if ($this->is_dir($path1)) {
362
-			return parent::copy($path1, $path2);
363
-		} else {
364
-			$oldMask = umask(022);
365
-			$result = copy($this->getSourcePath($path1), $this->getSourcePath($path2));
366
-			umask($oldMask);
367
-			return $result;
368
-		}
369
-	}
370
-
371
-	public function fopen($path, $mode) {
372
-		$oldMask = umask(022);
373
-		$result = fopen($this->getSourcePath($path), $mode);
374
-		umask($oldMask);
375
-		return $result;
376
-	}
377
-
378
-	public function hash($type, $path, $raw = false) {
379
-		return hash_file($type, $this->getSourcePath($path), $raw);
380
-	}
381
-
382
-	public function free_space($path) {
383
-		$sourcePath = $this->getSourcePath($path);
384
-		// using !is_dir because $sourcePath might be a part file or
385
-		// non-existing file, so we'd still want to use the parent dir
386
-		// in such cases
387
-		if (!is_dir($sourcePath)) {
388
-			// disk_free_space doesn't work on files
389
-			$sourcePath = dirname($sourcePath);
390
-		}
391
-		$space = function_exists('disk_free_space') ? disk_free_space($sourcePath) : false;
392
-		if ($space === false || is_null($space)) {
393
-			return \OCP\Files\FileInfo::SPACE_UNKNOWN;
394
-		}
395
-		return $space;
396
-	}
397
-
398
-	public function search($query) {
399
-		return $this->searchInDir($query);
400
-	}
401
-
402
-	public function getLocalFile($path) {
403
-		return $this->getSourcePath($path);
404
-	}
405
-
406
-	public function getLocalFolder($path) {
407
-		return $this->getSourcePath($path);
408
-	}
409
-
410
-	/**
411
-	 * @param string $query
412
-	 * @param string $dir
413
-	 * @return array
414
-	 */
415
-	protected function searchInDir($query, $dir = '') {
416
-		$files = [];
417
-		$physicalDir = $this->getSourcePath($dir);
418
-		foreach (scandir($physicalDir) as $item) {
419
-			if (\OC\Files\Filesystem::isIgnoredDir($item)) {
420
-				continue;
421
-			}
422
-			$physicalItem = $physicalDir . '/' . $item;
423
-
424
-			if (strstr(strtolower($item), strtolower($query)) !== false) {
425
-				$files[] = $dir . '/' . $item;
426
-			}
427
-			if (is_dir($physicalItem)) {
428
-				$files = array_merge($files, $this->searchInDir($query, $dir . '/' . $item));
429
-			}
430
-		}
431
-		return $files;
432
-	}
433
-
434
-	/**
435
-	 * check if a file or folder has been updated since $time
436
-	 *
437
-	 * @param string $path
438
-	 * @param int $time
439
-	 * @return bool
440
-	 */
441
-	public function hasUpdated($path, $time) {
442
-		if ($this->file_exists($path)) {
443
-			return $this->filemtime($path) > $time;
444
-		} else {
445
-			return true;
446
-		}
447
-	}
448
-
449
-	/**
450
-	 * Get the source path (on disk) of a given path
451
-	 *
452
-	 * @param string $path
453
-	 * @return string
454
-	 * @throws ForbiddenException
455
-	 */
456
-	public function getSourcePath($path) {
457
-		if (Filesystem::isFileBlacklisted($path)) {
458
-			throw new ForbiddenException('Invalid path: ' . $path, false);
459
-		}
460
-
461
-		$fullPath = $this->datadir . $path;
462
-		$currentPath = $path;
463
-		$allowSymlinks = $this->config->getSystemValue('localstorage.allowsymlinks', false);
464
-		if ($allowSymlinks || $currentPath === '') {
465
-			return $fullPath;
466
-		}
467
-		$pathToResolve = $fullPath;
468
-		$realPath = realpath($pathToResolve);
469
-		while ($realPath === false) { // for non existing files check the parent directory
470
-			$currentPath = dirname($currentPath);
471
-			if ($currentPath === '' || $currentPath === '.') {
472
-				return $fullPath;
473
-			}
474
-			$realPath = realpath($this->datadir . $currentPath);
475
-		}
476
-		if ($realPath) {
477
-			$realPath = $realPath . '/';
478
-		}
479
-		if (substr($realPath, 0, $this->dataDirLength) === $this->realDataDir) {
480
-			return $fullPath;
481
-		}
482
-
483
-		\OCP\Util::writeLog('core', "Following symlinks is not allowed ('$fullPath' -> '$realPath' not inside '{$this->realDataDir}')", ILogger::ERROR);
484
-		throw new ForbiddenException('Following symlinks is not allowed', false);
485
-	}
486
-
487
-	/**
488
-	 * {@inheritdoc}
489
-	 */
490
-	public function isLocal() {
491
-		return true;
492
-	}
493
-
494
-	/**
495
-	 * get the ETag for a file or folder
496
-	 *
497
-	 * @param string $path
498
-	 * @return string
499
-	 */
500
-	public function getETag($path) {
501
-		return $this->calculateEtag($path, $this->stat($path));
502
-	}
503
-
504
-	private function calculateEtag(string $path, array $stat): string {
505
-		if ($stat['mode'] & 0x4000 && !($stat['mode'] & 0x8000)) { // is_dir & not socket
506
-			return parent::getETag($path);
507
-		} else {
508
-			if ($stat === false) {
509
-				return md5('');
510
-			}
511
-
512
-			$toHash = '';
513
-			if (isset($stat['mtime'])) {
514
-				$toHash .= $stat['mtime'];
515
-			}
516
-			if (isset($stat['ino'])) {
517
-				$toHash .= $stat['ino'];
518
-			}
519
-			if (isset($stat['dev'])) {
520
-				$toHash .= $stat['dev'];
521
-			}
522
-			if (isset($stat['size'])) {
523
-				$toHash .= $stat['size'];
524
-			}
525
-
526
-			return md5($toHash);
527
-		}
528
-	}
529
-
530
-	/**
531
-	 * @param IStorage $sourceStorage
532
-	 * @param string $sourceInternalPath
533
-	 * @param string $targetInternalPath
534
-	 * @param bool $preserveMtime
535
-	 * @return bool
536
-	 */
537
-	public function copyFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath, $preserveMtime = false) {
538
-		// Don't treat ACLStorageWrapper like local storage where copy can be done directly.
539
-		// Instead use the slower recursive copying in php from Common::copyFromStorage with
540
-		// more permissions checks.
541
-		if ($sourceStorage->instanceOfStorage(Local::class) && !$sourceStorage->instanceOfStorage('OCA\GroupFolders\ACL\ACLStorageWrapper')) {
542
-			if ($sourceStorage->instanceOfStorage(Jail::class)) {
543
-				/**
544
-				 * @var \OC\Files\Storage\Wrapper\Jail $sourceStorage
545
-				 */
546
-				$sourceInternalPath = $sourceStorage->getUnjailedPath($sourceInternalPath);
547
-			}
548
-			/**
549
-			 * @var \OC\Files\Storage\Local $sourceStorage
550
-			 */
551
-			$rootStorage = new Local(['datadir' => '/']);
552
-			return $rootStorage->copy($sourceStorage->getSourcePath($sourceInternalPath), $this->getSourcePath($targetInternalPath));
553
-		} else {
554
-			return parent::copyFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
555
-		}
556
-	}
557
-
558
-	/**
559
-	 * @param IStorage $sourceStorage
560
-	 * @param string $sourceInternalPath
561
-	 * @param string $targetInternalPath
562
-	 * @return bool
563
-	 */
564
-	public function moveFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) {
565
-		if ($sourceStorage->instanceOfStorage(Local::class)) {
566
-			if ($sourceStorage->instanceOfStorage(Jail::class)) {
567
-				/**
568
-				 * @var \OC\Files\Storage\Wrapper\Jail $sourceStorage
569
-				 */
570
-				$sourceInternalPath = $sourceStorage->getUnjailedPath($sourceInternalPath);
571
-			}
572
-			/**
573
-			 * @var \OC\Files\Storage\Local $sourceStorage
574
-			 */
575
-			$rootStorage = new Local(['datadir' => '/']);
576
-			return $rootStorage->rename($sourceStorage->getSourcePath($sourceInternalPath), $this->getSourcePath($targetInternalPath));
577
-		} else {
578
-			return parent::moveFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
579
-		}
580
-	}
581
-
582
-	public function writeStream(string $path, $stream, int $size = null): int {
583
-		$result = $this->file_put_contents($path, $stream);
584
-		if (is_resource($stream)) {
585
-			fclose($stream);
586
-		}
587
-		if ($result === false) {
588
-			throw new GenericFileException("Failed write stream to $path");
589
-		} else {
590
-			return $result;
591
-		}
592
-	}
59
+    protected $datadir;
60
+
61
+    protected $dataDirLength;
62
+
63
+    protected $realDataDir;
64
+
65
+    private IConfig $config;
66
+
67
+    private IMimeTypeDetector $mimeTypeDetector;
68
+
69
+    public function __construct($arguments) {
70
+        if (!isset($arguments['datadir']) || !is_string($arguments['datadir'])) {
71
+            throw new \InvalidArgumentException('No data directory set for local storage');
72
+        }
73
+        $this->datadir = str_replace('//', '/', $arguments['datadir']);
74
+        // some crazy code uses a local storage on root...
75
+        if ($this->datadir === '/') {
76
+            $this->realDataDir = $this->datadir;
77
+        } else {
78
+            $realPath = realpath($this->datadir) ?: $this->datadir;
79
+            $this->realDataDir = rtrim($realPath, '/') . '/';
80
+        }
81
+        if (substr($this->datadir, -1) !== '/') {
82
+            $this->datadir .= '/';
83
+        }
84
+        $this->dataDirLength = strlen($this->realDataDir);
85
+        $this->config = \OC::$server->get(IConfig::class);
86
+        $this->mimeTypeDetector = \OC::$server->get(IMimeTypeDetector::class);
87
+    }
88
+
89
+    public function __destruct() {
90
+    }
91
+
92
+    public function getId() {
93
+        return 'local::' . $this->datadir;
94
+    }
95
+
96
+    public function mkdir($path) {
97
+        $sourcePath = $this->getSourcePath($path);
98
+        $oldMask = umask(022);
99
+        $result = @mkdir($sourcePath, 0777, true);
100
+        umask($oldMask);
101
+        return $result;
102
+    }
103
+
104
+    public function rmdir($path) {
105
+        if (!$this->isDeletable($path)) {
106
+            return false;
107
+        }
108
+        try {
109
+            $it = new \RecursiveIteratorIterator(
110
+                new \RecursiveDirectoryIterator($this->getSourcePath($path)),
111
+                \RecursiveIteratorIterator::CHILD_FIRST
112
+            );
113
+            /**
114
+             * RecursiveDirectoryIterator on an NFS path isn't iterable with foreach
115
+             * This bug is fixed in PHP 5.5.9 or before
116
+             * See #8376
117
+             */
118
+            $it->rewind();
119
+            while ($it->valid()) {
120
+                /**
121
+                 * @var \SplFileInfo $file
122
+                 */
123
+                $file = $it->current();
124
+                clearstatcache(true, $this->getSourcePath($file));
125
+                if (in_array($file->getBasename(), ['.', '..'])) {
126
+                    $it->next();
127
+                    continue;
128
+                } elseif ($file->isDir()) {
129
+                    rmdir($file->getPathname());
130
+                } elseif ($file->isFile() || $file->isLink()) {
131
+                    unlink($file->getPathname());
132
+                }
133
+                $it->next();
134
+            }
135
+            clearstatcache(true, $this->getSourcePath($path));
136
+            return rmdir($this->getSourcePath($path));
137
+        } catch (\UnexpectedValueException $e) {
138
+            return false;
139
+        }
140
+    }
141
+
142
+    public function opendir($path) {
143
+        return opendir($this->getSourcePath($path));
144
+    }
145
+
146
+    public function is_dir($path) {
147
+        if (substr($path, -1) == '/') {
148
+            $path = substr($path, 0, -1);
149
+        }
150
+        return is_dir($this->getSourcePath($path));
151
+    }
152
+
153
+    public function is_file($path) {
154
+        return is_file($this->getSourcePath($path));
155
+    }
156
+
157
+    public function stat($path) {
158
+        $fullPath = $this->getSourcePath($path);
159
+        clearstatcache(true, $fullPath);
160
+        $statResult = @stat($fullPath);
161
+        if (PHP_INT_SIZE === 4 && $statResult && !$this->is_dir($path)) {
162
+            $filesize = $this->filesize($path);
163
+            $statResult['size'] = $filesize;
164
+            $statResult[7] = $filesize;
165
+        }
166
+        if (is_array($statResult)) {
167
+            $statResult['full_path'] = $fullPath;
168
+        }
169
+        return $statResult;
170
+    }
171
+
172
+    /**
173
+     * @inheritdoc
174
+     */
175
+    public function getMetaData($path) {
176
+        $stat = $this->stat($path);
177
+        if (!$stat) {
178
+            return null;
179
+        }
180
+
181
+        $permissions = Constants::PERMISSION_SHARE;
182
+        $statPermissions = $stat['mode'];
183
+        $isDir = ($statPermissions & 0x4000) === 0x4000 && !($statPermissions & 0x8000);
184
+        if ($statPermissions & 0x0100) {
185
+            $permissions += Constants::PERMISSION_READ;
186
+        }
187
+        if ($statPermissions & 0x0080) {
188
+            $permissions += Constants::PERMISSION_UPDATE;
189
+            if ($isDir) {
190
+                $permissions += Constants::PERMISSION_CREATE;
191
+            }
192
+        }
193
+
194
+        if (!($path === '' || $path === '/')) { // deletable depends on the parents unix permissions
195
+            $parent = dirname($stat['full_path']);
196
+            if (is_writable($parent)) {
197
+                $permissions += Constants::PERMISSION_DELETE;
198
+            }
199
+        }
200
+
201
+        $data = [];
202
+        $data['mimetype'] = $isDir ? 'httpd/unix-directory' : $this->mimeTypeDetector->detectPath($path);
203
+        $data['mtime'] = $stat['mtime'];
204
+        if ($data['mtime'] === false) {
205
+            $data['mtime'] = time();
206
+        }
207
+        if ($isDir) {
208
+            $data['size'] = -1; //unknown
209
+        } else {
210
+            $data['size'] = $stat['size'];
211
+        }
212
+        $data['etag'] = $this->calculateEtag($path, $stat);
213
+        $data['storage_mtime'] = $data['mtime'];
214
+        $data['permissions'] = $permissions;
215
+        $data['name'] = basename($path);
216
+
217
+        return $data;
218
+    }
219
+
220
+    public function filetype($path) {
221
+        $filetype = filetype($this->getSourcePath($path));
222
+        if ($filetype == 'link') {
223
+            $filetype = filetype(realpath($this->getSourcePath($path)));
224
+        }
225
+        return $filetype;
226
+    }
227
+
228
+    public function filesize($path) {
229
+        if (!$this->is_file($path)) {
230
+            return 0;
231
+        }
232
+        $fullPath = $this->getSourcePath($path);
233
+        if (PHP_INT_SIZE === 4) {
234
+            $helper = new \OC\LargeFileHelper;
235
+            return $helper->getFileSize($fullPath);
236
+        }
237
+        return filesize($fullPath);
238
+    }
239
+
240
+    public function isReadable($path) {
241
+        return is_readable($this->getSourcePath($path));
242
+    }
243
+
244
+    public function isUpdatable($path) {
245
+        return is_writable($this->getSourcePath($path));
246
+    }
247
+
248
+    public function file_exists($path) {
249
+        return file_exists($this->getSourcePath($path));
250
+    }
251
+
252
+    public function filemtime($path) {
253
+        $fullPath = $this->getSourcePath($path);
254
+        clearstatcache(true, $fullPath);
255
+        if (!$this->file_exists($path)) {
256
+            return false;
257
+        }
258
+        if (PHP_INT_SIZE === 4) {
259
+            $helper = new \OC\LargeFileHelper();
260
+            return $helper->getFileMtime($fullPath);
261
+        }
262
+        return filemtime($fullPath);
263
+    }
264
+
265
+    public function touch($path, $mtime = null) {
266
+        // sets the modification time of the file to the given value.
267
+        // If mtime is nil the current time is set.
268
+        // note that the access time of the file always changes to the current time.
269
+        if ($this->file_exists($path) and !$this->isUpdatable($path)) {
270
+            return false;
271
+        }
272
+        $oldMask = umask(022);
273
+        if (!is_null($mtime)) {
274
+            $result = @touch($this->getSourcePath($path), $mtime);
275
+        } else {
276
+            $result = @touch($this->getSourcePath($path));
277
+        }
278
+        umask($oldMask);
279
+        if ($result) {
280
+            clearstatcache(true, $this->getSourcePath($path));
281
+        }
282
+
283
+        return $result;
284
+    }
285
+
286
+    public function file_get_contents($path) {
287
+        return file_get_contents($this->getSourcePath($path));
288
+    }
289
+
290
+    public function file_put_contents($path, $data) {
291
+        $oldMask = umask(022);
292
+        $result = file_put_contents($this->getSourcePath($path), $data);
293
+        umask($oldMask);
294
+        return $result;
295
+    }
296
+
297
+    public function unlink($path) {
298
+        if ($this->is_dir($path)) {
299
+            return $this->rmdir($path);
300
+        } elseif ($this->is_file($path)) {
301
+            return unlink($this->getSourcePath($path));
302
+        } else {
303
+            return false;
304
+        }
305
+    }
306
+
307
+    private function checkTreeForForbiddenItems(string $path) {
308
+        $iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($path));
309
+        foreach ($iterator as $file) {
310
+            /** @var \SplFileInfo $file */
311
+            if (Filesystem::isFileBlacklisted($file->getBasename())) {
312
+                throw new ForbiddenException('Invalid path: ' . $file->getPathname(), false);
313
+            }
314
+        }
315
+    }
316
+
317
+    public function rename($path1, $path2) {
318
+        $srcParent = dirname($path1);
319
+        $dstParent = dirname($path2);
320
+
321
+        if (!$this->isUpdatable($srcParent)) {
322
+            \OCP\Util::writeLog('core', 'unable to rename, source directory is not writable : ' . $srcParent, ILogger::ERROR);
323
+            return false;
324
+        }
325
+
326
+        if (!$this->isUpdatable($dstParent)) {
327
+            \OCP\Util::writeLog('core', 'unable to rename, destination directory is not writable : ' . $dstParent, ILogger::ERROR);
328
+            return false;
329
+        }
330
+
331
+        if (!$this->file_exists($path1)) {
332
+            \OCP\Util::writeLog('core', 'unable to rename, file does not exists : ' . $path1, ILogger::ERROR);
333
+            return false;
334
+        }
335
+
336
+        if ($this->is_dir($path2)) {
337
+            $this->rmdir($path2);
338
+        } elseif ($this->is_file($path2)) {
339
+            $this->unlink($path2);
340
+        }
341
+
342
+        if ($this->is_dir($path1)) {
343
+            // we can't move folders across devices, use copy instead
344
+            $stat1 = stat(dirname($this->getSourcePath($path1)));
345
+            $stat2 = stat(dirname($this->getSourcePath($path2)));
346
+            if ($stat1['dev'] !== $stat2['dev']) {
347
+                $result = $this->copy($path1, $path2);
348
+                if ($result) {
349
+                    $result &= $this->rmdir($path1);
350
+                }
351
+                return $result;
352
+            }
353
+
354
+            $this->checkTreeForForbiddenItems($this->getSourcePath($path1));
355
+        }
356
+
357
+        return rename($this->getSourcePath($path1), $this->getSourcePath($path2));
358
+    }
359
+
360
+    public function copy($path1, $path2) {
361
+        if ($this->is_dir($path1)) {
362
+            return parent::copy($path1, $path2);
363
+        } else {
364
+            $oldMask = umask(022);
365
+            $result = copy($this->getSourcePath($path1), $this->getSourcePath($path2));
366
+            umask($oldMask);
367
+            return $result;
368
+        }
369
+    }
370
+
371
+    public function fopen($path, $mode) {
372
+        $oldMask = umask(022);
373
+        $result = fopen($this->getSourcePath($path), $mode);
374
+        umask($oldMask);
375
+        return $result;
376
+    }
377
+
378
+    public function hash($type, $path, $raw = false) {
379
+        return hash_file($type, $this->getSourcePath($path), $raw);
380
+    }
381
+
382
+    public function free_space($path) {
383
+        $sourcePath = $this->getSourcePath($path);
384
+        // using !is_dir because $sourcePath might be a part file or
385
+        // non-existing file, so we'd still want to use the parent dir
386
+        // in such cases
387
+        if (!is_dir($sourcePath)) {
388
+            // disk_free_space doesn't work on files
389
+            $sourcePath = dirname($sourcePath);
390
+        }
391
+        $space = function_exists('disk_free_space') ? disk_free_space($sourcePath) : false;
392
+        if ($space === false || is_null($space)) {
393
+            return \OCP\Files\FileInfo::SPACE_UNKNOWN;
394
+        }
395
+        return $space;
396
+    }
397
+
398
+    public function search($query) {
399
+        return $this->searchInDir($query);
400
+    }
401
+
402
+    public function getLocalFile($path) {
403
+        return $this->getSourcePath($path);
404
+    }
405
+
406
+    public function getLocalFolder($path) {
407
+        return $this->getSourcePath($path);
408
+    }
409
+
410
+    /**
411
+     * @param string $query
412
+     * @param string $dir
413
+     * @return array
414
+     */
415
+    protected function searchInDir($query, $dir = '') {
416
+        $files = [];
417
+        $physicalDir = $this->getSourcePath($dir);
418
+        foreach (scandir($physicalDir) as $item) {
419
+            if (\OC\Files\Filesystem::isIgnoredDir($item)) {
420
+                continue;
421
+            }
422
+            $physicalItem = $physicalDir . '/' . $item;
423
+
424
+            if (strstr(strtolower($item), strtolower($query)) !== false) {
425
+                $files[] = $dir . '/' . $item;
426
+            }
427
+            if (is_dir($physicalItem)) {
428
+                $files = array_merge($files, $this->searchInDir($query, $dir . '/' . $item));
429
+            }
430
+        }
431
+        return $files;
432
+    }
433
+
434
+    /**
435
+     * check if a file or folder has been updated since $time
436
+     *
437
+     * @param string $path
438
+     * @param int $time
439
+     * @return bool
440
+     */
441
+    public function hasUpdated($path, $time) {
442
+        if ($this->file_exists($path)) {
443
+            return $this->filemtime($path) > $time;
444
+        } else {
445
+            return true;
446
+        }
447
+    }
448
+
449
+    /**
450
+     * Get the source path (on disk) of a given path
451
+     *
452
+     * @param string $path
453
+     * @return string
454
+     * @throws ForbiddenException
455
+     */
456
+    public function getSourcePath($path) {
457
+        if (Filesystem::isFileBlacklisted($path)) {
458
+            throw new ForbiddenException('Invalid path: ' . $path, false);
459
+        }
460
+
461
+        $fullPath = $this->datadir . $path;
462
+        $currentPath = $path;
463
+        $allowSymlinks = $this->config->getSystemValue('localstorage.allowsymlinks', false);
464
+        if ($allowSymlinks || $currentPath === '') {
465
+            return $fullPath;
466
+        }
467
+        $pathToResolve = $fullPath;
468
+        $realPath = realpath($pathToResolve);
469
+        while ($realPath === false) { // for non existing files check the parent directory
470
+            $currentPath = dirname($currentPath);
471
+            if ($currentPath === '' || $currentPath === '.') {
472
+                return $fullPath;
473
+            }
474
+            $realPath = realpath($this->datadir . $currentPath);
475
+        }
476
+        if ($realPath) {
477
+            $realPath = $realPath . '/';
478
+        }
479
+        if (substr($realPath, 0, $this->dataDirLength) === $this->realDataDir) {
480
+            return $fullPath;
481
+        }
482
+
483
+        \OCP\Util::writeLog('core', "Following symlinks is not allowed ('$fullPath' -> '$realPath' not inside '{$this->realDataDir}')", ILogger::ERROR);
484
+        throw new ForbiddenException('Following symlinks is not allowed', false);
485
+    }
486
+
487
+    /**
488
+     * {@inheritdoc}
489
+     */
490
+    public function isLocal() {
491
+        return true;
492
+    }
493
+
494
+    /**
495
+     * get the ETag for a file or folder
496
+     *
497
+     * @param string $path
498
+     * @return string
499
+     */
500
+    public function getETag($path) {
501
+        return $this->calculateEtag($path, $this->stat($path));
502
+    }
503
+
504
+    private function calculateEtag(string $path, array $stat): string {
505
+        if ($stat['mode'] & 0x4000 && !($stat['mode'] & 0x8000)) { // is_dir & not socket
506
+            return parent::getETag($path);
507
+        } else {
508
+            if ($stat === false) {
509
+                return md5('');
510
+            }
511
+
512
+            $toHash = '';
513
+            if (isset($stat['mtime'])) {
514
+                $toHash .= $stat['mtime'];
515
+            }
516
+            if (isset($stat['ino'])) {
517
+                $toHash .= $stat['ino'];
518
+            }
519
+            if (isset($stat['dev'])) {
520
+                $toHash .= $stat['dev'];
521
+            }
522
+            if (isset($stat['size'])) {
523
+                $toHash .= $stat['size'];
524
+            }
525
+
526
+            return md5($toHash);
527
+        }
528
+    }
529
+
530
+    /**
531
+     * @param IStorage $sourceStorage
532
+     * @param string $sourceInternalPath
533
+     * @param string $targetInternalPath
534
+     * @param bool $preserveMtime
535
+     * @return bool
536
+     */
537
+    public function copyFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath, $preserveMtime = false) {
538
+        // Don't treat ACLStorageWrapper like local storage where copy can be done directly.
539
+        // Instead use the slower recursive copying in php from Common::copyFromStorage with
540
+        // more permissions checks.
541
+        if ($sourceStorage->instanceOfStorage(Local::class) && !$sourceStorage->instanceOfStorage('OCA\GroupFolders\ACL\ACLStorageWrapper')) {
542
+            if ($sourceStorage->instanceOfStorage(Jail::class)) {
543
+                /**
544
+                 * @var \OC\Files\Storage\Wrapper\Jail $sourceStorage
545
+                 */
546
+                $sourceInternalPath = $sourceStorage->getUnjailedPath($sourceInternalPath);
547
+            }
548
+            /**
549
+             * @var \OC\Files\Storage\Local $sourceStorage
550
+             */
551
+            $rootStorage = new Local(['datadir' => '/']);
552
+            return $rootStorage->copy($sourceStorage->getSourcePath($sourceInternalPath), $this->getSourcePath($targetInternalPath));
553
+        } else {
554
+            return parent::copyFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
555
+        }
556
+    }
557
+
558
+    /**
559
+     * @param IStorage $sourceStorage
560
+     * @param string $sourceInternalPath
561
+     * @param string $targetInternalPath
562
+     * @return bool
563
+     */
564
+    public function moveFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) {
565
+        if ($sourceStorage->instanceOfStorage(Local::class)) {
566
+            if ($sourceStorage->instanceOfStorage(Jail::class)) {
567
+                /**
568
+                 * @var \OC\Files\Storage\Wrapper\Jail $sourceStorage
569
+                 */
570
+                $sourceInternalPath = $sourceStorage->getUnjailedPath($sourceInternalPath);
571
+            }
572
+            /**
573
+             * @var \OC\Files\Storage\Local $sourceStorage
574
+             */
575
+            $rootStorage = new Local(['datadir' => '/']);
576
+            return $rootStorage->rename($sourceStorage->getSourcePath($sourceInternalPath), $this->getSourcePath($targetInternalPath));
577
+        } else {
578
+            return parent::moveFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
579
+        }
580
+    }
581
+
582
+    public function writeStream(string $path, $stream, int $size = null): int {
583
+        $result = $this->file_put_contents($path, $stream);
584
+        if (is_resource($stream)) {
585
+            fclose($stream);
586
+        }
587
+        if ($result === false) {
588
+            throw new GenericFileException("Failed write stream to $path");
589
+        } else {
590
+            return $result;
591
+        }
592
+    }
593 593
 }
Please login to merge, or discard this patch.
Spacing   +13 added lines, -13 removed lines patch added patch discarded remove patch
@@ -76,7 +76,7 @@  discard block
 block discarded – undo
76 76
 			$this->realDataDir = $this->datadir;
77 77
 		} else {
78 78
 			$realPath = realpath($this->datadir) ?: $this->datadir;
79
-			$this->realDataDir = rtrim($realPath, '/') . '/';
79
+			$this->realDataDir = rtrim($realPath, '/').'/';
80 80
 		}
81 81
 		if (substr($this->datadir, -1) !== '/') {
82 82
 			$this->datadir .= '/';
@@ -90,7 +90,7 @@  discard block
 block discarded – undo
90 90
 	}
91 91
 
92 92
 	public function getId() {
93
-		return 'local::' . $this->datadir;
93
+		return 'local::'.$this->datadir;
94 94
 	}
95 95
 
96 96
 	public function mkdir($path) {
@@ -309,7 +309,7 @@  discard block
 block discarded – undo
309 309
 		foreach ($iterator as $file) {
310 310
 			/** @var \SplFileInfo $file */
311 311
 			if (Filesystem::isFileBlacklisted($file->getBasename())) {
312
-				throw new ForbiddenException('Invalid path: ' . $file->getPathname(), false);
312
+				throw new ForbiddenException('Invalid path: '.$file->getPathname(), false);
313 313
 			}
314 314
 		}
315 315
 	}
@@ -319,17 +319,17 @@  discard block
 block discarded – undo
319 319
 		$dstParent = dirname($path2);
320 320
 
321 321
 		if (!$this->isUpdatable($srcParent)) {
322
-			\OCP\Util::writeLog('core', 'unable to rename, source directory is not writable : ' . $srcParent, ILogger::ERROR);
322
+			\OCP\Util::writeLog('core', 'unable to rename, source directory is not writable : '.$srcParent, ILogger::ERROR);
323 323
 			return false;
324 324
 		}
325 325
 
326 326
 		if (!$this->isUpdatable($dstParent)) {
327
-			\OCP\Util::writeLog('core', 'unable to rename, destination directory is not writable : ' . $dstParent, ILogger::ERROR);
327
+			\OCP\Util::writeLog('core', 'unable to rename, destination directory is not writable : '.$dstParent, ILogger::ERROR);
328 328
 			return false;
329 329
 		}
330 330
 
331 331
 		if (!$this->file_exists($path1)) {
332
-			\OCP\Util::writeLog('core', 'unable to rename, file does not exists : ' . $path1, ILogger::ERROR);
332
+			\OCP\Util::writeLog('core', 'unable to rename, file does not exists : '.$path1, ILogger::ERROR);
333 333
 			return false;
334 334
 		}
335 335
 
@@ -419,13 +419,13 @@  discard block
 block discarded – undo
419 419
 			if (\OC\Files\Filesystem::isIgnoredDir($item)) {
420 420
 				continue;
421 421
 			}
422
-			$physicalItem = $physicalDir . '/' . $item;
422
+			$physicalItem = $physicalDir.'/'.$item;
423 423
 
424 424
 			if (strstr(strtolower($item), strtolower($query)) !== false) {
425
-				$files[] = $dir . '/' . $item;
425
+				$files[] = $dir.'/'.$item;
426 426
 			}
427 427
 			if (is_dir($physicalItem)) {
428
-				$files = array_merge($files, $this->searchInDir($query, $dir . '/' . $item));
428
+				$files = array_merge($files, $this->searchInDir($query, $dir.'/'.$item));
429 429
 			}
430 430
 		}
431 431
 		return $files;
@@ -455,10 +455,10 @@  discard block
 block discarded – undo
455 455
 	 */
456 456
 	public function getSourcePath($path) {
457 457
 		if (Filesystem::isFileBlacklisted($path)) {
458
-			throw new ForbiddenException('Invalid path: ' . $path, false);
458
+			throw new ForbiddenException('Invalid path: '.$path, false);
459 459
 		}
460 460
 
461
-		$fullPath = $this->datadir . $path;
461
+		$fullPath = $this->datadir.$path;
462 462
 		$currentPath = $path;
463 463
 		$allowSymlinks = $this->config->getSystemValue('localstorage.allowsymlinks', false);
464 464
 		if ($allowSymlinks || $currentPath === '') {
@@ -471,10 +471,10 @@  discard block
 block discarded – undo
471 471
 			if ($currentPath === '' || $currentPath === '.') {
472 472
 				return $fullPath;
473 473
 			}
474
-			$realPath = realpath($this->datadir . $currentPath);
474
+			$realPath = realpath($this->datadir.$currentPath);
475 475
 		}
476 476
 		if ($realPath) {
477
-			$realPath = $realPath . '/';
477
+			$realPath = $realPath.'/';
478 478
 		}
479 479
 		if (substr($realPath, 0, $this->dataDirLength) === $this->realDataDir) {
480 480
 			return $fullPath;
Please login to merge, or discard this patch.
lib/private/Files/Storage/Common.php 2 patches
Indentation   +814 added lines, -814 removed lines patch added patch discarded remove patch
@@ -77,822 +77,822 @@
 block discarded – undo
77 77
  * in classes which extend it, e.g. $this->stat() .
78 78
  */
79 79
 abstract class Common implements Storage, ILockingStorage, IWriteStreamStorage {
80
-	use LocalTempFileTrait;
81
-
82
-	protected $cache;
83
-	protected $scanner;
84
-	protected $watcher;
85
-	protected $propagator;
86
-	protected $storageCache;
87
-	protected $updater;
88
-
89
-	protected $mountOptions = [];
90
-	protected $owner = null;
91
-
92
-	private $shouldLogLocks = null;
93
-	private $logger;
94
-
95
-	public function __construct($parameters) {
96
-	}
97
-
98
-	/**
99
-	 * Remove a file or folder
100
-	 *
101
-	 * @param string $path
102
-	 * @return bool
103
-	 */
104
-	protected function remove($path) {
105
-		if ($this->is_dir($path)) {
106
-			return $this->rmdir($path);
107
-		} elseif ($this->is_file($path)) {
108
-			return $this->unlink($path);
109
-		} else {
110
-			return false;
111
-		}
112
-	}
113
-
114
-	public function is_dir($path) {
115
-		return $this->filetype($path) === 'dir';
116
-	}
117
-
118
-	public function is_file($path) {
119
-		return $this->filetype($path) === 'file';
120
-	}
121
-
122
-	public function filesize($path) {
123
-		if ($this->is_dir($path)) {
124
-			return 0; //by definition
125
-		} else {
126
-			$stat = $this->stat($path);
127
-			if (isset($stat['size'])) {
128
-				return $stat['size'];
129
-			} else {
130
-				return 0;
131
-			}
132
-		}
133
-	}
134
-
135
-	public function isReadable($path) {
136
-		// at least check whether it exists
137
-		// subclasses might want to implement this more thoroughly
138
-		return $this->file_exists($path);
139
-	}
140
-
141
-	public function isUpdatable($path) {
142
-		// at least check whether it exists
143
-		// subclasses might want to implement this more thoroughly
144
-		// a non-existing file/folder isn't updatable
145
-		return $this->file_exists($path);
146
-	}
147
-
148
-	public function isCreatable($path) {
149
-		if ($this->is_dir($path) && $this->isUpdatable($path)) {
150
-			return true;
151
-		}
152
-		return false;
153
-	}
154
-
155
-	public function isDeletable($path) {
156
-		if ($path === '' || $path === '/') {
157
-			return $this->isUpdatable($path);
158
-		}
159
-		$parent = dirname($path);
160
-		return $this->isUpdatable($parent) && $this->isUpdatable($path);
161
-	}
162
-
163
-	public function isSharable($path) {
164
-		return $this->isReadable($path);
165
-	}
166
-
167
-	public function getPermissions($path) {
168
-		$permissions = 0;
169
-		if ($this->isCreatable($path)) {
170
-			$permissions |= \OCP\Constants::PERMISSION_CREATE;
171
-		}
172
-		if ($this->isReadable($path)) {
173
-			$permissions |= \OCP\Constants::PERMISSION_READ;
174
-		}
175
-		if ($this->isUpdatable($path)) {
176
-			$permissions |= \OCP\Constants::PERMISSION_UPDATE;
177
-		}
178
-		if ($this->isDeletable($path)) {
179
-			$permissions |= \OCP\Constants::PERMISSION_DELETE;
180
-		}
181
-		if ($this->isSharable($path)) {
182
-			$permissions |= \OCP\Constants::PERMISSION_SHARE;
183
-		}
184
-		return $permissions;
185
-	}
186
-
187
-	public function filemtime($path) {
188
-		$stat = $this->stat($path);
189
-		if (isset($stat['mtime']) && $stat['mtime'] > 0) {
190
-			return $stat['mtime'];
191
-		} else {
192
-			return 0;
193
-		}
194
-	}
195
-
196
-	public function file_get_contents($path) {
197
-		$handle = $this->fopen($path, "r");
198
-		if (!$handle) {
199
-			return false;
200
-		}
201
-		$data = stream_get_contents($handle);
202
-		fclose($handle);
203
-		return $data;
204
-	}
205
-
206
-	public function file_put_contents($path, $data) {
207
-		$handle = $this->fopen($path, "w");
208
-		$this->removeCachedFile($path);
209
-		$count = fwrite($handle, $data);
210
-		fclose($handle);
211
-		return $count;
212
-	}
213
-
214
-	public function rename($path1, $path2) {
215
-		$this->remove($path2);
216
-
217
-		$this->removeCachedFile($path1);
218
-		return $this->copy($path1, $path2) and $this->remove($path1);
219
-	}
220
-
221
-	public function copy($path1, $path2) {
222
-		if ($this->is_dir($path1)) {
223
-			$this->remove($path2);
224
-			$dir = $this->opendir($path1);
225
-			$this->mkdir($path2);
226
-			while ($file = readdir($dir)) {
227
-				if (!Filesystem::isIgnoredDir($file)) {
228
-					if (!$this->copy($path1 . '/' . $file, $path2 . '/' . $file)) {
229
-						return false;
230
-					}
231
-				}
232
-			}
233
-			closedir($dir);
234
-			return true;
235
-		} else {
236
-			$source = $this->fopen($path1, 'r');
237
-			$target = $this->fopen($path2, 'w');
238
-			[, $result] = \OC_Helper::streamCopy($source, $target);
239
-			if (!$result) {
240
-				\OC::$server->getLogger()->warning("Failed to write data while copying $path1 to $path2");
241
-			}
242
-			$this->removeCachedFile($path2);
243
-			return $result;
244
-		}
245
-	}
246
-
247
-	public function getMimeType($path) {
248
-		if ($this->is_dir($path)) {
249
-			return 'httpd/unix-directory';
250
-		} elseif ($this->file_exists($path)) {
251
-			return \OC::$server->getMimeTypeDetector()->detectPath($path);
252
-		} else {
253
-			return false;
254
-		}
255
-	}
256
-
257
-	public function hash($type, $path, $raw = false) {
258
-		$fh = $this->fopen($path, 'rb');
259
-		$ctx = hash_init($type);
260
-		hash_update_stream($ctx, $fh);
261
-		fclose($fh);
262
-		return hash_final($ctx, $raw);
263
-	}
264
-
265
-	public function search($query) {
266
-		return $this->searchInDir($query);
267
-	}
268
-
269
-	public function getLocalFile($path) {
270
-		return $this->getCachedFile($path);
271
-	}
272
-
273
-	/**
274
-	 * @param string $path
275
-	 * @param string $target
276
-	 */
277
-	private function addLocalFolder($path, $target) {
278
-		$dh = $this->opendir($path);
279
-		if (is_resource($dh)) {
280
-			while (($file = readdir($dh)) !== false) {
281
-				if (!\OC\Files\Filesystem::isIgnoredDir($file)) {
282
-					if ($this->is_dir($path . '/' . $file)) {
283
-						mkdir($target . '/' . $file);
284
-						$this->addLocalFolder($path . '/' . $file, $target . '/' . $file);
285
-					} else {
286
-						$tmp = $this->toTmpFile($path . '/' . $file);
287
-						rename($tmp, $target . '/' . $file);
288
-					}
289
-				}
290
-			}
291
-		}
292
-	}
293
-
294
-	/**
295
-	 * @param string $query
296
-	 * @param string $dir
297
-	 * @return array
298
-	 */
299
-	protected function searchInDir($query, $dir = '') {
300
-		$files = [];
301
-		$dh = $this->opendir($dir);
302
-		if (is_resource($dh)) {
303
-			while (($item = readdir($dh)) !== false) {
304
-				if (\OC\Files\Filesystem::isIgnoredDir($item)) {
305
-					continue;
306
-				}
307
-				if (strstr(strtolower($item), strtolower($query)) !== false) {
308
-					$files[] = $dir . '/' . $item;
309
-				}
310
-				if ($this->is_dir($dir . '/' . $item)) {
311
-					$files = array_merge($files, $this->searchInDir($query, $dir . '/' . $item));
312
-				}
313
-			}
314
-		}
315
-		closedir($dh);
316
-		return $files;
317
-	}
318
-
319
-	/**
320
-	 * Check if a file or folder has been updated since $time
321
-	 *
322
-	 * The method is only used to check if the cache needs to be updated. Storage backends that don't support checking
323
-	 * the mtime should always return false here. As a result storage implementations that always return false expect
324
-	 * exclusive access to the backend and will not pick up files that have been added in a way that circumvents
325
-	 * Nextcloud filesystem.
326
-	 *
327
-	 * @param string $path
328
-	 * @param int $time
329
-	 * @return bool
330
-	 */
331
-	public function hasUpdated($path, $time) {
332
-		return $this->filemtime($path) > $time;
333
-	}
334
-
335
-	public function getCache($path = '', $storage = null) {
336
-		if (!$storage) {
337
-			$storage = $this;
338
-		}
339
-		if (!isset($storage->cache)) {
340
-			$storage->cache = new Cache($storage);
341
-		}
342
-		return $storage->cache;
343
-	}
344
-
345
-	public function getScanner($path = '', $storage = null) {
346
-		if (!$storage) {
347
-			$storage = $this;
348
-		}
349
-		if (!isset($storage->scanner)) {
350
-			$storage->scanner = new Scanner($storage);
351
-		}
352
-		return $storage->scanner;
353
-	}
354
-
355
-	public function getWatcher($path = '', $storage = null) {
356
-		if (!$storage) {
357
-			$storage = $this;
358
-		}
359
-		if (!isset($this->watcher)) {
360
-			$this->watcher = new Watcher($storage);
361
-			$globalPolicy = \OC::$server->getConfig()->getSystemValue('filesystem_check_changes', Watcher::CHECK_NEVER);
362
-			$this->watcher->setPolicy((int)$this->getMountOption('filesystem_check_changes', $globalPolicy));
363
-		}
364
-		return $this->watcher;
365
-	}
366
-
367
-	/**
368
-	 * get a propagator instance for the cache
369
-	 *
370
-	 * @param \OC\Files\Storage\Storage (optional) the storage to pass to the watcher
371
-	 * @return \OC\Files\Cache\Propagator
372
-	 */
373
-	public function getPropagator($storage = null) {
374
-		if (!$storage) {
375
-			$storage = $this;
376
-		}
377
-		if (!isset($storage->propagator)) {
378
-			$config = \OC::$server->getSystemConfig();
379
-			$storage->propagator = new Propagator($storage, \OC::$server->getDatabaseConnection(), ['appdata_' . $config->getValue('instanceid')]);
380
-		}
381
-		return $storage->propagator;
382
-	}
383
-
384
-	public function getUpdater($storage = null) {
385
-		if (!$storage) {
386
-			$storage = $this;
387
-		}
388
-		if (!isset($storage->updater)) {
389
-			$storage->updater = new Updater($storage);
390
-		}
391
-		return $storage->updater;
392
-	}
393
-
394
-	public function getStorageCache($storage = null) {
395
-		if (!$storage) {
396
-			$storage = $this;
397
-		}
398
-		if (!isset($this->storageCache)) {
399
-			$this->storageCache = new \OC\Files\Cache\Storage($storage);
400
-		}
401
-		return $this->storageCache;
402
-	}
403
-
404
-	/**
405
-	 * get the owner of a path
406
-	 *
407
-	 * @param string $path The path to get the owner
408
-	 * @return string|false uid or false
409
-	 */
410
-	public function getOwner($path) {
411
-		if ($this->owner === null) {
412
-			$this->owner = \OC_User::getUser();
413
-		}
414
-
415
-		return $this->owner;
416
-	}
417
-
418
-	/**
419
-	 * get the ETag for a file or folder
420
-	 *
421
-	 * @param string $path
422
-	 * @return string
423
-	 */
424
-	public function getETag($path) {
425
-		return uniqid();
426
-	}
427
-
428
-	/**
429
-	 * clean a path, i.e. remove all redundant '.' and '..'
430
-	 * making sure that it can't point to higher than '/'
431
-	 *
432
-	 * @param string $path The path to clean
433
-	 * @return string cleaned path
434
-	 */
435
-	public function cleanPath($path) {
436
-		if (strlen($path) == 0 or $path[0] != '/') {
437
-			$path = '/' . $path;
438
-		}
439
-
440
-		$output = [];
441
-		foreach (explode('/', $path) as $chunk) {
442
-			if ($chunk == '..') {
443
-				array_pop($output);
444
-			} elseif ($chunk == '.') {
445
-			} else {
446
-				$output[] = $chunk;
447
-			}
448
-		}
449
-		return implode('/', $output);
450
-	}
451
-
452
-	/**
453
-	 * Test a storage for availability
454
-	 *
455
-	 * @return bool
456
-	 */
457
-	public function test() {
458
-		try {
459
-			if ($this->stat('')) {
460
-				return true;
461
-			}
462
-			\OC::$server->getLogger()->info("External storage not available: stat() failed");
463
-			return false;
464
-		} catch (\Exception $e) {
465
-			\OC::$server->getLogger()->warning("External storage not available: " . $e->getMessage());
466
-			\OC::$server->getLogger()->logException($e, ['level' => ILogger::WARN]);
467
-			return false;
468
-		}
469
-	}
470
-
471
-	/**
472
-	 * get the free space in the storage
473
-	 *
474
-	 * @param string $path
475
-	 * @return int|false
476
-	 */
477
-	public function free_space($path) {
478
-		return \OCP\Files\FileInfo::SPACE_UNKNOWN;
479
-	}
480
-
481
-	/**
482
-	 * {@inheritdoc}
483
-	 */
484
-	public function isLocal() {
485
-		// the common implementation returns a temporary file by
486
-		// default, which is not local
487
-		return false;
488
-	}
489
-
490
-	/**
491
-	 * Check if the storage is an instance of $class or is a wrapper for a storage that is an instance of $class
492
-	 *
493
-	 * @param string $class
494
-	 * @return bool
495
-	 */
496
-	public function instanceOfStorage($class) {
497
-		if (ltrim($class, '\\') === 'OC\Files\Storage\Shared') {
498
-			// FIXME Temporary fix to keep existing checks working
499
-			$class = '\OCA\Files_Sharing\SharedStorage';
500
-		}
501
-		return is_a($this, $class);
502
-	}
503
-
504
-	/**
505
-	 * A custom storage implementation can return an url for direct download of a give file.
506
-	 *
507
-	 * For now the returned array can hold the parameter url - in future more attributes might follow.
508
-	 *
509
-	 * @param string $path
510
-	 * @return array|false
511
-	 */
512
-	public function getDirectDownload($path) {
513
-		return [];
514
-	}
515
-
516
-	/**
517
-	 * @inheritdoc
518
-	 * @throws InvalidPathException
519
-	 */
520
-	public function verifyPath($path, $fileName) {
521
-
522
-		// verify empty and dot files
523
-		$trimmed = trim($fileName);
524
-		if ($trimmed === '') {
525
-			throw new EmptyFileNameException();
526
-		}
527
-
528
-		if (\OC\Files\Filesystem::isIgnoredDir($trimmed)) {
529
-			throw new InvalidDirectoryException();
530
-		}
531
-
532
-		if (!\OC::$server->getDatabaseConnection()->supports4ByteText()) {
533
-			// verify database - e.g. mysql only 3-byte chars
534
-			if (preg_match('%(?:
80
+    use LocalTempFileTrait;
81
+
82
+    protected $cache;
83
+    protected $scanner;
84
+    protected $watcher;
85
+    protected $propagator;
86
+    protected $storageCache;
87
+    protected $updater;
88
+
89
+    protected $mountOptions = [];
90
+    protected $owner = null;
91
+
92
+    private $shouldLogLocks = null;
93
+    private $logger;
94
+
95
+    public function __construct($parameters) {
96
+    }
97
+
98
+    /**
99
+     * Remove a file or folder
100
+     *
101
+     * @param string $path
102
+     * @return bool
103
+     */
104
+    protected function remove($path) {
105
+        if ($this->is_dir($path)) {
106
+            return $this->rmdir($path);
107
+        } elseif ($this->is_file($path)) {
108
+            return $this->unlink($path);
109
+        } else {
110
+            return false;
111
+        }
112
+    }
113
+
114
+    public function is_dir($path) {
115
+        return $this->filetype($path) === 'dir';
116
+    }
117
+
118
+    public function is_file($path) {
119
+        return $this->filetype($path) === 'file';
120
+    }
121
+
122
+    public function filesize($path) {
123
+        if ($this->is_dir($path)) {
124
+            return 0; //by definition
125
+        } else {
126
+            $stat = $this->stat($path);
127
+            if (isset($stat['size'])) {
128
+                return $stat['size'];
129
+            } else {
130
+                return 0;
131
+            }
132
+        }
133
+    }
134
+
135
+    public function isReadable($path) {
136
+        // at least check whether it exists
137
+        // subclasses might want to implement this more thoroughly
138
+        return $this->file_exists($path);
139
+    }
140
+
141
+    public function isUpdatable($path) {
142
+        // at least check whether it exists
143
+        // subclasses might want to implement this more thoroughly
144
+        // a non-existing file/folder isn't updatable
145
+        return $this->file_exists($path);
146
+    }
147
+
148
+    public function isCreatable($path) {
149
+        if ($this->is_dir($path) && $this->isUpdatable($path)) {
150
+            return true;
151
+        }
152
+        return false;
153
+    }
154
+
155
+    public function isDeletable($path) {
156
+        if ($path === '' || $path === '/') {
157
+            return $this->isUpdatable($path);
158
+        }
159
+        $parent = dirname($path);
160
+        return $this->isUpdatable($parent) && $this->isUpdatable($path);
161
+    }
162
+
163
+    public function isSharable($path) {
164
+        return $this->isReadable($path);
165
+    }
166
+
167
+    public function getPermissions($path) {
168
+        $permissions = 0;
169
+        if ($this->isCreatable($path)) {
170
+            $permissions |= \OCP\Constants::PERMISSION_CREATE;
171
+        }
172
+        if ($this->isReadable($path)) {
173
+            $permissions |= \OCP\Constants::PERMISSION_READ;
174
+        }
175
+        if ($this->isUpdatable($path)) {
176
+            $permissions |= \OCP\Constants::PERMISSION_UPDATE;
177
+        }
178
+        if ($this->isDeletable($path)) {
179
+            $permissions |= \OCP\Constants::PERMISSION_DELETE;
180
+        }
181
+        if ($this->isSharable($path)) {
182
+            $permissions |= \OCP\Constants::PERMISSION_SHARE;
183
+        }
184
+        return $permissions;
185
+    }
186
+
187
+    public function filemtime($path) {
188
+        $stat = $this->stat($path);
189
+        if (isset($stat['mtime']) && $stat['mtime'] > 0) {
190
+            return $stat['mtime'];
191
+        } else {
192
+            return 0;
193
+        }
194
+    }
195
+
196
+    public function file_get_contents($path) {
197
+        $handle = $this->fopen($path, "r");
198
+        if (!$handle) {
199
+            return false;
200
+        }
201
+        $data = stream_get_contents($handle);
202
+        fclose($handle);
203
+        return $data;
204
+    }
205
+
206
+    public function file_put_contents($path, $data) {
207
+        $handle = $this->fopen($path, "w");
208
+        $this->removeCachedFile($path);
209
+        $count = fwrite($handle, $data);
210
+        fclose($handle);
211
+        return $count;
212
+    }
213
+
214
+    public function rename($path1, $path2) {
215
+        $this->remove($path2);
216
+
217
+        $this->removeCachedFile($path1);
218
+        return $this->copy($path1, $path2) and $this->remove($path1);
219
+    }
220
+
221
+    public function copy($path1, $path2) {
222
+        if ($this->is_dir($path1)) {
223
+            $this->remove($path2);
224
+            $dir = $this->opendir($path1);
225
+            $this->mkdir($path2);
226
+            while ($file = readdir($dir)) {
227
+                if (!Filesystem::isIgnoredDir($file)) {
228
+                    if (!$this->copy($path1 . '/' . $file, $path2 . '/' . $file)) {
229
+                        return false;
230
+                    }
231
+                }
232
+            }
233
+            closedir($dir);
234
+            return true;
235
+        } else {
236
+            $source = $this->fopen($path1, 'r');
237
+            $target = $this->fopen($path2, 'w');
238
+            [, $result] = \OC_Helper::streamCopy($source, $target);
239
+            if (!$result) {
240
+                \OC::$server->getLogger()->warning("Failed to write data while copying $path1 to $path2");
241
+            }
242
+            $this->removeCachedFile($path2);
243
+            return $result;
244
+        }
245
+    }
246
+
247
+    public function getMimeType($path) {
248
+        if ($this->is_dir($path)) {
249
+            return 'httpd/unix-directory';
250
+        } elseif ($this->file_exists($path)) {
251
+            return \OC::$server->getMimeTypeDetector()->detectPath($path);
252
+        } else {
253
+            return false;
254
+        }
255
+    }
256
+
257
+    public function hash($type, $path, $raw = false) {
258
+        $fh = $this->fopen($path, 'rb');
259
+        $ctx = hash_init($type);
260
+        hash_update_stream($ctx, $fh);
261
+        fclose($fh);
262
+        return hash_final($ctx, $raw);
263
+    }
264
+
265
+    public function search($query) {
266
+        return $this->searchInDir($query);
267
+    }
268
+
269
+    public function getLocalFile($path) {
270
+        return $this->getCachedFile($path);
271
+    }
272
+
273
+    /**
274
+     * @param string $path
275
+     * @param string $target
276
+     */
277
+    private function addLocalFolder($path, $target) {
278
+        $dh = $this->opendir($path);
279
+        if (is_resource($dh)) {
280
+            while (($file = readdir($dh)) !== false) {
281
+                if (!\OC\Files\Filesystem::isIgnoredDir($file)) {
282
+                    if ($this->is_dir($path . '/' . $file)) {
283
+                        mkdir($target . '/' . $file);
284
+                        $this->addLocalFolder($path . '/' . $file, $target . '/' . $file);
285
+                    } else {
286
+                        $tmp = $this->toTmpFile($path . '/' . $file);
287
+                        rename($tmp, $target . '/' . $file);
288
+                    }
289
+                }
290
+            }
291
+        }
292
+    }
293
+
294
+    /**
295
+     * @param string $query
296
+     * @param string $dir
297
+     * @return array
298
+     */
299
+    protected function searchInDir($query, $dir = '') {
300
+        $files = [];
301
+        $dh = $this->opendir($dir);
302
+        if (is_resource($dh)) {
303
+            while (($item = readdir($dh)) !== false) {
304
+                if (\OC\Files\Filesystem::isIgnoredDir($item)) {
305
+                    continue;
306
+                }
307
+                if (strstr(strtolower($item), strtolower($query)) !== false) {
308
+                    $files[] = $dir . '/' . $item;
309
+                }
310
+                if ($this->is_dir($dir . '/' . $item)) {
311
+                    $files = array_merge($files, $this->searchInDir($query, $dir . '/' . $item));
312
+                }
313
+            }
314
+        }
315
+        closedir($dh);
316
+        return $files;
317
+    }
318
+
319
+    /**
320
+     * Check if a file or folder has been updated since $time
321
+     *
322
+     * The method is only used to check if the cache needs to be updated. Storage backends that don't support checking
323
+     * the mtime should always return false here. As a result storage implementations that always return false expect
324
+     * exclusive access to the backend and will not pick up files that have been added in a way that circumvents
325
+     * Nextcloud filesystem.
326
+     *
327
+     * @param string $path
328
+     * @param int $time
329
+     * @return bool
330
+     */
331
+    public function hasUpdated($path, $time) {
332
+        return $this->filemtime($path) > $time;
333
+    }
334
+
335
+    public function getCache($path = '', $storage = null) {
336
+        if (!$storage) {
337
+            $storage = $this;
338
+        }
339
+        if (!isset($storage->cache)) {
340
+            $storage->cache = new Cache($storage);
341
+        }
342
+        return $storage->cache;
343
+    }
344
+
345
+    public function getScanner($path = '', $storage = null) {
346
+        if (!$storage) {
347
+            $storage = $this;
348
+        }
349
+        if (!isset($storage->scanner)) {
350
+            $storage->scanner = new Scanner($storage);
351
+        }
352
+        return $storage->scanner;
353
+    }
354
+
355
+    public function getWatcher($path = '', $storage = null) {
356
+        if (!$storage) {
357
+            $storage = $this;
358
+        }
359
+        if (!isset($this->watcher)) {
360
+            $this->watcher = new Watcher($storage);
361
+            $globalPolicy = \OC::$server->getConfig()->getSystemValue('filesystem_check_changes', Watcher::CHECK_NEVER);
362
+            $this->watcher->setPolicy((int)$this->getMountOption('filesystem_check_changes', $globalPolicy));
363
+        }
364
+        return $this->watcher;
365
+    }
366
+
367
+    /**
368
+     * get a propagator instance for the cache
369
+     *
370
+     * @param \OC\Files\Storage\Storage (optional) the storage to pass to the watcher
371
+     * @return \OC\Files\Cache\Propagator
372
+     */
373
+    public function getPropagator($storage = null) {
374
+        if (!$storage) {
375
+            $storage = $this;
376
+        }
377
+        if (!isset($storage->propagator)) {
378
+            $config = \OC::$server->getSystemConfig();
379
+            $storage->propagator = new Propagator($storage, \OC::$server->getDatabaseConnection(), ['appdata_' . $config->getValue('instanceid')]);
380
+        }
381
+        return $storage->propagator;
382
+    }
383
+
384
+    public function getUpdater($storage = null) {
385
+        if (!$storage) {
386
+            $storage = $this;
387
+        }
388
+        if (!isset($storage->updater)) {
389
+            $storage->updater = new Updater($storage);
390
+        }
391
+        return $storage->updater;
392
+    }
393
+
394
+    public function getStorageCache($storage = null) {
395
+        if (!$storage) {
396
+            $storage = $this;
397
+        }
398
+        if (!isset($this->storageCache)) {
399
+            $this->storageCache = new \OC\Files\Cache\Storage($storage);
400
+        }
401
+        return $this->storageCache;
402
+    }
403
+
404
+    /**
405
+     * get the owner of a path
406
+     *
407
+     * @param string $path The path to get the owner
408
+     * @return string|false uid or false
409
+     */
410
+    public function getOwner($path) {
411
+        if ($this->owner === null) {
412
+            $this->owner = \OC_User::getUser();
413
+        }
414
+
415
+        return $this->owner;
416
+    }
417
+
418
+    /**
419
+     * get the ETag for a file or folder
420
+     *
421
+     * @param string $path
422
+     * @return string
423
+     */
424
+    public function getETag($path) {
425
+        return uniqid();
426
+    }
427
+
428
+    /**
429
+     * clean a path, i.e. remove all redundant '.' and '..'
430
+     * making sure that it can't point to higher than '/'
431
+     *
432
+     * @param string $path The path to clean
433
+     * @return string cleaned path
434
+     */
435
+    public function cleanPath($path) {
436
+        if (strlen($path) == 0 or $path[0] != '/') {
437
+            $path = '/' . $path;
438
+        }
439
+
440
+        $output = [];
441
+        foreach (explode('/', $path) as $chunk) {
442
+            if ($chunk == '..') {
443
+                array_pop($output);
444
+            } elseif ($chunk == '.') {
445
+            } else {
446
+                $output[] = $chunk;
447
+            }
448
+        }
449
+        return implode('/', $output);
450
+    }
451
+
452
+    /**
453
+     * Test a storage for availability
454
+     *
455
+     * @return bool
456
+     */
457
+    public function test() {
458
+        try {
459
+            if ($this->stat('')) {
460
+                return true;
461
+            }
462
+            \OC::$server->getLogger()->info("External storage not available: stat() failed");
463
+            return false;
464
+        } catch (\Exception $e) {
465
+            \OC::$server->getLogger()->warning("External storage not available: " . $e->getMessage());
466
+            \OC::$server->getLogger()->logException($e, ['level' => ILogger::WARN]);
467
+            return false;
468
+        }
469
+    }
470
+
471
+    /**
472
+     * get the free space in the storage
473
+     *
474
+     * @param string $path
475
+     * @return int|false
476
+     */
477
+    public function free_space($path) {
478
+        return \OCP\Files\FileInfo::SPACE_UNKNOWN;
479
+    }
480
+
481
+    /**
482
+     * {@inheritdoc}
483
+     */
484
+    public function isLocal() {
485
+        // the common implementation returns a temporary file by
486
+        // default, which is not local
487
+        return false;
488
+    }
489
+
490
+    /**
491
+     * Check if the storage is an instance of $class or is a wrapper for a storage that is an instance of $class
492
+     *
493
+     * @param string $class
494
+     * @return bool
495
+     */
496
+    public function instanceOfStorage($class) {
497
+        if (ltrim($class, '\\') === 'OC\Files\Storage\Shared') {
498
+            // FIXME Temporary fix to keep existing checks working
499
+            $class = '\OCA\Files_Sharing\SharedStorage';
500
+        }
501
+        return is_a($this, $class);
502
+    }
503
+
504
+    /**
505
+     * A custom storage implementation can return an url for direct download of a give file.
506
+     *
507
+     * For now the returned array can hold the parameter url - in future more attributes might follow.
508
+     *
509
+     * @param string $path
510
+     * @return array|false
511
+     */
512
+    public function getDirectDownload($path) {
513
+        return [];
514
+    }
515
+
516
+    /**
517
+     * @inheritdoc
518
+     * @throws InvalidPathException
519
+     */
520
+    public function verifyPath($path, $fileName) {
521
+
522
+        // verify empty and dot files
523
+        $trimmed = trim($fileName);
524
+        if ($trimmed === '') {
525
+            throw new EmptyFileNameException();
526
+        }
527
+
528
+        if (\OC\Files\Filesystem::isIgnoredDir($trimmed)) {
529
+            throw new InvalidDirectoryException();
530
+        }
531
+
532
+        if (!\OC::$server->getDatabaseConnection()->supports4ByteText()) {
533
+            // verify database - e.g. mysql only 3-byte chars
534
+            if (preg_match('%(?:
535 535
       \xF0[\x90-\xBF][\x80-\xBF]{2}      # planes 1-3
536 536
     | [\xF1-\xF3][\x80-\xBF]{3}          # planes 4-15
537 537
     | \xF4[\x80-\x8F][\x80-\xBF]{2}      # plane 16
538 538
 )%xs', $fileName)) {
539
-				throw new InvalidCharacterInPathException();
540
-			}
541
-		}
542
-
543
-		// 255 characters is the limit on common file systems (ext/xfs)
544
-		// oc_filecache has a 250 char length limit for the filename
545
-		if (isset($fileName[250])) {
546
-			throw new FileNameTooLongException();
547
-		}
548
-
549
-		// NOTE: $path will remain unverified for now
550
-		$this->verifyPosixPath($fileName);
551
-	}
552
-
553
-	/**
554
-	 * @param string $fileName
555
-	 * @throws InvalidPathException
556
-	 */
557
-	protected function verifyPosixPath($fileName) {
558
-		$this->scanForInvalidCharacters($fileName, "\\/");
559
-		$fileName = trim($fileName);
560
-		$reservedNames = ['*'];
561
-		if (in_array($fileName, $reservedNames)) {
562
-			throw new ReservedWordException();
563
-		}
564
-	}
565
-
566
-	/**
567
-	 * @param string $fileName
568
-	 * @param string $invalidChars
569
-	 * @throws InvalidPathException
570
-	 */
571
-	private function scanForInvalidCharacters($fileName, $invalidChars) {
572
-		foreach (str_split($invalidChars) as $char) {
573
-			if (strpos($fileName, $char) !== false) {
574
-				throw new InvalidCharacterInPathException();
575
-			}
576
-		}
577
-
578
-		$sanitizedFileName = filter_var($fileName, FILTER_UNSAFE_RAW, FILTER_FLAG_STRIP_LOW);
579
-		if ($sanitizedFileName !== $fileName) {
580
-			throw new InvalidCharacterInPathException();
581
-		}
582
-	}
583
-
584
-	/**
585
-	 * @param array $options
586
-	 */
587
-	public function setMountOptions(array $options) {
588
-		$this->mountOptions = $options;
589
-	}
590
-
591
-	/**
592
-	 * @param string $name
593
-	 * @param mixed $default
594
-	 * @return mixed
595
-	 */
596
-	public function getMountOption($name, $default = null) {
597
-		return isset($this->mountOptions[$name]) ? $this->mountOptions[$name] : $default;
598
-	}
599
-
600
-	/**
601
-	 * @param IStorage $sourceStorage
602
-	 * @param string $sourceInternalPath
603
-	 * @param string $targetInternalPath
604
-	 * @param bool $preserveMtime
605
-	 * @return bool
606
-	 */
607
-	public function copyFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath, $preserveMtime = false) {
608
-		if ($sourceStorage === $this) {
609
-			return $this->copy($sourceInternalPath, $targetInternalPath);
610
-		}
611
-
612
-		if ($sourceStorage->is_dir($sourceInternalPath)) {
613
-			$dh = $sourceStorage->opendir($sourceInternalPath);
614
-			$result = $this->mkdir($targetInternalPath);
615
-			if (is_resource($dh)) {
616
-				$result = true;
617
-				while ($result and ($file = readdir($dh)) !== false) {
618
-					if (!Filesystem::isIgnoredDir($file)) {
619
-						$result &= $this->copyFromStorage($sourceStorage, $sourceInternalPath . '/' . $file, $targetInternalPath . '/' . $file);
620
-					}
621
-				}
622
-			}
623
-		} else {
624
-			$source = $sourceStorage->fopen($sourceInternalPath, 'r');
625
-			$result = false;
626
-			if ($source) {
627
-				try {
628
-					$this->writeStream($targetInternalPath, $source);
629
-					$result = true;
630
-				} catch (\Exception $e) {
631
-					\OC::$server->getLogger()->logException($e, ['level' => ILogger::WARN, 'message' => 'Failed to copy stream to storage']);
632
-				}
633
-			}
634
-
635
-			if ($result && $preserveMtime) {
636
-				$mtime = $sourceStorage->filemtime($sourceInternalPath);
637
-				$this->touch($targetInternalPath, is_int($mtime) ? $mtime : null);
638
-			}
639
-
640
-			if (!$result) {
641
-				// delete partially written target file
642
-				$this->unlink($targetInternalPath);
643
-				// delete cache entry that was created by fopen
644
-				$this->getCache()->remove($targetInternalPath);
645
-			}
646
-		}
647
-		return (bool)$result;
648
-	}
649
-
650
-	/**
651
-	 * Check if a storage is the same as the current one, including wrapped storages
652
-	 *
653
-	 * @param IStorage $storage
654
-	 * @return bool
655
-	 */
656
-	private function isSameStorage(IStorage $storage): bool {
657
-		while ($storage->instanceOfStorage(Wrapper::class)) {
658
-			/**
659
-			 * @var Wrapper $sourceStorage
660
-			 */
661
-			$storage = $storage->getWrapperStorage();
662
-		}
663
-
664
-		return $storage === $this;
665
-	}
666
-
667
-	/**
668
-	 * @param IStorage $sourceStorage
669
-	 * @param string $sourceInternalPath
670
-	 * @param string $targetInternalPath
671
-	 * @return bool
672
-	 */
673
-	public function moveFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) {
674
-		if ($this->isSameStorage($sourceStorage)) {
675
-			// resolve any jailed paths
676
-			while ($sourceStorage->instanceOfStorage(Jail::class)) {
677
-				/**
678
-				 * @var Jail $sourceStorage
679
-				 */
680
-				$sourceInternalPath = $sourceStorage->getUnjailedPath($sourceInternalPath);
681
-				$sourceStorage = $sourceStorage->getUnjailedStorage();
682
-			}
683
-
684
-			return $this->rename($sourceInternalPath, $targetInternalPath);
685
-		}
686
-
687
-		if (!$sourceStorage->isDeletable($sourceInternalPath)) {
688
-			return false;
689
-		}
690
-
691
-		$result = $this->copyFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath, true);
692
-		if ($result) {
693
-			if ($sourceStorage->is_dir($sourceInternalPath)) {
694
-				$result = $result && $sourceStorage->rmdir($sourceInternalPath);
695
-			} else {
696
-				$result = $result && $sourceStorage->unlink($sourceInternalPath);
697
-			}
698
-		}
699
-		return $result;
700
-	}
701
-
702
-	/**
703
-	 * @inheritdoc
704
-	 */
705
-	public function getMetaData($path) {
706
-		if (Filesystem::isFileBlacklisted($path)) {
707
-			throw new ForbiddenException('Invalid path: ' . $path, false);
708
-		}
709
-
710
-		$permissions = $this->getPermissions($path);
711
-		if (!$permissions & \OCP\Constants::PERMISSION_READ) {
712
-			//can't read, nothing we can do
713
-			return null;
714
-		}
715
-
716
-		$data = [];
717
-		$data['mimetype'] = $this->getMimeType($path);
718
-		$data['mtime'] = $this->filemtime($path);
719
-		if ($data['mtime'] === false) {
720
-			$data['mtime'] = time();
721
-		}
722
-		if ($data['mimetype'] == 'httpd/unix-directory') {
723
-			$data['size'] = -1; //unknown
724
-		} else {
725
-			$data['size'] = $this->filesize($path);
726
-		}
727
-		$data['etag'] = $this->getETag($path);
728
-		$data['storage_mtime'] = $data['mtime'];
729
-		$data['permissions'] = $permissions;
730
-		$data['name'] = basename($path);
731
-
732
-		return $data;
733
-	}
734
-
735
-	/**
736
-	 * @param string $path
737
-	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
738
-	 * @param \OCP\Lock\ILockingProvider $provider
739
-	 * @throws \OCP\Lock\LockedException
740
-	 */
741
-	public function acquireLock($path, $type, ILockingProvider $provider) {
742
-		$logger = $this->getLockLogger();
743
-		if ($logger) {
744
-			$typeString = ($type === ILockingProvider::LOCK_SHARED) ? 'shared' : 'exclusive';
745
-			$logger->info(
746
-				sprintf(
747
-					'acquire %s lock on "%s" on storage "%s"',
748
-					$typeString,
749
-					$path,
750
-					$this->getId()
751
-				),
752
-				[
753
-					'app' => 'locking',
754
-				]
755
-			);
756
-		}
757
-		try {
758
-			$provider->acquireLock('files/' . md5($this->getId() . '::' . trim($path, '/')), $type, $this->getId() . '::' . $path);
759
-		} catch (LockedException $e) {
760
-			if ($logger) {
761
-				$logger->logException($e, ['level' => ILogger::INFO]);
762
-			}
763
-			throw $e;
764
-		}
765
-	}
766
-
767
-	/**
768
-	 * @param string $path
769
-	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
770
-	 * @param \OCP\Lock\ILockingProvider $provider
771
-	 * @throws \OCP\Lock\LockedException
772
-	 */
773
-	public function releaseLock($path, $type, ILockingProvider $provider) {
774
-		$logger = $this->getLockLogger();
775
-		if ($logger) {
776
-			$typeString = ($type === ILockingProvider::LOCK_SHARED) ? 'shared' : 'exclusive';
777
-			$logger->info(
778
-				sprintf(
779
-					'release %s lock on "%s" on storage "%s"',
780
-					$typeString,
781
-					$path,
782
-					$this->getId()
783
-				),
784
-				[
785
-					'app' => 'locking',
786
-				]
787
-			);
788
-		}
789
-		try {
790
-			$provider->releaseLock('files/' . md5($this->getId() . '::' . trim($path, '/')), $type);
791
-		} catch (LockedException $e) {
792
-			if ($logger) {
793
-				$logger->logException($e, ['level' => ILogger::INFO]);
794
-			}
795
-			throw $e;
796
-		}
797
-	}
798
-
799
-	/**
800
-	 * @param string $path
801
-	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
802
-	 * @param \OCP\Lock\ILockingProvider $provider
803
-	 * @throws \OCP\Lock\LockedException
804
-	 */
805
-	public function changeLock($path, $type, ILockingProvider $provider) {
806
-		$logger = $this->getLockLogger();
807
-		if ($logger) {
808
-			$typeString = ($type === ILockingProvider::LOCK_SHARED) ? 'shared' : 'exclusive';
809
-			$logger->info(
810
-				sprintf(
811
-					'change lock on "%s" to %s on storage "%s"',
812
-					$path,
813
-					$typeString,
814
-					$this->getId()
815
-				),
816
-				[
817
-					'app' => 'locking',
818
-				]
819
-			);
820
-		}
821
-		try {
822
-			$provider->changeLock('files/' . md5($this->getId() . '::' . trim($path, '/')), $type);
823
-		} catch (LockedException $e) {
824
-			\OC::$server->getLogger()->logException($e, ['level' => ILogger::INFO]);
825
-			throw $e;
826
-		}
827
-	}
828
-
829
-	private function getLockLogger() {
830
-		if (is_null($this->shouldLogLocks)) {
831
-			$this->shouldLogLocks = \OC::$server->getConfig()->getSystemValue('filelocking.debug', false);
832
-			$this->logger = $this->shouldLogLocks ? \OC::$server->getLogger() : null;
833
-		}
834
-		return $this->logger;
835
-	}
836
-
837
-	/**
838
-	 * @return array [ available, last_checked ]
839
-	 */
840
-	public function getAvailability() {
841
-		return $this->getStorageCache()->getAvailability();
842
-	}
843
-
844
-	/**
845
-	 * @param bool $isAvailable
846
-	 */
847
-	public function setAvailability($isAvailable) {
848
-		$this->getStorageCache()->setAvailability($isAvailable);
849
-	}
850
-
851
-	/**
852
-	 * @return bool
853
-	 */
854
-	public function needsPartFile() {
855
-		return true;
856
-	}
857
-
858
-	/**
859
-	 * fallback implementation
860
-	 *
861
-	 * @param string $path
862
-	 * @param resource $stream
863
-	 * @param int $size
864
-	 * @return int
865
-	 */
866
-	public function writeStream(string $path, $stream, int $size = null): int {
867
-		$target = $this->fopen($path, 'w');
868
-		if (!$target) {
869
-			throw new GenericFileException("Failed to open $path for writing");
870
-		}
871
-		try {
872
-			[$count, $result] = \OC_Helper::streamCopy($stream, $target);
873
-			if (!$result) {
874
-				throw new GenericFileException("Failed to copy stream");
875
-			}
876
-		} finally {
877
-			fclose($target);
878
-			fclose($stream);
879
-		}
880
-		return $count;
881
-	}
882
-
883
-	public function getDirectoryContent($directory): \Traversable {
884
-		$dh = $this->opendir($directory);
885
-		if (is_resource($dh)) {
886
-			$basePath = rtrim($directory, '/');
887
-			while (($file = readdir($dh)) !== false) {
888
-				if (!Filesystem::isIgnoredDir($file)) {
889
-					$childPath = $basePath . '/' . trim($file, '/');
890
-					$metadata = $this->getMetaData($childPath);
891
-					if ($metadata !== null) {
892
-						yield $metadata;
893
-					}
894
-				}
895
-			}
896
-		}
897
-	}
539
+                throw new InvalidCharacterInPathException();
540
+            }
541
+        }
542
+
543
+        // 255 characters is the limit on common file systems (ext/xfs)
544
+        // oc_filecache has a 250 char length limit for the filename
545
+        if (isset($fileName[250])) {
546
+            throw new FileNameTooLongException();
547
+        }
548
+
549
+        // NOTE: $path will remain unverified for now
550
+        $this->verifyPosixPath($fileName);
551
+    }
552
+
553
+    /**
554
+     * @param string $fileName
555
+     * @throws InvalidPathException
556
+     */
557
+    protected function verifyPosixPath($fileName) {
558
+        $this->scanForInvalidCharacters($fileName, "\\/");
559
+        $fileName = trim($fileName);
560
+        $reservedNames = ['*'];
561
+        if (in_array($fileName, $reservedNames)) {
562
+            throw new ReservedWordException();
563
+        }
564
+    }
565
+
566
+    /**
567
+     * @param string $fileName
568
+     * @param string $invalidChars
569
+     * @throws InvalidPathException
570
+     */
571
+    private function scanForInvalidCharacters($fileName, $invalidChars) {
572
+        foreach (str_split($invalidChars) as $char) {
573
+            if (strpos($fileName, $char) !== false) {
574
+                throw new InvalidCharacterInPathException();
575
+            }
576
+        }
577
+
578
+        $sanitizedFileName = filter_var($fileName, FILTER_UNSAFE_RAW, FILTER_FLAG_STRIP_LOW);
579
+        if ($sanitizedFileName !== $fileName) {
580
+            throw new InvalidCharacterInPathException();
581
+        }
582
+    }
583
+
584
+    /**
585
+     * @param array $options
586
+     */
587
+    public function setMountOptions(array $options) {
588
+        $this->mountOptions = $options;
589
+    }
590
+
591
+    /**
592
+     * @param string $name
593
+     * @param mixed $default
594
+     * @return mixed
595
+     */
596
+    public function getMountOption($name, $default = null) {
597
+        return isset($this->mountOptions[$name]) ? $this->mountOptions[$name] : $default;
598
+    }
599
+
600
+    /**
601
+     * @param IStorage $sourceStorage
602
+     * @param string $sourceInternalPath
603
+     * @param string $targetInternalPath
604
+     * @param bool $preserveMtime
605
+     * @return bool
606
+     */
607
+    public function copyFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath, $preserveMtime = false) {
608
+        if ($sourceStorage === $this) {
609
+            return $this->copy($sourceInternalPath, $targetInternalPath);
610
+        }
611
+
612
+        if ($sourceStorage->is_dir($sourceInternalPath)) {
613
+            $dh = $sourceStorage->opendir($sourceInternalPath);
614
+            $result = $this->mkdir($targetInternalPath);
615
+            if (is_resource($dh)) {
616
+                $result = true;
617
+                while ($result and ($file = readdir($dh)) !== false) {
618
+                    if (!Filesystem::isIgnoredDir($file)) {
619
+                        $result &= $this->copyFromStorage($sourceStorage, $sourceInternalPath . '/' . $file, $targetInternalPath . '/' . $file);
620
+                    }
621
+                }
622
+            }
623
+        } else {
624
+            $source = $sourceStorage->fopen($sourceInternalPath, 'r');
625
+            $result = false;
626
+            if ($source) {
627
+                try {
628
+                    $this->writeStream($targetInternalPath, $source);
629
+                    $result = true;
630
+                } catch (\Exception $e) {
631
+                    \OC::$server->getLogger()->logException($e, ['level' => ILogger::WARN, 'message' => 'Failed to copy stream to storage']);
632
+                }
633
+            }
634
+
635
+            if ($result && $preserveMtime) {
636
+                $mtime = $sourceStorage->filemtime($sourceInternalPath);
637
+                $this->touch($targetInternalPath, is_int($mtime) ? $mtime : null);
638
+            }
639
+
640
+            if (!$result) {
641
+                // delete partially written target file
642
+                $this->unlink($targetInternalPath);
643
+                // delete cache entry that was created by fopen
644
+                $this->getCache()->remove($targetInternalPath);
645
+            }
646
+        }
647
+        return (bool)$result;
648
+    }
649
+
650
+    /**
651
+     * Check if a storage is the same as the current one, including wrapped storages
652
+     *
653
+     * @param IStorage $storage
654
+     * @return bool
655
+     */
656
+    private function isSameStorage(IStorage $storage): bool {
657
+        while ($storage->instanceOfStorage(Wrapper::class)) {
658
+            /**
659
+             * @var Wrapper $sourceStorage
660
+             */
661
+            $storage = $storage->getWrapperStorage();
662
+        }
663
+
664
+        return $storage === $this;
665
+    }
666
+
667
+    /**
668
+     * @param IStorage $sourceStorage
669
+     * @param string $sourceInternalPath
670
+     * @param string $targetInternalPath
671
+     * @return bool
672
+     */
673
+    public function moveFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) {
674
+        if ($this->isSameStorage($sourceStorage)) {
675
+            // resolve any jailed paths
676
+            while ($sourceStorage->instanceOfStorage(Jail::class)) {
677
+                /**
678
+                 * @var Jail $sourceStorage
679
+                 */
680
+                $sourceInternalPath = $sourceStorage->getUnjailedPath($sourceInternalPath);
681
+                $sourceStorage = $sourceStorage->getUnjailedStorage();
682
+            }
683
+
684
+            return $this->rename($sourceInternalPath, $targetInternalPath);
685
+        }
686
+
687
+        if (!$sourceStorage->isDeletable($sourceInternalPath)) {
688
+            return false;
689
+        }
690
+
691
+        $result = $this->copyFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath, true);
692
+        if ($result) {
693
+            if ($sourceStorage->is_dir($sourceInternalPath)) {
694
+                $result = $result && $sourceStorage->rmdir($sourceInternalPath);
695
+            } else {
696
+                $result = $result && $sourceStorage->unlink($sourceInternalPath);
697
+            }
698
+        }
699
+        return $result;
700
+    }
701
+
702
+    /**
703
+     * @inheritdoc
704
+     */
705
+    public function getMetaData($path) {
706
+        if (Filesystem::isFileBlacklisted($path)) {
707
+            throw new ForbiddenException('Invalid path: ' . $path, false);
708
+        }
709
+
710
+        $permissions = $this->getPermissions($path);
711
+        if (!$permissions & \OCP\Constants::PERMISSION_READ) {
712
+            //can't read, nothing we can do
713
+            return null;
714
+        }
715
+
716
+        $data = [];
717
+        $data['mimetype'] = $this->getMimeType($path);
718
+        $data['mtime'] = $this->filemtime($path);
719
+        if ($data['mtime'] === false) {
720
+            $data['mtime'] = time();
721
+        }
722
+        if ($data['mimetype'] == 'httpd/unix-directory') {
723
+            $data['size'] = -1; //unknown
724
+        } else {
725
+            $data['size'] = $this->filesize($path);
726
+        }
727
+        $data['etag'] = $this->getETag($path);
728
+        $data['storage_mtime'] = $data['mtime'];
729
+        $data['permissions'] = $permissions;
730
+        $data['name'] = basename($path);
731
+
732
+        return $data;
733
+    }
734
+
735
+    /**
736
+     * @param string $path
737
+     * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
738
+     * @param \OCP\Lock\ILockingProvider $provider
739
+     * @throws \OCP\Lock\LockedException
740
+     */
741
+    public function acquireLock($path, $type, ILockingProvider $provider) {
742
+        $logger = $this->getLockLogger();
743
+        if ($logger) {
744
+            $typeString = ($type === ILockingProvider::LOCK_SHARED) ? 'shared' : 'exclusive';
745
+            $logger->info(
746
+                sprintf(
747
+                    'acquire %s lock on "%s" on storage "%s"',
748
+                    $typeString,
749
+                    $path,
750
+                    $this->getId()
751
+                ),
752
+                [
753
+                    'app' => 'locking',
754
+                ]
755
+            );
756
+        }
757
+        try {
758
+            $provider->acquireLock('files/' . md5($this->getId() . '::' . trim($path, '/')), $type, $this->getId() . '::' . $path);
759
+        } catch (LockedException $e) {
760
+            if ($logger) {
761
+                $logger->logException($e, ['level' => ILogger::INFO]);
762
+            }
763
+            throw $e;
764
+        }
765
+    }
766
+
767
+    /**
768
+     * @param string $path
769
+     * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
770
+     * @param \OCP\Lock\ILockingProvider $provider
771
+     * @throws \OCP\Lock\LockedException
772
+     */
773
+    public function releaseLock($path, $type, ILockingProvider $provider) {
774
+        $logger = $this->getLockLogger();
775
+        if ($logger) {
776
+            $typeString = ($type === ILockingProvider::LOCK_SHARED) ? 'shared' : 'exclusive';
777
+            $logger->info(
778
+                sprintf(
779
+                    'release %s lock on "%s" on storage "%s"',
780
+                    $typeString,
781
+                    $path,
782
+                    $this->getId()
783
+                ),
784
+                [
785
+                    'app' => 'locking',
786
+                ]
787
+            );
788
+        }
789
+        try {
790
+            $provider->releaseLock('files/' . md5($this->getId() . '::' . trim($path, '/')), $type);
791
+        } catch (LockedException $e) {
792
+            if ($logger) {
793
+                $logger->logException($e, ['level' => ILogger::INFO]);
794
+            }
795
+            throw $e;
796
+        }
797
+    }
798
+
799
+    /**
800
+     * @param string $path
801
+     * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
802
+     * @param \OCP\Lock\ILockingProvider $provider
803
+     * @throws \OCP\Lock\LockedException
804
+     */
805
+    public function changeLock($path, $type, ILockingProvider $provider) {
806
+        $logger = $this->getLockLogger();
807
+        if ($logger) {
808
+            $typeString = ($type === ILockingProvider::LOCK_SHARED) ? 'shared' : 'exclusive';
809
+            $logger->info(
810
+                sprintf(
811
+                    'change lock on "%s" to %s on storage "%s"',
812
+                    $path,
813
+                    $typeString,
814
+                    $this->getId()
815
+                ),
816
+                [
817
+                    'app' => 'locking',
818
+                ]
819
+            );
820
+        }
821
+        try {
822
+            $provider->changeLock('files/' . md5($this->getId() . '::' . trim($path, '/')), $type);
823
+        } catch (LockedException $e) {
824
+            \OC::$server->getLogger()->logException($e, ['level' => ILogger::INFO]);
825
+            throw $e;
826
+        }
827
+    }
828
+
829
+    private function getLockLogger() {
830
+        if (is_null($this->shouldLogLocks)) {
831
+            $this->shouldLogLocks = \OC::$server->getConfig()->getSystemValue('filelocking.debug', false);
832
+            $this->logger = $this->shouldLogLocks ? \OC::$server->getLogger() : null;
833
+        }
834
+        return $this->logger;
835
+    }
836
+
837
+    /**
838
+     * @return array [ available, last_checked ]
839
+     */
840
+    public function getAvailability() {
841
+        return $this->getStorageCache()->getAvailability();
842
+    }
843
+
844
+    /**
845
+     * @param bool $isAvailable
846
+     */
847
+    public function setAvailability($isAvailable) {
848
+        $this->getStorageCache()->setAvailability($isAvailable);
849
+    }
850
+
851
+    /**
852
+     * @return bool
853
+     */
854
+    public function needsPartFile() {
855
+        return true;
856
+    }
857
+
858
+    /**
859
+     * fallback implementation
860
+     *
861
+     * @param string $path
862
+     * @param resource $stream
863
+     * @param int $size
864
+     * @return int
865
+     */
866
+    public function writeStream(string $path, $stream, int $size = null): int {
867
+        $target = $this->fopen($path, 'w');
868
+        if (!$target) {
869
+            throw new GenericFileException("Failed to open $path for writing");
870
+        }
871
+        try {
872
+            [$count, $result] = \OC_Helper::streamCopy($stream, $target);
873
+            if (!$result) {
874
+                throw new GenericFileException("Failed to copy stream");
875
+            }
876
+        } finally {
877
+            fclose($target);
878
+            fclose($stream);
879
+        }
880
+        return $count;
881
+    }
882
+
883
+    public function getDirectoryContent($directory): \Traversable {
884
+        $dh = $this->opendir($directory);
885
+        if (is_resource($dh)) {
886
+            $basePath = rtrim($directory, '/');
887
+            while (($file = readdir($dh)) !== false) {
888
+                if (!Filesystem::isIgnoredDir($file)) {
889
+                    $childPath = $basePath . '/' . trim($file, '/');
890
+                    $metadata = $this->getMetaData($childPath);
891
+                    if ($metadata !== null) {
892
+                        yield $metadata;
893
+                    }
894
+                }
895
+            }
896
+        }
897
+    }
898 898
 }
Please login to merge, or discard this patch.
Spacing   +20 added lines, -20 removed lines patch added patch discarded remove patch
@@ -225,7 +225,7 @@  discard block
 block discarded – undo
225 225
 			$this->mkdir($path2);
226 226
 			while ($file = readdir($dir)) {
227 227
 				if (!Filesystem::isIgnoredDir($file)) {
228
-					if (!$this->copy($path1 . '/' . $file, $path2 . '/' . $file)) {
228
+					if (!$this->copy($path1.'/'.$file, $path2.'/'.$file)) {
229 229
 						return false;
230 230
 					}
231 231
 				}
@@ -279,12 +279,12 @@  discard block
 block discarded – undo
279 279
 		if (is_resource($dh)) {
280 280
 			while (($file = readdir($dh)) !== false) {
281 281
 				if (!\OC\Files\Filesystem::isIgnoredDir($file)) {
282
-					if ($this->is_dir($path . '/' . $file)) {
283
-						mkdir($target . '/' . $file);
284
-						$this->addLocalFolder($path . '/' . $file, $target . '/' . $file);
282
+					if ($this->is_dir($path.'/'.$file)) {
283
+						mkdir($target.'/'.$file);
284
+						$this->addLocalFolder($path.'/'.$file, $target.'/'.$file);
285 285
 					} else {
286
-						$tmp = $this->toTmpFile($path . '/' . $file);
287
-						rename($tmp, $target . '/' . $file);
286
+						$tmp = $this->toTmpFile($path.'/'.$file);
287
+						rename($tmp, $target.'/'.$file);
288 288
 					}
289 289
 				}
290 290
 			}
@@ -305,10 +305,10 @@  discard block
 block discarded – undo
305 305
 					continue;
306 306
 				}
307 307
 				if (strstr(strtolower($item), strtolower($query)) !== false) {
308
-					$files[] = $dir . '/' . $item;
308
+					$files[] = $dir.'/'.$item;
309 309
 				}
310
-				if ($this->is_dir($dir . '/' . $item)) {
311
-					$files = array_merge($files, $this->searchInDir($query, $dir . '/' . $item));
310
+				if ($this->is_dir($dir.'/'.$item)) {
311
+					$files = array_merge($files, $this->searchInDir($query, $dir.'/'.$item));
312 312
 				}
313 313
 			}
314 314
 		}
@@ -359,7 +359,7 @@  discard block
 block discarded – undo
359 359
 		if (!isset($this->watcher)) {
360 360
 			$this->watcher = new Watcher($storage);
361 361
 			$globalPolicy = \OC::$server->getConfig()->getSystemValue('filesystem_check_changes', Watcher::CHECK_NEVER);
362
-			$this->watcher->setPolicy((int)$this->getMountOption('filesystem_check_changes', $globalPolicy));
362
+			$this->watcher->setPolicy((int) $this->getMountOption('filesystem_check_changes', $globalPolicy));
363 363
 		}
364 364
 		return $this->watcher;
365 365
 	}
@@ -376,7 +376,7 @@  discard block
 block discarded – undo
376 376
 		}
377 377
 		if (!isset($storage->propagator)) {
378 378
 			$config = \OC::$server->getSystemConfig();
379
-			$storage->propagator = new Propagator($storage, \OC::$server->getDatabaseConnection(), ['appdata_' . $config->getValue('instanceid')]);
379
+			$storage->propagator = new Propagator($storage, \OC::$server->getDatabaseConnection(), ['appdata_'.$config->getValue('instanceid')]);
380 380
 		}
381 381
 		return $storage->propagator;
382 382
 	}
@@ -434,7 +434,7 @@  discard block
 block discarded – undo
434 434
 	 */
435 435
 	public function cleanPath($path) {
436 436
 		if (strlen($path) == 0 or $path[0] != '/') {
437
-			$path = '/' . $path;
437
+			$path = '/'.$path;
438 438
 		}
439 439
 
440 440
 		$output = [];
@@ -462,7 +462,7 @@  discard block
 block discarded – undo
462 462
 			\OC::$server->getLogger()->info("External storage not available: stat() failed");
463 463
 			return false;
464 464
 		} catch (\Exception $e) {
465
-			\OC::$server->getLogger()->warning("External storage not available: " . $e->getMessage());
465
+			\OC::$server->getLogger()->warning("External storage not available: ".$e->getMessage());
466 466
 			\OC::$server->getLogger()->logException($e, ['level' => ILogger::WARN]);
467 467
 			return false;
468 468
 		}
@@ -616,7 +616,7 @@  discard block
 block discarded – undo
616 616
 				$result = true;
617 617
 				while ($result and ($file = readdir($dh)) !== false) {
618 618
 					if (!Filesystem::isIgnoredDir($file)) {
619
-						$result &= $this->copyFromStorage($sourceStorage, $sourceInternalPath . '/' . $file, $targetInternalPath . '/' . $file);
619
+						$result &= $this->copyFromStorage($sourceStorage, $sourceInternalPath.'/'.$file, $targetInternalPath.'/'.$file);
620 620
 					}
621 621
 				}
622 622
 			}
@@ -644,7 +644,7 @@  discard block
 block discarded – undo
644 644
 				$this->getCache()->remove($targetInternalPath);
645 645
 			}
646 646
 		}
647
-		return (bool)$result;
647
+		return (bool) $result;
648 648
 	}
649 649
 
650 650
 	/**
@@ -704,7 +704,7 @@  discard block
 block discarded – undo
704 704
 	 */
705 705
 	public function getMetaData($path) {
706 706
 		if (Filesystem::isFileBlacklisted($path)) {
707
-			throw new ForbiddenException('Invalid path: ' . $path, false);
707
+			throw new ForbiddenException('Invalid path: '.$path, false);
708 708
 		}
709 709
 
710 710
 		$permissions = $this->getPermissions($path);
@@ -755,7 +755,7 @@  discard block
 block discarded – undo
755 755
 			);
756 756
 		}
757 757
 		try {
758
-			$provider->acquireLock('files/' . md5($this->getId() . '::' . trim($path, '/')), $type, $this->getId() . '::' . $path);
758
+			$provider->acquireLock('files/'.md5($this->getId().'::'.trim($path, '/')), $type, $this->getId().'::'.$path);
759 759
 		} catch (LockedException $e) {
760 760
 			if ($logger) {
761 761
 				$logger->logException($e, ['level' => ILogger::INFO]);
@@ -787,7 +787,7 @@  discard block
 block discarded – undo
787 787
 			);
788 788
 		}
789 789
 		try {
790
-			$provider->releaseLock('files/' . md5($this->getId() . '::' . trim($path, '/')), $type);
790
+			$provider->releaseLock('files/'.md5($this->getId().'::'.trim($path, '/')), $type);
791 791
 		} catch (LockedException $e) {
792 792
 			if ($logger) {
793 793
 				$logger->logException($e, ['level' => ILogger::INFO]);
@@ -819,7 +819,7 @@  discard block
 block discarded – undo
819 819
 			);
820 820
 		}
821 821
 		try {
822
-			$provider->changeLock('files/' . md5($this->getId() . '::' . trim($path, '/')), $type);
822
+			$provider->changeLock('files/'.md5($this->getId().'::'.trim($path, '/')), $type);
823 823
 		} catch (LockedException $e) {
824 824
 			\OC::$server->getLogger()->logException($e, ['level' => ILogger::INFO]);
825 825
 			throw $e;
@@ -886,7 +886,7 @@  discard block
 block discarded – undo
886 886
 			$basePath = rtrim($directory, '/');
887 887
 			while (($file = readdir($dh)) !== false) {
888 888
 				if (!Filesystem::isIgnoredDir($file)) {
889
-					$childPath = $basePath . '/' . trim($file, '/');
889
+					$childPath = $basePath.'/'.trim($file, '/');
890 890
 					$metadata = $this->getMetaData($childPath);
891 891
 					if ($metadata !== null) {
892 892
 						yield $metadata;
Please login to merge, or discard this patch.
lib/private/Files/Filesystem.php 1 patch
Indentation   +746 added lines, -746 removed lines patch added patch discarded remove patch
@@ -49,750 +49,750 @@
 block discarded – undo
49 49
 
50 50
 class Filesystem {
51 51
 
52
-	/**
53
-	 * @var Mount\Manager $mounts
54
-	 */
55
-	private static $mounts;
56
-
57
-	public static $loaded = false;
58
-	/**
59
-	 * @var \OC\Files\View $defaultInstance
60
-	 */
61
-	private static $defaultInstance;
62
-
63
-	private static $usersSetup = [];
64
-
65
-	private static $normalizedPathCache = null;
66
-
67
-	private static $listeningForProviders = false;
68
-
69
-	/** @var string[]|null */
70
-	private static $blacklist = null;
71
-
72
-	/**
73
-	 * classname which used for hooks handling
74
-	 * used as signalclass in OC_Hooks::emit()
75
-	 */
76
-	public const CLASSNAME = 'OC_Filesystem';
77
-
78
-	/**
79
-	 * signalname emitted before file renaming
80
-	 *
81
-	 * @param string $oldpath
82
-	 * @param string $newpath
83
-	 */
84
-	public const signal_rename = 'rename';
85
-
86
-	/**
87
-	 * signal emitted after file renaming
88
-	 *
89
-	 * @param string $oldpath
90
-	 * @param string $newpath
91
-	 */
92
-	public const signal_post_rename = 'post_rename';
93
-
94
-	/**
95
-	 * signal emitted before file/dir creation
96
-	 *
97
-	 * @param string $path
98
-	 * @param bool $run changing this flag to false in hook handler will cancel event
99
-	 */
100
-	public const signal_create = 'create';
101
-
102
-	/**
103
-	 * signal emitted after file/dir creation
104
-	 *
105
-	 * @param string $path
106
-	 * @param bool $run changing this flag to false in hook handler will cancel event
107
-	 */
108
-	public const signal_post_create = 'post_create';
109
-
110
-	/**
111
-	 * signal emits before file/dir copy
112
-	 *
113
-	 * @param string $oldpath
114
-	 * @param string $newpath
115
-	 * @param bool $run changing this flag to false in hook handler will cancel event
116
-	 */
117
-	public const signal_copy = 'copy';
118
-
119
-	/**
120
-	 * signal emits after file/dir copy
121
-	 *
122
-	 * @param string $oldpath
123
-	 * @param string $newpath
124
-	 */
125
-	public const signal_post_copy = 'post_copy';
126
-
127
-	/**
128
-	 * signal emits before file/dir save
129
-	 *
130
-	 * @param string $path
131
-	 * @param bool $run changing this flag to false in hook handler will cancel event
132
-	 */
133
-	public const signal_write = 'write';
134
-
135
-	/**
136
-	 * signal emits after file/dir save
137
-	 *
138
-	 * @param string $path
139
-	 */
140
-	public const signal_post_write = 'post_write';
141
-
142
-	/**
143
-	 * signal emitted before file/dir update
144
-	 *
145
-	 * @param string $path
146
-	 * @param bool $run changing this flag to false in hook handler will cancel event
147
-	 */
148
-	public const signal_update = 'update';
149
-
150
-	/**
151
-	 * signal emitted after file/dir update
152
-	 *
153
-	 * @param string $path
154
-	 * @param bool $run changing this flag to false in hook handler will cancel event
155
-	 */
156
-	public const signal_post_update = 'post_update';
157
-
158
-	/**
159
-	 * signal emits when reading file/dir
160
-	 *
161
-	 * @param string $path
162
-	 */
163
-	public const signal_read = 'read';
164
-
165
-	/**
166
-	 * signal emits when removing file/dir
167
-	 *
168
-	 * @param string $path
169
-	 */
170
-	public const signal_delete = 'delete';
171
-
172
-	/**
173
-	 * parameters definitions for signals
174
-	 */
175
-	public const signal_param_path = 'path';
176
-	public const signal_param_oldpath = 'oldpath';
177
-	public const signal_param_newpath = 'newpath';
178
-
179
-	/**
180
-	 * run - changing this flag to false in hook handler will cancel event
181
-	 */
182
-	public const signal_param_run = 'run';
183
-
184
-	public const signal_create_mount = 'create_mount';
185
-	public const signal_delete_mount = 'delete_mount';
186
-	public const signal_param_mount_type = 'mounttype';
187
-	public const signal_param_users = 'users';
188
-
189
-	/**
190
-	 * @var \OC\Files\Storage\StorageFactory $loader
191
-	 */
192
-	private static $loader;
193
-
194
-	/** @var bool */
195
-	private static $logWarningWhenAddingStorageWrapper = true;
196
-
197
-	/**
198
-	 * @param bool $shouldLog
199
-	 * @return bool previous value
200
-	 * @internal
201
-	 */
202
-	public static function logWarningWhenAddingStorageWrapper($shouldLog) {
203
-		$previousValue = self::$logWarningWhenAddingStorageWrapper;
204
-		self::$logWarningWhenAddingStorageWrapper = (bool) $shouldLog;
205
-		return $previousValue;
206
-	}
207
-
208
-	/**
209
-	 * @param string $wrapperName
210
-	 * @param callable $wrapper
211
-	 * @param int $priority
212
-	 */
213
-	public static function addStorageWrapper($wrapperName, $wrapper, $priority = 50) {
214
-		if (self::$logWarningWhenAddingStorageWrapper) {
215
-			\OC::$server->getLogger()->warning("Storage wrapper '{wrapper}' was not registered via the 'OC_Filesystem - preSetup' hook which could cause potential problems.", [
216
-				'wrapper' => $wrapperName,
217
-				'app' => 'filesystem',
218
-			]);
219
-		}
220
-
221
-		$mounts = self::getMountManager()->getAll();
222
-		if (!self::getLoader()->addStorageWrapper($wrapperName, $wrapper, $priority, $mounts)) {
223
-			// do not re-wrap if storage with this name already existed
224
-			return;
225
-		}
226
-	}
227
-
228
-	/**
229
-	 * Returns the storage factory
230
-	 *
231
-	 * @return IStorageFactory
232
-	 */
233
-	public static function getLoader() {
234
-		if (!self::$loader) {
235
-			self::$loader = \OC::$server->query(IStorageFactory::class);
236
-		}
237
-		return self::$loader;
238
-	}
239
-
240
-	/**
241
-	 * Returns the mount manager
242
-	 *
243
-	 * @return \OC\Files\Mount\Manager
244
-	 */
245
-	public static function getMountManager($user = '') {
246
-		self::initMountManager();
247
-		return self::$mounts;
248
-	}
249
-
250
-	/**
251
-	 * get the mountpoint of the storage object for a path
252
-	 * ( note: because a storage is not always mounted inside the fakeroot, the
253
-	 * returned mountpoint is relative to the absolute root of the filesystem
254
-	 * and doesn't take the chroot into account )
255
-	 *
256
-	 * @param string $path
257
-	 * @return string
258
-	 */
259
-	public static function getMountPoint($path) {
260
-		if (!self::$mounts) {
261
-			\OC_Util::setupFS();
262
-		}
263
-		$mount = self::$mounts->find($path);
264
-		return $mount->getMountPoint();
265
-	}
266
-
267
-	/**
268
-	 * get a list of all mount points in a directory
269
-	 *
270
-	 * @param string $path
271
-	 * @return string[]
272
-	 */
273
-	public static function getMountPoints($path) {
274
-		if (!self::$mounts) {
275
-			\OC_Util::setupFS();
276
-		}
277
-		$result = [];
278
-		$mounts = self::$mounts->findIn($path);
279
-		foreach ($mounts as $mount) {
280
-			$result[] = $mount->getMountPoint();
281
-		}
282
-		return $result;
283
-	}
284
-
285
-	/**
286
-	 * get the storage mounted at $mountPoint
287
-	 *
288
-	 * @param string $mountPoint
289
-	 * @return \OC\Files\Storage\Storage|null
290
-	 */
291
-	public static function getStorage($mountPoint) {
292
-		$mount = self::getMountManager()->find($mountPoint);
293
-		return $mount->getStorage();
294
-	}
295
-
296
-	/**
297
-	 * @param string $id
298
-	 * @return Mount\MountPoint[]
299
-	 */
300
-	public static function getMountByStorageId($id) {
301
-		return self::getMountManager()->findByStorageId($id);
302
-	}
303
-
304
-	/**
305
-	 * @param int $id
306
-	 * @return Mount\MountPoint[]
307
-	 */
308
-	public static function getMountByNumericId($id) {
309
-		return self::getMountManager()->findByNumericId($id);
310
-	}
311
-
312
-	/**
313
-	 * resolve a path to a storage and internal path
314
-	 *
315
-	 * @param string $path
316
-	 * @return array an array consisting of the storage and the internal path
317
-	 */
318
-	public static function resolvePath($path) {
319
-		$mount = self::getMountManager()->find($path);
320
-		return [$mount->getStorage(), rtrim($mount->getInternalPath($path), '/')];
321
-	}
322
-
323
-	public static function init($user, $root) {
324
-		if (self::$defaultInstance) {
325
-			return false;
326
-		}
327
-		self::getLoader();
328
-		self::$defaultInstance = new View($root);
329
-		/** @var IEventDispatcher $eventDispatcher */
330
-		$eventDispatcher = \OC::$server->get(IEventDispatcher::class);
331
-		$eventDispatcher->addListener(FilesystemTornDownEvent::class, function () {
332
-			self::$defaultInstance = null;
333
-			self::$usersSetup = [];
334
-			self::$loaded = false;
335
-		});
336
-
337
-		if (!self::$mounts) {
338
-			self::$mounts = \OC::$server->getMountManager();
339
-		}
340
-
341
-		//load custom mount config
342
-		self::initMountPoints($user);
343
-
344
-		self::$loaded = true;
345
-
346
-		return true;
347
-	}
348
-
349
-	public static function initMountManager() {
350
-		if (!self::$mounts) {
351
-			self::$mounts = \OC::$server->getMountManager();
352
-		}
353
-	}
354
-
355
-	/**
356
-	 * Initialize system and personal mount points for a user
357
-	 *
358
-	 * @param string|IUser|null $user
359
-	 * @throws \OC\User\NoUserException if the user is not available
360
-	 */
361
-	public static function initMountPoints($user = '') {
362
-		/** @var IUserManager $userManager */
363
-		$userManager = \OC::$server->get(IUserManager::class);
364
-
365
-		$userObject = ($user instanceof IUser) ? $user : $userManager->get($user);
366
-		if ($userObject) {
367
-			/** @var SetupManager $setupManager */
368
-			$setupManager = \OC::$server->get(SetupManager::class);
369
-			$setupManager->setupForUser($userObject);
370
-		} else {
371
-			throw new NoUserException();
372
-		}
373
-	}
374
-
375
-	/**
376
-	 * get the default filesystem view
377
-	 *
378
-	 * @return View
379
-	 */
380
-	public static function getView() {
381
-		return self::$defaultInstance;
382
-	}
383
-
384
-	/**
385
-	 * tear down the filesystem, removing all storage providers
386
-	 */
387
-	public static function tearDown() {
388
-		\OC_Util::tearDownFS();
389
-	}
390
-
391
-	/**
392
-	 * get the relative path of the root data directory for the current user
393
-	 *
394
-	 * @return string
395
-	 *
396
-	 * Returns path like /admin/files
397
-	 */
398
-	public static function getRoot() {
399
-		if (!self::$defaultInstance) {
400
-			return null;
401
-		}
402
-		return self::$defaultInstance->getRoot();
403
-	}
404
-
405
-	/**
406
-	 * mount an \OC\Files\Storage\Storage in our virtual filesystem
407
-	 *
408
-	 * @param \OC\Files\Storage\Storage|string $class
409
-	 * @param array $arguments
410
-	 * @param string $mountpoint
411
-	 */
412
-	public static function mount($class, $arguments, $mountpoint) {
413
-		if (!self::$mounts) {
414
-			\OC_Util::setupFS();
415
-		}
416
-		$mount = new Mount\MountPoint($class, $mountpoint, $arguments, self::getLoader());
417
-		self::$mounts->addMount($mount);
418
-	}
419
-
420
-	/**
421
-	 * return the path to a local version of the file
422
-	 * we need this because we can't know if a file is stored local or not from
423
-	 * outside the filestorage and for some purposes a local file is needed
424
-	 *
425
-	 * @param string $path
426
-	 * @return string
427
-	 */
428
-	public static function getLocalFile($path) {
429
-		return self::$defaultInstance->getLocalFile($path);
430
-	}
431
-
432
-	/**
433
-	 * @param string $path
434
-	 * @return string
435
-	 */
436
-	public static function getLocalFolder($path) {
437
-		return self::$defaultInstance->getLocalFolder($path);
438
-	}
439
-
440
-	/**
441
-	 * return path to file which reflects one visible in browser
442
-	 *
443
-	 * @param string $path
444
-	 * @return string
445
-	 */
446
-	public static function getLocalPath($path) {
447
-		$datadir = \OC_User::getHome(\OC_User::getUser()) . '/files';
448
-		$newpath = $path;
449
-		if (strncmp($newpath, $datadir, strlen($datadir)) == 0) {
450
-			$newpath = substr($path, strlen($datadir));
451
-		}
452
-		return $newpath;
453
-	}
454
-
455
-	/**
456
-	 * check if the requested path is valid
457
-	 *
458
-	 * @param string $path
459
-	 * @return bool
460
-	 */
461
-	public static function isValidPath($path) {
462
-		$path = self::normalizePath($path);
463
-		if (!$path || $path[0] !== '/') {
464
-			$path = '/' . $path;
465
-		}
466
-		if (strpos($path, '/../') !== false || strrchr($path, '/') === '/..') {
467
-			return false;
468
-		}
469
-		return true;
470
-	}
471
-
472
-	/**
473
-	 * checks if a file is blacklisted for storage in the filesystem
474
-	 * Listens to write and rename hooks
475
-	 *
476
-	 * @param array $data from hook
477
-	 */
478
-	public static function isBlacklisted($data) {
479
-		if (isset($data['path'])) {
480
-			$path = $data['path'];
481
-		} elseif (isset($data['newpath'])) {
482
-			$path = $data['newpath'];
483
-		}
484
-		if (isset($path)) {
485
-			if (self::isFileBlacklisted($path)) {
486
-				$data['run'] = false;
487
-			}
488
-		}
489
-	}
490
-
491
-	/**
492
-	 * @param string $filename
493
-	 * @return bool
494
-	 */
495
-	public static function isFileBlacklisted($filename) {
496
-		$filename = self::normalizePath($filename);
497
-
498
-		if (self::$blacklist === null) {
499
-			self::$blacklist = \OC::$server->getConfig()->getSystemValue('blacklisted_files', ['.htaccess']);
500
-		}
501
-
502
-		$filename = strtolower(basename($filename));
503
-		return in_array($filename, self::$blacklist);
504
-	}
505
-
506
-	/**
507
-	 * check if the directory should be ignored when scanning
508
-	 * NOTE: the special directories . and .. would cause never ending recursion
509
-	 *
510
-	 * @param string $dir
511
-	 * @return boolean
512
-	 */
513
-	public static function isIgnoredDir($dir) {
514
-		if ($dir === '.' || $dir === '..') {
515
-			return true;
516
-		}
517
-		return false;
518
-	}
519
-
520
-	/**
521
-	 * following functions are equivalent to their php builtin equivalents for arguments/return values.
522
-	 */
523
-	public static function mkdir($path) {
524
-		return self::$defaultInstance->mkdir($path);
525
-	}
526
-
527
-	public static function rmdir($path) {
528
-		return self::$defaultInstance->rmdir($path);
529
-	}
530
-
531
-	public static function is_dir($path) {
532
-		return self::$defaultInstance->is_dir($path);
533
-	}
534
-
535
-	public static function is_file($path) {
536
-		return self::$defaultInstance->is_file($path);
537
-	}
538
-
539
-	public static function stat($path) {
540
-		return self::$defaultInstance->stat($path);
541
-	}
542
-
543
-	public static function filetype($path) {
544
-		return self::$defaultInstance->filetype($path);
545
-	}
546
-
547
-	public static function filesize($path) {
548
-		return self::$defaultInstance->filesize($path);
549
-	}
550
-
551
-	public static function readfile($path) {
552
-		return self::$defaultInstance->readfile($path);
553
-	}
554
-
555
-	public static function isCreatable($path) {
556
-		return self::$defaultInstance->isCreatable($path);
557
-	}
558
-
559
-	public static function isReadable($path) {
560
-		return self::$defaultInstance->isReadable($path);
561
-	}
562
-
563
-	public static function isUpdatable($path) {
564
-		return self::$defaultInstance->isUpdatable($path);
565
-	}
566
-
567
-	public static function isDeletable($path) {
568
-		return self::$defaultInstance->isDeletable($path);
569
-	}
570
-
571
-	public static function isSharable($path) {
572
-		return self::$defaultInstance->isSharable($path);
573
-	}
574
-
575
-	public static function file_exists($path) {
576
-		return self::$defaultInstance->file_exists($path);
577
-	}
578
-
579
-	public static function filemtime($path) {
580
-		return self::$defaultInstance->filemtime($path);
581
-	}
582
-
583
-	public static function touch($path, $mtime = null) {
584
-		return self::$defaultInstance->touch($path, $mtime);
585
-	}
586
-
587
-	/**
588
-	 * @return string
589
-	 */
590
-	public static function file_get_contents($path) {
591
-		return self::$defaultInstance->file_get_contents($path);
592
-	}
593
-
594
-	public static function file_put_contents($path, $data) {
595
-		return self::$defaultInstance->file_put_contents($path, $data);
596
-	}
597
-
598
-	public static function unlink($path) {
599
-		return self::$defaultInstance->unlink($path);
600
-	}
601
-
602
-	public static function rename($path1, $path2) {
603
-		return self::$defaultInstance->rename($path1, $path2);
604
-	}
605
-
606
-	public static function copy($path1, $path2) {
607
-		return self::$defaultInstance->copy($path1, $path2);
608
-	}
609
-
610
-	public static function fopen($path, $mode) {
611
-		return self::$defaultInstance->fopen($path, $mode);
612
-	}
613
-
614
-	/**
615
-	 * @return string
616
-	 */
617
-	public static function toTmpFile($path) {
618
-		return self::$defaultInstance->toTmpFile($path);
619
-	}
620
-
621
-	public static function fromTmpFile($tmpFile, $path) {
622
-		return self::$defaultInstance->fromTmpFile($tmpFile, $path);
623
-	}
624
-
625
-	public static function getMimeType($path) {
626
-		return self::$defaultInstance->getMimeType($path);
627
-	}
628
-
629
-	public static function hash($type, $path, $raw = false) {
630
-		return self::$defaultInstance->hash($type, $path, $raw);
631
-	}
632
-
633
-	public static function free_space($path = '/') {
634
-		return self::$defaultInstance->free_space($path);
635
-	}
636
-
637
-	public static function search($query) {
638
-		return self::$defaultInstance->search($query);
639
-	}
640
-
641
-	/**
642
-	 * @param string $query
643
-	 */
644
-	public static function searchByMime($query) {
645
-		return self::$defaultInstance->searchByMime($query);
646
-	}
647
-
648
-	/**
649
-	 * @param string|int $tag name or tag id
650
-	 * @param string $userId owner of the tags
651
-	 * @return FileInfo[] array or file info
652
-	 */
653
-	public static function searchByTag($tag, $userId) {
654
-		return self::$defaultInstance->searchByTag($tag, $userId);
655
-	}
656
-
657
-	/**
658
-	 * check if a file or folder has been updated since $time
659
-	 *
660
-	 * @param string $path
661
-	 * @param int $time
662
-	 * @return bool
663
-	 */
664
-	public static function hasUpdated($path, $time) {
665
-		return self::$defaultInstance->hasUpdated($path, $time);
666
-	}
667
-
668
-	/**
669
-	 * Fix common problems with a file path
670
-	 *
671
-	 * @param string $path
672
-	 * @param bool $stripTrailingSlash whether to strip the trailing slash
673
-	 * @param bool $isAbsolutePath whether the given path is absolute
674
-	 * @param bool $keepUnicode true to disable unicode normalization
675
-	 * @return string
676
-	 */
677
-	public static function normalizePath($path, $stripTrailingSlash = true, $isAbsolutePath = false, $keepUnicode = false) {
678
-		/**
679
-		 * FIXME: This is a workaround for existing classes and files which call
680
-		 *        this function with another type than a valid string. This
681
-		 *        conversion should get removed as soon as all existing
682
-		 *        function calls have been fixed.
683
-		 */
684
-		$path = (string)$path;
685
-
686
-		if ($path === '') {
687
-			return '/';
688
-		}
689
-
690
-		if (is_null(self::$normalizedPathCache)) {
691
-			self::$normalizedPathCache = new CappedMemoryCache(2048);
692
-		}
693
-
694
-		$cacheKey = json_encode([$path, $stripTrailingSlash, $isAbsolutePath, $keepUnicode]);
695
-
696
-		if ($cacheKey && isset(self::$normalizedPathCache[$cacheKey])) {
697
-			return self::$normalizedPathCache[$cacheKey];
698
-		}
699
-
700
-		//normalize unicode if possible
701
-		if (!$keepUnicode) {
702
-			$path = \OC_Util::normalizeUnicode($path);
703
-		}
704
-
705
-		//add leading slash, if it is already there we strip it anyway
706
-		$path = '/' . $path;
707
-
708
-		$patterns = [
709
-			'#\\\\#s',       // no windows style '\\' slashes
710
-			'#/\.(/\.)*/#s', // remove '/./'
711
-			'#\//+#s',       // remove sequence of slashes
712
-			'#/\.$#s',       // remove trailing '/.'
713
-		];
714
-
715
-		do {
716
-			$count = 0;
717
-			$path = preg_replace($patterns, '/', $path, -1, $count);
718
-		} while ($count > 0);
719
-
720
-		//remove trailing slash
721
-		if ($stripTrailingSlash && strlen($path) > 1) {
722
-			$path = rtrim($path, '/');
723
-		}
724
-
725
-		self::$normalizedPathCache[$cacheKey] = $path;
726
-
727
-		return $path;
728
-	}
729
-
730
-	/**
731
-	 * get the filesystem info
732
-	 *
733
-	 * @param string $path
734
-	 * @param boolean $includeMountPoints whether to add mountpoint sizes,
735
-	 * defaults to true
736
-	 * @return \OC\Files\FileInfo|false False if file does not exist
737
-	 */
738
-	public static function getFileInfo($path, $includeMountPoints = true) {
739
-		return self::$defaultInstance->getFileInfo($path, $includeMountPoints);
740
-	}
741
-
742
-	/**
743
-	 * change file metadata
744
-	 *
745
-	 * @param string $path
746
-	 * @param array $data
747
-	 * @return int
748
-	 *
749
-	 * returns the fileid of the updated file
750
-	 */
751
-	public static function putFileInfo($path, $data) {
752
-		return self::$defaultInstance->putFileInfo($path, $data);
753
-	}
754
-
755
-	/**
756
-	 * get the content of a directory
757
-	 *
758
-	 * @param string $directory path under datadirectory
759
-	 * @param string $mimetype_filter limit returned content to this mimetype or mimepart
760
-	 * @return \OC\Files\FileInfo[]
761
-	 */
762
-	public static function getDirectoryContent($directory, $mimetype_filter = '') {
763
-		return self::$defaultInstance->getDirectoryContent($directory, $mimetype_filter);
764
-	}
765
-
766
-	/**
767
-	 * Get the path of a file by id
768
-	 *
769
-	 * Note that the resulting path is not guaranteed to be unique for the id, multiple paths can point to the same file
770
-	 *
771
-	 * @param int $id
772
-	 * @throws NotFoundException
773
-	 * @return string
774
-	 */
775
-	public static function getPath($id) {
776
-		return self::$defaultInstance->getPath($id);
777
-	}
778
-
779
-	/**
780
-	 * Get the owner for a file or folder
781
-	 *
782
-	 * @param string $path
783
-	 * @return string
784
-	 */
785
-	public static function getOwner($path) {
786
-		return self::$defaultInstance->getOwner($path);
787
-	}
788
-
789
-	/**
790
-	 * get the ETag for a file or folder
791
-	 *
792
-	 * @param string $path
793
-	 * @return string
794
-	 */
795
-	public static function getETag($path) {
796
-		return self::$defaultInstance->getETag($path);
797
-	}
52
+    /**
53
+     * @var Mount\Manager $mounts
54
+     */
55
+    private static $mounts;
56
+
57
+    public static $loaded = false;
58
+    /**
59
+     * @var \OC\Files\View $defaultInstance
60
+     */
61
+    private static $defaultInstance;
62
+
63
+    private static $usersSetup = [];
64
+
65
+    private static $normalizedPathCache = null;
66
+
67
+    private static $listeningForProviders = false;
68
+
69
+    /** @var string[]|null */
70
+    private static $blacklist = null;
71
+
72
+    /**
73
+     * classname which used for hooks handling
74
+     * used as signalclass in OC_Hooks::emit()
75
+     */
76
+    public const CLASSNAME = 'OC_Filesystem';
77
+
78
+    /**
79
+     * signalname emitted before file renaming
80
+     *
81
+     * @param string $oldpath
82
+     * @param string $newpath
83
+     */
84
+    public const signal_rename = 'rename';
85
+
86
+    /**
87
+     * signal emitted after file renaming
88
+     *
89
+     * @param string $oldpath
90
+     * @param string $newpath
91
+     */
92
+    public const signal_post_rename = 'post_rename';
93
+
94
+    /**
95
+     * signal emitted before file/dir creation
96
+     *
97
+     * @param string $path
98
+     * @param bool $run changing this flag to false in hook handler will cancel event
99
+     */
100
+    public const signal_create = 'create';
101
+
102
+    /**
103
+     * signal emitted after file/dir creation
104
+     *
105
+     * @param string $path
106
+     * @param bool $run changing this flag to false in hook handler will cancel event
107
+     */
108
+    public const signal_post_create = 'post_create';
109
+
110
+    /**
111
+     * signal emits before file/dir copy
112
+     *
113
+     * @param string $oldpath
114
+     * @param string $newpath
115
+     * @param bool $run changing this flag to false in hook handler will cancel event
116
+     */
117
+    public const signal_copy = 'copy';
118
+
119
+    /**
120
+     * signal emits after file/dir copy
121
+     *
122
+     * @param string $oldpath
123
+     * @param string $newpath
124
+     */
125
+    public const signal_post_copy = 'post_copy';
126
+
127
+    /**
128
+     * signal emits before file/dir save
129
+     *
130
+     * @param string $path
131
+     * @param bool $run changing this flag to false in hook handler will cancel event
132
+     */
133
+    public const signal_write = 'write';
134
+
135
+    /**
136
+     * signal emits after file/dir save
137
+     *
138
+     * @param string $path
139
+     */
140
+    public const signal_post_write = 'post_write';
141
+
142
+    /**
143
+     * signal emitted before file/dir update
144
+     *
145
+     * @param string $path
146
+     * @param bool $run changing this flag to false in hook handler will cancel event
147
+     */
148
+    public const signal_update = 'update';
149
+
150
+    /**
151
+     * signal emitted after file/dir update
152
+     *
153
+     * @param string $path
154
+     * @param bool $run changing this flag to false in hook handler will cancel event
155
+     */
156
+    public const signal_post_update = 'post_update';
157
+
158
+    /**
159
+     * signal emits when reading file/dir
160
+     *
161
+     * @param string $path
162
+     */
163
+    public const signal_read = 'read';
164
+
165
+    /**
166
+     * signal emits when removing file/dir
167
+     *
168
+     * @param string $path
169
+     */
170
+    public const signal_delete = 'delete';
171
+
172
+    /**
173
+     * parameters definitions for signals
174
+     */
175
+    public const signal_param_path = 'path';
176
+    public const signal_param_oldpath = 'oldpath';
177
+    public const signal_param_newpath = 'newpath';
178
+
179
+    /**
180
+     * run - changing this flag to false in hook handler will cancel event
181
+     */
182
+    public const signal_param_run = 'run';
183
+
184
+    public const signal_create_mount = 'create_mount';
185
+    public const signal_delete_mount = 'delete_mount';
186
+    public const signal_param_mount_type = 'mounttype';
187
+    public const signal_param_users = 'users';
188
+
189
+    /**
190
+     * @var \OC\Files\Storage\StorageFactory $loader
191
+     */
192
+    private static $loader;
193
+
194
+    /** @var bool */
195
+    private static $logWarningWhenAddingStorageWrapper = true;
196
+
197
+    /**
198
+     * @param bool $shouldLog
199
+     * @return bool previous value
200
+     * @internal
201
+     */
202
+    public static function logWarningWhenAddingStorageWrapper($shouldLog) {
203
+        $previousValue = self::$logWarningWhenAddingStorageWrapper;
204
+        self::$logWarningWhenAddingStorageWrapper = (bool) $shouldLog;
205
+        return $previousValue;
206
+    }
207
+
208
+    /**
209
+     * @param string $wrapperName
210
+     * @param callable $wrapper
211
+     * @param int $priority
212
+     */
213
+    public static function addStorageWrapper($wrapperName, $wrapper, $priority = 50) {
214
+        if (self::$logWarningWhenAddingStorageWrapper) {
215
+            \OC::$server->getLogger()->warning("Storage wrapper '{wrapper}' was not registered via the 'OC_Filesystem - preSetup' hook which could cause potential problems.", [
216
+                'wrapper' => $wrapperName,
217
+                'app' => 'filesystem',
218
+            ]);
219
+        }
220
+
221
+        $mounts = self::getMountManager()->getAll();
222
+        if (!self::getLoader()->addStorageWrapper($wrapperName, $wrapper, $priority, $mounts)) {
223
+            // do not re-wrap if storage with this name already existed
224
+            return;
225
+        }
226
+    }
227
+
228
+    /**
229
+     * Returns the storage factory
230
+     *
231
+     * @return IStorageFactory
232
+     */
233
+    public static function getLoader() {
234
+        if (!self::$loader) {
235
+            self::$loader = \OC::$server->query(IStorageFactory::class);
236
+        }
237
+        return self::$loader;
238
+    }
239
+
240
+    /**
241
+     * Returns the mount manager
242
+     *
243
+     * @return \OC\Files\Mount\Manager
244
+     */
245
+    public static function getMountManager($user = '') {
246
+        self::initMountManager();
247
+        return self::$mounts;
248
+    }
249
+
250
+    /**
251
+     * get the mountpoint of the storage object for a path
252
+     * ( note: because a storage is not always mounted inside the fakeroot, the
253
+     * returned mountpoint is relative to the absolute root of the filesystem
254
+     * and doesn't take the chroot into account )
255
+     *
256
+     * @param string $path
257
+     * @return string
258
+     */
259
+    public static function getMountPoint($path) {
260
+        if (!self::$mounts) {
261
+            \OC_Util::setupFS();
262
+        }
263
+        $mount = self::$mounts->find($path);
264
+        return $mount->getMountPoint();
265
+    }
266
+
267
+    /**
268
+     * get a list of all mount points in a directory
269
+     *
270
+     * @param string $path
271
+     * @return string[]
272
+     */
273
+    public static function getMountPoints($path) {
274
+        if (!self::$mounts) {
275
+            \OC_Util::setupFS();
276
+        }
277
+        $result = [];
278
+        $mounts = self::$mounts->findIn($path);
279
+        foreach ($mounts as $mount) {
280
+            $result[] = $mount->getMountPoint();
281
+        }
282
+        return $result;
283
+    }
284
+
285
+    /**
286
+     * get the storage mounted at $mountPoint
287
+     *
288
+     * @param string $mountPoint
289
+     * @return \OC\Files\Storage\Storage|null
290
+     */
291
+    public static function getStorage($mountPoint) {
292
+        $mount = self::getMountManager()->find($mountPoint);
293
+        return $mount->getStorage();
294
+    }
295
+
296
+    /**
297
+     * @param string $id
298
+     * @return Mount\MountPoint[]
299
+     */
300
+    public static function getMountByStorageId($id) {
301
+        return self::getMountManager()->findByStorageId($id);
302
+    }
303
+
304
+    /**
305
+     * @param int $id
306
+     * @return Mount\MountPoint[]
307
+     */
308
+    public static function getMountByNumericId($id) {
309
+        return self::getMountManager()->findByNumericId($id);
310
+    }
311
+
312
+    /**
313
+     * resolve a path to a storage and internal path
314
+     *
315
+     * @param string $path
316
+     * @return array an array consisting of the storage and the internal path
317
+     */
318
+    public static function resolvePath($path) {
319
+        $mount = self::getMountManager()->find($path);
320
+        return [$mount->getStorage(), rtrim($mount->getInternalPath($path), '/')];
321
+    }
322
+
323
+    public static function init($user, $root) {
324
+        if (self::$defaultInstance) {
325
+            return false;
326
+        }
327
+        self::getLoader();
328
+        self::$defaultInstance = new View($root);
329
+        /** @var IEventDispatcher $eventDispatcher */
330
+        $eventDispatcher = \OC::$server->get(IEventDispatcher::class);
331
+        $eventDispatcher->addListener(FilesystemTornDownEvent::class, function () {
332
+            self::$defaultInstance = null;
333
+            self::$usersSetup = [];
334
+            self::$loaded = false;
335
+        });
336
+
337
+        if (!self::$mounts) {
338
+            self::$mounts = \OC::$server->getMountManager();
339
+        }
340
+
341
+        //load custom mount config
342
+        self::initMountPoints($user);
343
+
344
+        self::$loaded = true;
345
+
346
+        return true;
347
+    }
348
+
349
+    public static function initMountManager() {
350
+        if (!self::$mounts) {
351
+            self::$mounts = \OC::$server->getMountManager();
352
+        }
353
+    }
354
+
355
+    /**
356
+     * Initialize system and personal mount points for a user
357
+     *
358
+     * @param string|IUser|null $user
359
+     * @throws \OC\User\NoUserException if the user is not available
360
+     */
361
+    public static function initMountPoints($user = '') {
362
+        /** @var IUserManager $userManager */
363
+        $userManager = \OC::$server->get(IUserManager::class);
364
+
365
+        $userObject = ($user instanceof IUser) ? $user : $userManager->get($user);
366
+        if ($userObject) {
367
+            /** @var SetupManager $setupManager */
368
+            $setupManager = \OC::$server->get(SetupManager::class);
369
+            $setupManager->setupForUser($userObject);
370
+        } else {
371
+            throw new NoUserException();
372
+        }
373
+    }
374
+
375
+    /**
376
+     * get the default filesystem view
377
+     *
378
+     * @return View
379
+     */
380
+    public static function getView() {
381
+        return self::$defaultInstance;
382
+    }
383
+
384
+    /**
385
+     * tear down the filesystem, removing all storage providers
386
+     */
387
+    public static function tearDown() {
388
+        \OC_Util::tearDownFS();
389
+    }
390
+
391
+    /**
392
+     * get the relative path of the root data directory for the current user
393
+     *
394
+     * @return string
395
+     *
396
+     * Returns path like /admin/files
397
+     */
398
+    public static function getRoot() {
399
+        if (!self::$defaultInstance) {
400
+            return null;
401
+        }
402
+        return self::$defaultInstance->getRoot();
403
+    }
404
+
405
+    /**
406
+     * mount an \OC\Files\Storage\Storage in our virtual filesystem
407
+     *
408
+     * @param \OC\Files\Storage\Storage|string $class
409
+     * @param array $arguments
410
+     * @param string $mountpoint
411
+     */
412
+    public static function mount($class, $arguments, $mountpoint) {
413
+        if (!self::$mounts) {
414
+            \OC_Util::setupFS();
415
+        }
416
+        $mount = new Mount\MountPoint($class, $mountpoint, $arguments, self::getLoader());
417
+        self::$mounts->addMount($mount);
418
+    }
419
+
420
+    /**
421
+     * return the path to a local version of the file
422
+     * we need this because we can't know if a file is stored local or not from
423
+     * outside the filestorage and for some purposes a local file is needed
424
+     *
425
+     * @param string $path
426
+     * @return string
427
+     */
428
+    public static function getLocalFile($path) {
429
+        return self::$defaultInstance->getLocalFile($path);
430
+    }
431
+
432
+    /**
433
+     * @param string $path
434
+     * @return string
435
+     */
436
+    public static function getLocalFolder($path) {
437
+        return self::$defaultInstance->getLocalFolder($path);
438
+    }
439
+
440
+    /**
441
+     * return path to file which reflects one visible in browser
442
+     *
443
+     * @param string $path
444
+     * @return string
445
+     */
446
+    public static function getLocalPath($path) {
447
+        $datadir = \OC_User::getHome(\OC_User::getUser()) . '/files';
448
+        $newpath = $path;
449
+        if (strncmp($newpath, $datadir, strlen($datadir)) == 0) {
450
+            $newpath = substr($path, strlen($datadir));
451
+        }
452
+        return $newpath;
453
+    }
454
+
455
+    /**
456
+     * check if the requested path is valid
457
+     *
458
+     * @param string $path
459
+     * @return bool
460
+     */
461
+    public static function isValidPath($path) {
462
+        $path = self::normalizePath($path);
463
+        if (!$path || $path[0] !== '/') {
464
+            $path = '/' . $path;
465
+        }
466
+        if (strpos($path, '/../') !== false || strrchr($path, '/') === '/..') {
467
+            return false;
468
+        }
469
+        return true;
470
+    }
471
+
472
+    /**
473
+     * checks if a file is blacklisted for storage in the filesystem
474
+     * Listens to write and rename hooks
475
+     *
476
+     * @param array $data from hook
477
+     */
478
+    public static function isBlacklisted($data) {
479
+        if (isset($data['path'])) {
480
+            $path = $data['path'];
481
+        } elseif (isset($data['newpath'])) {
482
+            $path = $data['newpath'];
483
+        }
484
+        if (isset($path)) {
485
+            if (self::isFileBlacklisted($path)) {
486
+                $data['run'] = false;
487
+            }
488
+        }
489
+    }
490
+
491
+    /**
492
+     * @param string $filename
493
+     * @return bool
494
+     */
495
+    public static function isFileBlacklisted($filename) {
496
+        $filename = self::normalizePath($filename);
497
+
498
+        if (self::$blacklist === null) {
499
+            self::$blacklist = \OC::$server->getConfig()->getSystemValue('blacklisted_files', ['.htaccess']);
500
+        }
501
+
502
+        $filename = strtolower(basename($filename));
503
+        return in_array($filename, self::$blacklist);
504
+    }
505
+
506
+    /**
507
+     * check if the directory should be ignored when scanning
508
+     * NOTE: the special directories . and .. would cause never ending recursion
509
+     *
510
+     * @param string $dir
511
+     * @return boolean
512
+     */
513
+    public static function isIgnoredDir($dir) {
514
+        if ($dir === '.' || $dir === '..') {
515
+            return true;
516
+        }
517
+        return false;
518
+    }
519
+
520
+    /**
521
+     * following functions are equivalent to their php builtin equivalents for arguments/return values.
522
+     */
523
+    public static function mkdir($path) {
524
+        return self::$defaultInstance->mkdir($path);
525
+    }
526
+
527
+    public static function rmdir($path) {
528
+        return self::$defaultInstance->rmdir($path);
529
+    }
530
+
531
+    public static function is_dir($path) {
532
+        return self::$defaultInstance->is_dir($path);
533
+    }
534
+
535
+    public static function is_file($path) {
536
+        return self::$defaultInstance->is_file($path);
537
+    }
538
+
539
+    public static function stat($path) {
540
+        return self::$defaultInstance->stat($path);
541
+    }
542
+
543
+    public static function filetype($path) {
544
+        return self::$defaultInstance->filetype($path);
545
+    }
546
+
547
+    public static function filesize($path) {
548
+        return self::$defaultInstance->filesize($path);
549
+    }
550
+
551
+    public static function readfile($path) {
552
+        return self::$defaultInstance->readfile($path);
553
+    }
554
+
555
+    public static function isCreatable($path) {
556
+        return self::$defaultInstance->isCreatable($path);
557
+    }
558
+
559
+    public static function isReadable($path) {
560
+        return self::$defaultInstance->isReadable($path);
561
+    }
562
+
563
+    public static function isUpdatable($path) {
564
+        return self::$defaultInstance->isUpdatable($path);
565
+    }
566
+
567
+    public static function isDeletable($path) {
568
+        return self::$defaultInstance->isDeletable($path);
569
+    }
570
+
571
+    public static function isSharable($path) {
572
+        return self::$defaultInstance->isSharable($path);
573
+    }
574
+
575
+    public static function file_exists($path) {
576
+        return self::$defaultInstance->file_exists($path);
577
+    }
578
+
579
+    public static function filemtime($path) {
580
+        return self::$defaultInstance->filemtime($path);
581
+    }
582
+
583
+    public static function touch($path, $mtime = null) {
584
+        return self::$defaultInstance->touch($path, $mtime);
585
+    }
586
+
587
+    /**
588
+     * @return string
589
+     */
590
+    public static function file_get_contents($path) {
591
+        return self::$defaultInstance->file_get_contents($path);
592
+    }
593
+
594
+    public static function file_put_contents($path, $data) {
595
+        return self::$defaultInstance->file_put_contents($path, $data);
596
+    }
597
+
598
+    public static function unlink($path) {
599
+        return self::$defaultInstance->unlink($path);
600
+    }
601
+
602
+    public static function rename($path1, $path2) {
603
+        return self::$defaultInstance->rename($path1, $path2);
604
+    }
605
+
606
+    public static function copy($path1, $path2) {
607
+        return self::$defaultInstance->copy($path1, $path2);
608
+    }
609
+
610
+    public static function fopen($path, $mode) {
611
+        return self::$defaultInstance->fopen($path, $mode);
612
+    }
613
+
614
+    /**
615
+     * @return string
616
+     */
617
+    public static function toTmpFile($path) {
618
+        return self::$defaultInstance->toTmpFile($path);
619
+    }
620
+
621
+    public static function fromTmpFile($tmpFile, $path) {
622
+        return self::$defaultInstance->fromTmpFile($tmpFile, $path);
623
+    }
624
+
625
+    public static function getMimeType($path) {
626
+        return self::$defaultInstance->getMimeType($path);
627
+    }
628
+
629
+    public static function hash($type, $path, $raw = false) {
630
+        return self::$defaultInstance->hash($type, $path, $raw);
631
+    }
632
+
633
+    public static function free_space($path = '/') {
634
+        return self::$defaultInstance->free_space($path);
635
+    }
636
+
637
+    public static function search($query) {
638
+        return self::$defaultInstance->search($query);
639
+    }
640
+
641
+    /**
642
+     * @param string $query
643
+     */
644
+    public static function searchByMime($query) {
645
+        return self::$defaultInstance->searchByMime($query);
646
+    }
647
+
648
+    /**
649
+     * @param string|int $tag name or tag id
650
+     * @param string $userId owner of the tags
651
+     * @return FileInfo[] array or file info
652
+     */
653
+    public static function searchByTag($tag, $userId) {
654
+        return self::$defaultInstance->searchByTag($tag, $userId);
655
+    }
656
+
657
+    /**
658
+     * check if a file or folder has been updated since $time
659
+     *
660
+     * @param string $path
661
+     * @param int $time
662
+     * @return bool
663
+     */
664
+    public static function hasUpdated($path, $time) {
665
+        return self::$defaultInstance->hasUpdated($path, $time);
666
+    }
667
+
668
+    /**
669
+     * Fix common problems with a file path
670
+     *
671
+     * @param string $path
672
+     * @param bool $stripTrailingSlash whether to strip the trailing slash
673
+     * @param bool $isAbsolutePath whether the given path is absolute
674
+     * @param bool $keepUnicode true to disable unicode normalization
675
+     * @return string
676
+     */
677
+    public static function normalizePath($path, $stripTrailingSlash = true, $isAbsolutePath = false, $keepUnicode = false) {
678
+        /**
679
+         * FIXME: This is a workaround for existing classes and files which call
680
+         *        this function with another type than a valid string. This
681
+         *        conversion should get removed as soon as all existing
682
+         *        function calls have been fixed.
683
+         */
684
+        $path = (string)$path;
685
+
686
+        if ($path === '') {
687
+            return '/';
688
+        }
689
+
690
+        if (is_null(self::$normalizedPathCache)) {
691
+            self::$normalizedPathCache = new CappedMemoryCache(2048);
692
+        }
693
+
694
+        $cacheKey = json_encode([$path, $stripTrailingSlash, $isAbsolutePath, $keepUnicode]);
695
+
696
+        if ($cacheKey && isset(self::$normalizedPathCache[$cacheKey])) {
697
+            return self::$normalizedPathCache[$cacheKey];
698
+        }
699
+
700
+        //normalize unicode if possible
701
+        if (!$keepUnicode) {
702
+            $path = \OC_Util::normalizeUnicode($path);
703
+        }
704
+
705
+        //add leading slash, if it is already there we strip it anyway
706
+        $path = '/' . $path;
707
+
708
+        $patterns = [
709
+            '#\\\\#s',       // no windows style '\\' slashes
710
+            '#/\.(/\.)*/#s', // remove '/./'
711
+            '#\//+#s',       // remove sequence of slashes
712
+            '#/\.$#s',       // remove trailing '/.'
713
+        ];
714
+
715
+        do {
716
+            $count = 0;
717
+            $path = preg_replace($patterns, '/', $path, -1, $count);
718
+        } while ($count > 0);
719
+
720
+        //remove trailing slash
721
+        if ($stripTrailingSlash && strlen($path) > 1) {
722
+            $path = rtrim($path, '/');
723
+        }
724
+
725
+        self::$normalizedPathCache[$cacheKey] = $path;
726
+
727
+        return $path;
728
+    }
729
+
730
+    /**
731
+     * get the filesystem info
732
+     *
733
+     * @param string $path
734
+     * @param boolean $includeMountPoints whether to add mountpoint sizes,
735
+     * defaults to true
736
+     * @return \OC\Files\FileInfo|false False if file does not exist
737
+     */
738
+    public static function getFileInfo($path, $includeMountPoints = true) {
739
+        return self::$defaultInstance->getFileInfo($path, $includeMountPoints);
740
+    }
741
+
742
+    /**
743
+     * change file metadata
744
+     *
745
+     * @param string $path
746
+     * @param array $data
747
+     * @return int
748
+     *
749
+     * returns the fileid of the updated file
750
+     */
751
+    public static function putFileInfo($path, $data) {
752
+        return self::$defaultInstance->putFileInfo($path, $data);
753
+    }
754
+
755
+    /**
756
+     * get the content of a directory
757
+     *
758
+     * @param string $directory path under datadirectory
759
+     * @param string $mimetype_filter limit returned content to this mimetype or mimepart
760
+     * @return \OC\Files\FileInfo[]
761
+     */
762
+    public static function getDirectoryContent($directory, $mimetype_filter = '') {
763
+        return self::$defaultInstance->getDirectoryContent($directory, $mimetype_filter);
764
+    }
765
+
766
+    /**
767
+     * Get the path of a file by id
768
+     *
769
+     * Note that the resulting path is not guaranteed to be unique for the id, multiple paths can point to the same file
770
+     *
771
+     * @param int $id
772
+     * @throws NotFoundException
773
+     * @return string
774
+     */
775
+    public static function getPath($id) {
776
+        return self::$defaultInstance->getPath($id);
777
+    }
778
+
779
+    /**
780
+     * Get the owner for a file or folder
781
+     *
782
+     * @param string $path
783
+     * @return string
784
+     */
785
+    public static function getOwner($path) {
786
+        return self::$defaultInstance->getOwner($path);
787
+    }
788
+
789
+    /**
790
+     * get the ETag for a file or folder
791
+     *
792
+     * @param string $path
793
+     * @return string
794
+     */
795
+    public static function getETag($path) {
796
+        return self::$defaultInstance->getETag($path);
797
+    }
798 798
 }
Please login to merge, or discard this patch.
lib/private/Files/Cache/Scanner.php 1 patch
Indentation   +508 added lines, -508 removed lines patch added patch discarded remove patch
@@ -56,512 +56,512 @@
 block discarded – undo
56 56
  * @package OC\Files\Cache
57 57
  */
58 58
 class Scanner extends BasicEmitter implements IScanner {
59
-	/**
60
-	 * @var \OC\Files\Storage\Storage $storage
61
-	 */
62
-	protected $storage;
63
-
64
-	/**
65
-	 * @var string $storageId
66
-	 */
67
-	protected $storageId;
68
-
69
-	/**
70
-	 * @var \OC\Files\Cache\Cache $cache
71
-	 */
72
-	protected $cache;
73
-
74
-	/**
75
-	 * @var boolean $cacheActive If true, perform cache operations, if false, do not affect cache
76
-	 */
77
-	protected $cacheActive;
78
-
79
-	/**
80
-	 * @var bool $useTransactions whether to use transactions
81
-	 */
82
-	protected $useTransactions = true;
83
-
84
-	/**
85
-	 * @var \OCP\Lock\ILockingProvider
86
-	 */
87
-	protected $lockingProvider;
88
-
89
-	public function __construct(\OC\Files\Storage\Storage $storage) {
90
-		$this->storage = $storage;
91
-		$this->storageId = $this->storage->getId();
92
-		$this->cache = $storage->getCache();
93
-		$this->cacheActive = !\OC::$server->getConfig()->getSystemValue('filesystem_cache_readonly', false);
94
-		$this->lockingProvider = \OC::$server->getLockingProvider();
95
-	}
96
-
97
-	/**
98
-	 * Whether to wrap the scanning of a folder in a database transaction
99
-	 * On default transactions are used
100
-	 *
101
-	 * @param bool $useTransactions
102
-	 */
103
-	public function setUseTransactions($useTransactions) {
104
-		$this->useTransactions = $useTransactions;
105
-	}
106
-
107
-	/**
108
-	 * get all the metadata of a file or folder
109
-	 * *
110
-	 *
111
-	 * @param string $path
112
-	 * @return array|null an array of metadata of the file
113
-	 */
114
-	protected function getData($path) {
115
-		$data = $this->storage->getMetaData($path);
116
-		if (is_null($data)) {
117
-			\OCP\Util::writeLog(Scanner::class, "!!! Path '$path' is not accessible or present !!!", ILogger::DEBUG);
118
-		}
119
-		return $data;
120
-	}
121
-
122
-	/**
123
-	 * scan a single file and store it in the cache
124
-	 *
125
-	 * @param string $file
126
-	 * @param int $reuseExisting
127
-	 * @param int $parentId
128
-	 * @param array|null|false $cacheData existing data in the cache for the file to be scanned
129
-	 * @param bool $lock set to false to disable getting an additional read lock during scanning
130
-	 * @param null $data the metadata for the file, as returned by the storage
131
-	 * @return array|null an array of metadata of the scanned file
132
-	 * @throws \OCP\Lock\LockedException
133
-	 */
134
-	public function scanFile($file, $reuseExisting = 0, $parentId = -1, $cacheData = null, $lock = true, $data = null) {
135
-		if ($file !== '') {
136
-			try {
137
-				$this->storage->verifyPath(dirname($file), basename($file));
138
-			} catch (\Exception $e) {
139
-				return null;
140
-			}
141
-		}
142
-		// only proceed if $file is not a partial file, blacklist is handled by the storage
143
-		if (!self::isPartialFile($file)) {
144
-
145
-			//acquire a lock
146
-			if ($lock) {
147
-				if ($this->storage->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) {
148
-					$this->storage->acquireLock($file, ILockingProvider::LOCK_SHARED, $this->lockingProvider);
149
-				}
150
-			}
151
-
152
-			try {
153
-				$data = $data ?? $this->getData($file);
154
-			} catch (ForbiddenException $e) {
155
-				if ($lock) {
156
-					if ($this->storage->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) {
157
-						$this->storage->releaseLock($file, ILockingProvider::LOCK_SHARED, $this->lockingProvider);
158
-					}
159
-				}
160
-
161
-				return null;
162
-			}
163
-
164
-			try {
165
-				if ($data) {
166
-
167
-					// pre-emit only if it was a file. By that we avoid counting/treating folders as files
168
-					if ($data['mimetype'] !== 'httpd/unix-directory') {
169
-						$this->emit('\OC\Files\Cache\Scanner', 'scanFile', [$file, $this->storageId]);
170
-						\OC_Hook::emit('\OC\Files\Cache\Scanner', 'scan_file', ['path' => $file, 'storage' => $this->storageId]);
171
-					}
172
-
173
-					$parent = dirname($file);
174
-					if ($parent === '.' or $parent === '/') {
175
-						$parent = '';
176
-					}
177
-					if ($parentId === -1) {
178
-						$parentId = $this->cache->getParentId($file);
179
-					}
180
-
181
-					// scan the parent if it's not in the cache (id -1) and the current file is not the root folder
182
-					if ($file and $parentId === -1) {
183
-						$parentData = $this->scanFile($parent);
184
-						if (!$parentData) {
185
-							return null;
186
-						}
187
-						$parentId = $parentData['fileid'];
188
-					}
189
-					if ($parent) {
190
-						$data['parent'] = $parentId;
191
-					}
192
-					if (is_null($cacheData)) {
193
-						/** @var CacheEntry $cacheData */
194
-						$cacheData = $this->cache->get($file);
195
-					}
196
-					if ($cacheData and $reuseExisting and isset($cacheData['fileid'])) {
197
-						// prevent empty etag
198
-						if (empty($cacheData['etag'])) {
199
-							$etag = $data['etag'];
200
-						} else {
201
-							$etag = $cacheData['etag'];
202
-						}
203
-						$fileId = $cacheData['fileid'];
204
-						$data['fileid'] = $fileId;
205
-						// only reuse data if the file hasn't explicitly changed
206
-						if (isset($data['storage_mtime']) && isset($cacheData['storage_mtime']) && $data['storage_mtime'] === $cacheData['storage_mtime']) {
207
-							$data['mtime'] = $cacheData['mtime'];
208
-							if (($reuseExisting & self::REUSE_SIZE) && ($data['size'] === -1)) {
209
-								$data['size'] = $cacheData['size'];
210
-							}
211
-							if ($reuseExisting & self::REUSE_ETAG) {
212
-								$data['etag'] = $etag;
213
-							}
214
-						}
215
-						// Only update metadata that has changed
216
-						$newData = array_diff_assoc($data, $cacheData->getData());
217
-					} else {
218
-						$newData = $data;
219
-						$fileId = -1;
220
-					}
221
-					if (!empty($newData)) {
222
-						// Reset the checksum if the data has changed
223
-						$newData['checksum'] = '';
224
-						$newData['parent'] = $parentId;
225
-						$data['fileid'] = $this->addToCache($file, $newData, $fileId);
226
-					}
227
-					if ($cacheData && isset($cacheData['size'])) {
228
-						$data['oldSize'] = $cacheData['size'];
229
-					} else {
230
-						$data['oldSize'] = 0;
231
-					}
232
-
233
-					if ($cacheData && isset($cacheData['encrypted'])) {
234
-						$data['encrypted'] = $cacheData['encrypted'];
235
-					}
236
-
237
-					// post-emit only if it was a file. By that we avoid counting/treating folders as files
238
-					if ($data['mimetype'] !== 'httpd/unix-directory') {
239
-						$this->emit('\OC\Files\Cache\Scanner', 'postScanFile', [$file, $this->storageId]);
240
-						\OC_Hook::emit('\OC\Files\Cache\Scanner', 'post_scan_file', ['path' => $file, 'storage' => $this->storageId]);
241
-					}
242
-				} else {
243
-					$this->removeFromCache($file);
244
-				}
245
-			} catch (\Exception $e) {
246
-				if ($lock) {
247
-					if ($this->storage->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) {
248
-						$this->storage->releaseLock($file, ILockingProvider::LOCK_SHARED, $this->lockingProvider);
249
-					}
250
-				}
251
-				throw $e;
252
-			}
253
-
254
-			//release the acquired lock
255
-			if ($lock) {
256
-				if ($this->storage->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) {
257
-					$this->storage->releaseLock($file, ILockingProvider::LOCK_SHARED, $this->lockingProvider);
258
-				}
259
-			}
260
-
261
-			if ($data && !isset($data['encrypted'])) {
262
-				$data['encrypted'] = false;
263
-			}
264
-			return $data;
265
-		}
266
-
267
-		return null;
268
-	}
269
-
270
-	protected function removeFromCache($path) {
271
-		\OC_Hook::emit('Scanner', 'removeFromCache', ['file' => $path]);
272
-		$this->emit('\OC\Files\Cache\Scanner', 'removeFromCache', [$path]);
273
-		if ($this->cacheActive) {
274
-			$this->cache->remove($path);
275
-		}
276
-	}
277
-
278
-	/**
279
-	 * @param string $path
280
-	 * @param array $data
281
-	 * @param int $fileId
282
-	 * @return int the id of the added file
283
-	 */
284
-	protected function addToCache($path, $data, $fileId = -1) {
285
-		if (isset($data['scan_permissions'])) {
286
-			$data['permissions'] = $data['scan_permissions'];
287
-		}
288
-		\OC_Hook::emit('Scanner', 'addToCache', ['file' => $path, 'data' => $data]);
289
-		$this->emit('\OC\Files\Cache\Scanner', 'addToCache', [$path, $this->storageId, $data]);
290
-		if ($this->cacheActive) {
291
-			if ($fileId !== -1) {
292
-				$this->cache->update($fileId, $data);
293
-				return $fileId;
294
-			} else {
295
-				return $this->cache->insert($path, $data);
296
-			}
297
-		} else {
298
-			return -1;
299
-		}
300
-	}
301
-
302
-	/**
303
-	 * @param string $path
304
-	 * @param array $data
305
-	 * @param int $fileId
306
-	 */
307
-	protected function updateCache($path, $data, $fileId = -1) {
308
-		\OC_Hook::emit('Scanner', 'addToCache', ['file' => $path, 'data' => $data]);
309
-		$this->emit('\OC\Files\Cache\Scanner', 'updateCache', [$path, $this->storageId, $data]);
310
-		if ($this->cacheActive) {
311
-			if ($fileId !== -1) {
312
-				$this->cache->update($fileId, $data);
313
-			} else {
314
-				$this->cache->put($path, $data);
315
-			}
316
-		}
317
-	}
318
-
319
-	/**
320
-	 * scan a folder and all it's children
321
-	 *
322
-	 * @param string $path
323
-	 * @param bool $recursive
324
-	 * @param int $reuse
325
-	 * @param bool $lock set to false to disable getting an additional read lock during scanning
326
-	 * @return array|null an array of the meta data of the scanned file or folder
327
-	 */
328
-	public function scan($path, $recursive = self::SCAN_RECURSIVE, $reuse = -1, $lock = true) {
329
-		if ($reuse === -1) {
330
-			$reuse = ($recursive === self::SCAN_SHALLOW) ? self::REUSE_ETAG | self::REUSE_SIZE : self::REUSE_ETAG;
331
-		}
332
-		if ($lock) {
333
-			if ($this->storage->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) {
334
-				$this->storage->acquireLock('scanner::' . $path, ILockingProvider::LOCK_EXCLUSIVE, $this->lockingProvider);
335
-				$this->storage->acquireLock($path, ILockingProvider::LOCK_SHARED, $this->lockingProvider);
336
-			}
337
-		}
338
-		try {
339
-			$data = $this->scanFile($path, $reuse, -1, null, $lock);
340
-			if ($data and $data['mimetype'] === 'httpd/unix-directory') {
341
-				$size = $this->scanChildren($path, $recursive, $reuse, $data['fileid'], $lock);
342
-				$data['size'] = $size;
343
-			}
344
-		} finally {
345
-			if ($lock) {
346
-				if ($this->storage->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) {
347
-					$this->storage->releaseLock($path, ILockingProvider::LOCK_SHARED, $this->lockingProvider);
348
-					$this->storage->releaseLock('scanner::' . $path, ILockingProvider::LOCK_EXCLUSIVE, $this->lockingProvider);
349
-				}
350
-			}
351
-		}
352
-		return $data;
353
-	}
354
-
355
-	/**
356
-	 * Get the children currently in the cache
357
-	 *
358
-	 * @param int $folderId
359
-	 * @return array[]
360
-	 */
361
-	protected function getExistingChildren($folderId) {
362
-		$existingChildren = [];
363
-		$children = $this->cache->getFolderContentsById($folderId);
364
-		foreach ($children as $child) {
365
-			$existingChildren[$child['name']] = $child;
366
-		}
367
-		return $existingChildren;
368
-	}
369
-
370
-	/**
371
-	 * scan all the files and folders in a folder
372
-	 *
373
-	 * @param string $path
374
-	 * @param bool $recursive
375
-	 * @param int $reuse
376
-	 * @param int $folderId id for the folder to be scanned
377
-	 * @param bool $lock set to false to disable getting an additional read lock during scanning
378
-	 * @return int the size of the scanned folder or -1 if the size is unknown at this stage
379
-	 */
380
-	protected function scanChildren($path, $recursive = self::SCAN_RECURSIVE, $reuse = -1, $folderId = null, $lock = true) {
381
-		if ($reuse === -1) {
382
-			$reuse = ($recursive === self::SCAN_SHALLOW) ? self::REUSE_ETAG | self::REUSE_SIZE : self::REUSE_ETAG;
383
-		}
384
-		$this->emit('\OC\Files\Cache\Scanner', 'scanFolder', [$path, $this->storageId]);
385
-		$size = 0;
386
-		if (!is_null($folderId)) {
387
-			$folderId = $this->cache->getId($path);
388
-		}
389
-		$childQueue = $this->handleChildren($path, $recursive, $reuse, $folderId, $lock, $size);
390
-
391
-		foreach ($childQueue as $child => $childId) {
392
-			$childSize = $this->scanChildren($child, $recursive, $reuse, $childId, $lock);
393
-			if ($childSize === -1) {
394
-				$size = -1;
395
-			} elseif ($size !== -1) {
396
-				$size += $childSize;
397
-			}
398
-		}
399
-		if ($this->cacheActive) {
400
-			$this->cache->update($folderId, ['size' => $size]);
401
-		}
402
-		$this->emit('\OC\Files\Cache\Scanner', 'postScanFolder', [$path, $this->storageId]);
403
-		return $size;
404
-	}
405
-
406
-	private function handleChildren($path, $recursive, $reuse, $folderId, $lock, &$size) {
407
-		// we put this in it's own function so it cleans up the memory before we start recursing
408
-		$existingChildren = $this->getExistingChildren($folderId);
409
-		$newChildren = iterator_to_array($this->storage->getDirectoryContent($path));
410
-
411
-		if ($this->useTransactions) {
412
-			\OC::$server->getDatabaseConnection()->beginTransaction();
413
-		}
414
-
415
-		$exceptionOccurred = false;
416
-		$childQueue = [];
417
-		$newChildNames = [];
418
-		foreach ($newChildren as $fileMeta) {
419
-			$permissions = isset($fileMeta['scan_permissions']) ? $fileMeta['scan_permissions'] : $fileMeta['permissions'];
420
-			if ($permissions === 0) {
421
-				continue;
422
-			}
423
-			$originalFile = $fileMeta['name'];
424
-			$file = trim(\OC\Files\Filesystem::normalizePath($originalFile), '/');
425
-			if (trim($originalFile, '/') !== $file) {
426
-				// encoding mismatch, might require compatibility wrapper
427
-				\OC::$server->getLogger()->debug('Scanner: Skipping non-normalized file name "'. $originalFile . '" in path "' . $path . '".', ['app' => 'core']);
428
-				$this->emit('\OC\Files\Cache\Scanner', 'normalizedNameMismatch', [$path ? $path . '/' . $originalFile : $originalFile]);
429
-				// skip this entry
430
-				continue;
431
-			}
432
-
433
-			$newChildNames[] = $file;
434
-			$child = $path ? $path . '/' . $file : $file;
435
-			try {
436
-				$existingData = isset($existingChildren[$file]) ? $existingChildren[$file] : false;
437
-				$data = $this->scanFile($child, $reuse, $folderId, $existingData, $lock, $fileMeta);
438
-				if ($data) {
439
-					if ($data['mimetype'] === 'httpd/unix-directory' and $recursive === self::SCAN_RECURSIVE) {
440
-						$childQueue[$child] = $data['fileid'];
441
-					} elseif ($data['mimetype'] === 'httpd/unix-directory' and $recursive === self::SCAN_RECURSIVE_INCOMPLETE and $data['size'] === -1) {
442
-						// only recurse into folders which aren't fully scanned
443
-						$childQueue[$child] = $data['fileid'];
444
-					} elseif ($data['size'] === -1) {
445
-						$size = -1;
446
-					} elseif ($size !== -1) {
447
-						$size += $data['size'];
448
-					}
449
-				}
450
-			} catch (Exception $ex) {
451
-				// might happen if inserting duplicate while a scanning
452
-				// process is running in parallel
453
-				// log and ignore
454
-				if ($this->useTransactions) {
455
-					\OC::$server->getDatabaseConnection()->rollback();
456
-					\OC::$server->getDatabaseConnection()->beginTransaction();
457
-				}
458
-				\OC::$server->getLogger()->logException($ex, [
459
-					'message' => 'Exception while scanning file "' . $child . '"',
460
-					'level' => ILogger::DEBUG,
461
-					'app' => 'core',
462
-				]);
463
-				$exceptionOccurred = true;
464
-			} catch (\OCP\Lock\LockedException $e) {
465
-				if ($this->useTransactions) {
466
-					\OC::$server->getDatabaseConnection()->rollback();
467
-				}
468
-				throw $e;
469
-			}
470
-		}
471
-		$removedChildren = \array_diff(array_keys($existingChildren), $newChildNames);
472
-		foreach ($removedChildren as $childName) {
473
-			$child = $path ? $path . '/' . $childName : $childName;
474
-			$this->removeFromCache($child);
475
-		}
476
-		if ($this->useTransactions) {
477
-			\OC::$server->getDatabaseConnection()->commit();
478
-		}
479
-		if ($exceptionOccurred) {
480
-			// It might happen that the parallel scan process has already
481
-			// inserted mimetypes but those weren't available yet inside the transaction
482
-			// To make sure to have the updated mime types in such cases,
483
-			// we reload them here
484
-			\OC::$server->getMimeTypeLoader()->reset();
485
-		}
486
-		return $childQueue;
487
-	}
488
-
489
-	/**
490
-	 * check if the file should be ignored when scanning
491
-	 * NOTE: files with a '.part' extension are ignored as well!
492
-	 *       prevents unfinished put requests to be scanned
493
-	 *
494
-	 * @param string $file
495
-	 * @return boolean
496
-	 */
497
-	public static function isPartialFile($file) {
498
-		if (pathinfo($file, PATHINFO_EXTENSION) === 'part') {
499
-			return true;
500
-		}
501
-		if (strpos($file, '.part/') !== false) {
502
-			return true;
503
-		}
504
-
505
-		return false;
506
-	}
507
-
508
-	/**
509
-	 * walk over any folders that are not fully scanned yet and scan them
510
-	 */
511
-	public function backgroundScan() {
512
-		if ($this->storage->instanceOfStorage(Jail::class)) {
513
-			// for jail storage wrappers (shares, groupfolders) we run the background scan on the source storage
514
-			// this is mainly done because the jail wrapper doesn't implement `getIncomplete` (because it would be inefficient).
515
-			//
516
-			// Running the scan on the source storage might scan more than "needed", but the unscanned files outside the jail will
517
-			// have to be scanned at some point anyway.
518
-			$unJailedScanner = $this->storage->getUnjailedStorage()->getScanner();
519
-			$unJailedScanner->backgroundScan();
520
-		} else {
521
-			if (!$this->cache->inCache('')) {
522
-				// if the storage isn't in the cache yet, just scan the root completely
523
-				$this->runBackgroundScanJob(function () {
524
-					$this->scan('', self::SCAN_RECURSIVE, self::REUSE_ETAG);
525
-				}, '');
526
-			} else {
527
-				$lastPath = null;
528
-				// find any path marked as unscanned and run the scanner until no more paths are unscanned (or we get stuck)
529
-				while (($path = $this->cache->getIncomplete()) !== false && $path !== $lastPath) {
530
-					$this->runBackgroundScanJob(function () use ($path) {
531
-						$this->scan($path, self::SCAN_RECURSIVE_INCOMPLETE, self::REUSE_ETAG | self::REUSE_SIZE);
532
-					}, $path);
533
-					// FIXME: this won't proceed with the next item, needs revamping of getIncomplete()
534
-					// to make this possible
535
-					$lastPath = $path;
536
-				}
537
-			}
538
-		}
539
-	}
540
-
541
-	private function runBackgroundScanJob(callable $callback, $path) {
542
-		try {
543
-			$callback();
544
-			\OC_Hook::emit('Scanner', 'correctFolderSize', ['path' => $path]);
545
-			if ($this->cacheActive && $this->cache instanceof Cache) {
546
-				$this->cache->correctFolderSize($path, null, true);
547
-			}
548
-		} catch (\OCP\Files\StorageInvalidException $e) {
549
-			// skip unavailable storages
550
-		} catch (\OCP\Files\StorageNotAvailableException $e) {
551
-			// skip unavailable storages
552
-		} catch (\OCP\Files\ForbiddenException $e) {
553
-			// skip forbidden storages
554
-		} catch (\OCP\Lock\LockedException $e) {
555
-			// skip unavailable storages
556
-		}
557
-	}
558
-
559
-	/**
560
-	 * Set whether the cache is affected by scan operations
561
-	 *
562
-	 * @param boolean $active The active state of the cache
563
-	 */
564
-	public function setCacheActive($active) {
565
-		$this->cacheActive = $active;
566
-	}
59
+    /**
60
+     * @var \OC\Files\Storage\Storage $storage
61
+     */
62
+    protected $storage;
63
+
64
+    /**
65
+     * @var string $storageId
66
+     */
67
+    protected $storageId;
68
+
69
+    /**
70
+     * @var \OC\Files\Cache\Cache $cache
71
+     */
72
+    protected $cache;
73
+
74
+    /**
75
+     * @var boolean $cacheActive If true, perform cache operations, if false, do not affect cache
76
+     */
77
+    protected $cacheActive;
78
+
79
+    /**
80
+     * @var bool $useTransactions whether to use transactions
81
+     */
82
+    protected $useTransactions = true;
83
+
84
+    /**
85
+     * @var \OCP\Lock\ILockingProvider
86
+     */
87
+    protected $lockingProvider;
88
+
89
+    public function __construct(\OC\Files\Storage\Storage $storage) {
90
+        $this->storage = $storage;
91
+        $this->storageId = $this->storage->getId();
92
+        $this->cache = $storage->getCache();
93
+        $this->cacheActive = !\OC::$server->getConfig()->getSystemValue('filesystem_cache_readonly', false);
94
+        $this->lockingProvider = \OC::$server->getLockingProvider();
95
+    }
96
+
97
+    /**
98
+     * Whether to wrap the scanning of a folder in a database transaction
99
+     * On default transactions are used
100
+     *
101
+     * @param bool $useTransactions
102
+     */
103
+    public function setUseTransactions($useTransactions) {
104
+        $this->useTransactions = $useTransactions;
105
+    }
106
+
107
+    /**
108
+     * get all the metadata of a file or folder
109
+     * *
110
+     *
111
+     * @param string $path
112
+     * @return array|null an array of metadata of the file
113
+     */
114
+    protected function getData($path) {
115
+        $data = $this->storage->getMetaData($path);
116
+        if (is_null($data)) {
117
+            \OCP\Util::writeLog(Scanner::class, "!!! Path '$path' is not accessible or present !!!", ILogger::DEBUG);
118
+        }
119
+        return $data;
120
+    }
121
+
122
+    /**
123
+     * scan a single file and store it in the cache
124
+     *
125
+     * @param string $file
126
+     * @param int $reuseExisting
127
+     * @param int $parentId
128
+     * @param array|null|false $cacheData existing data in the cache for the file to be scanned
129
+     * @param bool $lock set to false to disable getting an additional read lock during scanning
130
+     * @param null $data the metadata for the file, as returned by the storage
131
+     * @return array|null an array of metadata of the scanned file
132
+     * @throws \OCP\Lock\LockedException
133
+     */
134
+    public function scanFile($file, $reuseExisting = 0, $parentId = -1, $cacheData = null, $lock = true, $data = null) {
135
+        if ($file !== '') {
136
+            try {
137
+                $this->storage->verifyPath(dirname($file), basename($file));
138
+            } catch (\Exception $e) {
139
+                return null;
140
+            }
141
+        }
142
+        // only proceed if $file is not a partial file, blacklist is handled by the storage
143
+        if (!self::isPartialFile($file)) {
144
+
145
+            //acquire a lock
146
+            if ($lock) {
147
+                if ($this->storage->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) {
148
+                    $this->storage->acquireLock($file, ILockingProvider::LOCK_SHARED, $this->lockingProvider);
149
+                }
150
+            }
151
+
152
+            try {
153
+                $data = $data ?? $this->getData($file);
154
+            } catch (ForbiddenException $e) {
155
+                if ($lock) {
156
+                    if ($this->storage->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) {
157
+                        $this->storage->releaseLock($file, ILockingProvider::LOCK_SHARED, $this->lockingProvider);
158
+                    }
159
+                }
160
+
161
+                return null;
162
+            }
163
+
164
+            try {
165
+                if ($data) {
166
+
167
+                    // pre-emit only if it was a file. By that we avoid counting/treating folders as files
168
+                    if ($data['mimetype'] !== 'httpd/unix-directory') {
169
+                        $this->emit('\OC\Files\Cache\Scanner', 'scanFile', [$file, $this->storageId]);
170
+                        \OC_Hook::emit('\OC\Files\Cache\Scanner', 'scan_file', ['path' => $file, 'storage' => $this->storageId]);
171
+                    }
172
+
173
+                    $parent = dirname($file);
174
+                    if ($parent === '.' or $parent === '/') {
175
+                        $parent = '';
176
+                    }
177
+                    if ($parentId === -1) {
178
+                        $parentId = $this->cache->getParentId($file);
179
+                    }
180
+
181
+                    // scan the parent if it's not in the cache (id -1) and the current file is not the root folder
182
+                    if ($file and $parentId === -1) {
183
+                        $parentData = $this->scanFile($parent);
184
+                        if (!$parentData) {
185
+                            return null;
186
+                        }
187
+                        $parentId = $parentData['fileid'];
188
+                    }
189
+                    if ($parent) {
190
+                        $data['parent'] = $parentId;
191
+                    }
192
+                    if (is_null($cacheData)) {
193
+                        /** @var CacheEntry $cacheData */
194
+                        $cacheData = $this->cache->get($file);
195
+                    }
196
+                    if ($cacheData and $reuseExisting and isset($cacheData['fileid'])) {
197
+                        // prevent empty etag
198
+                        if (empty($cacheData['etag'])) {
199
+                            $etag = $data['etag'];
200
+                        } else {
201
+                            $etag = $cacheData['etag'];
202
+                        }
203
+                        $fileId = $cacheData['fileid'];
204
+                        $data['fileid'] = $fileId;
205
+                        // only reuse data if the file hasn't explicitly changed
206
+                        if (isset($data['storage_mtime']) && isset($cacheData['storage_mtime']) && $data['storage_mtime'] === $cacheData['storage_mtime']) {
207
+                            $data['mtime'] = $cacheData['mtime'];
208
+                            if (($reuseExisting & self::REUSE_SIZE) && ($data['size'] === -1)) {
209
+                                $data['size'] = $cacheData['size'];
210
+                            }
211
+                            if ($reuseExisting & self::REUSE_ETAG) {
212
+                                $data['etag'] = $etag;
213
+                            }
214
+                        }
215
+                        // Only update metadata that has changed
216
+                        $newData = array_diff_assoc($data, $cacheData->getData());
217
+                    } else {
218
+                        $newData = $data;
219
+                        $fileId = -1;
220
+                    }
221
+                    if (!empty($newData)) {
222
+                        // Reset the checksum if the data has changed
223
+                        $newData['checksum'] = '';
224
+                        $newData['parent'] = $parentId;
225
+                        $data['fileid'] = $this->addToCache($file, $newData, $fileId);
226
+                    }
227
+                    if ($cacheData && isset($cacheData['size'])) {
228
+                        $data['oldSize'] = $cacheData['size'];
229
+                    } else {
230
+                        $data['oldSize'] = 0;
231
+                    }
232
+
233
+                    if ($cacheData && isset($cacheData['encrypted'])) {
234
+                        $data['encrypted'] = $cacheData['encrypted'];
235
+                    }
236
+
237
+                    // post-emit only if it was a file. By that we avoid counting/treating folders as files
238
+                    if ($data['mimetype'] !== 'httpd/unix-directory') {
239
+                        $this->emit('\OC\Files\Cache\Scanner', 'postScanFile', [$file, $this->storageId]);
240
+                        \OC_Hook::emit('\OC\Files\Cache\Scanner', 'post_scan_file', ['path' => $file, 'storage' => $this->storageId]);
241
+                    }
242
+                } else {
243
+                    $this->removeFromCache($file);
244
+                }
245
+            } catch (\Exception $e) {
246
+                if ($lock) {
247
+                    if ($this->storage->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) {
248
+                        $this->storage->releaseLock($file, ILockingProvider::LOCK_SHARED, $this->lockingProvider);
249
+                    }
250
+                }
251
+                throw $e;
252
+            }
253
+
254
+            //release the acquired lock
255
+            if ($lock) {
256
+                if ($this->storage->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) {
257
+                    $this->storage->releaseLock($file, ILockingProvider::LOCK_SHARED, $this->lockingProvider);
258
+                }
259
+            }
260
+
261
+            if ($data && !isset($data['encrypted'])) {
262
+                $data['encrypted'] = false;
263
+            }
264
+            return $data;
265
+        }
266
+
267
+        return null;
268
+    }
269
+
270
+    protected function removeFromCache($path) {
271
+        \OC_Hook::emit('Scanner', 'removeFromCache', ['file' => $path]);
272
+        $this->emit('\OC\Files\Cache\Scanner', 'removeFromCache', [$path]);
273
+        if ($this->cacheActive) {
274
+            $this->cache->remove($path);
275
+        }
276
+    }
277
+
278
+    /**
279
+     * @param string $path
280
+     * @param array $data
281
+     * @param int $fileId
282
+     * @return int the id of the added file
283
+     */
284
+    protected function addToCache($path, $data, $fileId = -1) {
285
+        if (isset($data['scan_permissions'])) {
286
+            $data['permissions'] = $data['scan_permissions'];
287
+        }
288
+        \OC_Hook::emit('Scanner', 'addToCache', ['file' => $path, 'data' => $data]);
289
+        $this->emit('\OC\Files\Cache\Scanner', 'addToCache', [$path, $this->storageId, $data]);
290
+        if ($this->cacheActive) {
291
+            if ($fileId !== -1) {
292
+                $this->cache->update($fileId, $data);
293
+                return $fileId;
294
+            } else {
295
+                return $this->cache->insert($path, $data);
296
+            }
297
+        } else {
298
+            return -1;
299
+        }
300
+    }
301
+
302
+    /**
303
+     * @param string $path
304
+     * @param array $data
305
+     * @param int $fileId
306
+     */
307
+    protected function updateCache($path, $data, $fileId = -1) {
308
+        \OC_Hook::emit('Scanner', 'addToCache', ['file' => $path, 'data' => $data]);
309
+        $this->emit('\OC\Files\Cache\Scanner', 'updateCache', [$path, $this->storageId, $data]);
310
+        if ($this->cacheActive) {
311
+            if ($fileId !== -1) {
312
+                $this->cache->update($fileId, $data);
313
+            } else {
314
+                $this->cache->put($path, $data);
315
+            }
316
+        }
317
+    }
318
+
319
+    /**
320
+     * scan a folder and all it's children
321
+     *
322
+     * @param string $path
323
+     * @param bool $recursive
324
+     * @param int $reuse
325
+     * @param bool $lock set to false to disable getting an additional read lock during scanning
326
+     * @return array|null an array of the meta data of the scanned file or folder
327
+     */
328
+    public function scan($path, $recursive = self::SCAN_RECURSIVE, $reuse = -1, $lock = true) {
329
+        if ($reuse === -1) {
330
+            $reuse = ($recursive === self::SCAN_SHALLOW) ? self::REUSE_ETAG | self::REUSE_SIZE : self::REUSE_ETAG;
331
+        }
332
+        if ($lock) {
333
+            if ($this->storage->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) {
334
+                $this->storage->acquireLock('scanner::' . $path, ILockingProvider::LOCK_EXCLUSIVE, $this->lockingProvider);
335
+                $this->storage->acquireLock($path, ILockingProvider::LOCK_SHARED, $this->lockingProvider);
336
+            }
337
+        }
338
+        try {
339
+            $data = $this->scanFile($path, $reuse, -1, null, $lock);
340
+            if ($data and $data['mimetype'] === 'httpd/unix-directory') {
341
+                $size = $this->scanChildren($path, $recursive, $reuse, $data['fileid'], $lock);
342
+                $data['size'] = $size;
343
+            }
344
+        } finally {
345
+            if ($lock) {
346
+                if ($this->storage->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) {
347
+                    $this->storage->releaseLock($path, ILockingProvider::LOCK_SHARED, $this->lockingProvider);
348
+                    $this->storage->releaseLock('scanner::' . $path, ILockingProvider::LOCK_EXCLUSIVE, $this->lockingProvider);
349
+                }
350
+            }
351
+        }
352
+        return $data;
353
+    }
354
+
355
+    /**
356
+     * Get the children currently in the cache
357
+     *
358
+     * @param int $folderId
359
+     * @return array[]
360
+     */
361
+    protected function getExistingChildren($folderId) {
362
+        $existingChildren = [];
363
+        $children = $this->cache->getFolderContentsById($folderId);
364
+        foreach ($children as $child) {
365
+            $existingChildren[$child['name']] = $child;
366
+        }
367
+        return $existingChildren;
368
+    }
369
+
370
+    /**
371
+     * scan all the files and folders in a folder
372
+     *
373
+     * @param string $path
374
+     * @param bool $recursive
375
+     * @param int $reuse
376
+     * @param int $folderId id for the folder to be scanned
377
+     * @param bool $lock set to false to disable getting an additional read lock during scanning
378
+     * @return int the size of the scanned folder or -1 if the size is unknown at this stage
379
+     */
380
+    protected function scanChildren($path, $recursive = self::SCAN_RECURSIVE, $reuse = -1, $folderId = null, $lock = true) {
381
+        if ($reuse === -1) {
382
+            $reuse = ($recursive === self::SCAN_SHALLOW) ? self::REUSE_ETAG | self::REUSE_SIZE : self::REUSE_ETAG;
383
+        }
384
+        $this->emit('\OC\Files\Cache\Scanner', 'scanFolder', [$path, $this->storageId]);
385
+        $size = 0;
386
+        if (!is_null($folderId)) {
387
+            $folderId = $this->cache->getId($path);
388
+        }
389
+        $childQueue = $this->handleChildren($path, $recursive, $reuse, $folderId, $lock, $size);
390
+
391
+        foreach ($childQueue as $child => $childId) {
392
+            $childSize = $this->scanChildren($child, $recursive, $reuse, $childId, $lock);
393
+            if ($childSize === -1) {
394
+                $size = -1;
395
+            } elseif ($size !== -1) {
396
+                $size += $childSize;
397
+            }
398
+        }
399
+        if ($this->cacheActive) {
400
+            $this->cache->update($folderId, ['size' => $size]);
401
+        }
402
+        $this->emit('\OC\Files\Cache\Scanner', 'postScanFolder', [$path, $this->storageId]);
403
+        return $size;
404
+    }
405
+
406
+    private function handleChildren($path, $recursive, $reuse, $folderId, $lock, &$size) {
407
+        // we put this in it's own function so it cleans up the memory before we start recursing
408
+        $existingChildren = $this->getExistingChildren($folderId);
409
+        $newChildren = iterator_to_array($this->storage->getDirectoryContent($path));
410
+
411
+        if ($this->useTransactions) {
412
+            \OC::$server->getDatabaseConnection()->beginTransaction();
413
+        }
414
+
415
+        $exceptionOccurred = false;
416
+        $childQueue = [];
417
+        $newChildNames = [];
418
+        foreach ($newChildren as $fileMeta) {
419
+            $permissions = isset($fileMeta['scan_permissions']) ? $fileMeta['scan_permissions'] : $fileMeta['permissions'];
420
+            if ($permissions === 0) {
421
+                continue;
422
+            }
423
+            $originalFile = $fileMeta['name'];
424
+            $file = trim(\OC\Files\Filesystem::normalizePath($originalFile), '/');
425
+            if (trim($originalFile, '/') !== $file) {
426
+                // encoding mismatch, might require compatibility wrapper
427
+                \OC::$server->getLogger()->debug('Scanner: Skipping non-normalized file name "'. $originalFile . '" in path "' . $path . '".', ['app' => 'core']);
428
+                $this->emit('\OC\Files\Cache\Scanner', 'normalizedNameMismatch', [$path ? $path . '/' . $originalFile : $originalFile]);
429
+                // skip this entry
430
+                continue;
431
+            }
432
+
433
+            $newChildNames[] = $file;
434
+            $child = $path ? $path . '/' . $file : $file;
435
+            try {
436
+                $existingData = isset($existingChildren[$file]) ? $existingChildren[$file] : false;
437
+                $data = $this->scanFile($child, $reuse, $folderId, $existingData, $lock, $fileMeta);
438
+                if ($data) {
439
+                    if ($data['mimetype'] === 'httpd/unix-directory' and $recursive === self::SCAN_RECURSIVE) {
440
+                        $childQueue[$child] = $data['fileid'];
441
+                    } elseif ($data['mimetype'] === 'httpd/unix-directory' and $recursive === self::SCAN_RECURSIVE_INCOMPLETE and $data['size'] === -1) {
442
+                        // only recurse into folders which aren't fully scanned
443
+                        $childQueue[$child] = $data['fileid'];
444
+                    } elseif ($data['size'] === -1) {
445
+                        $size = -1;
446
+                    } elseif ($size !== -1) {
447
+                        $size += $data['size'];
448
+                    }
449
+                }
450
+            } catch (Exception $ex) {
451
+                // might happen if inserting duplicate while a scanning
452
+                // process is running in parallel
453
+                // log and ignore
454
+                if ($this->useTransactions) {
455
+                    \OC::$server->getDatabaseConnection()->rollback();
456
+                    \OC::$server->getDatabaseConnection()->beginTransaction();
457
+                }
458
+                \OC::$server->getLogger()->logException($ex, [
459
+                    'message' => 'Exception while scanning file "' . $child . '"',
460
+                    'level' => ILogger::DEBUG,
461
+                    'app' => 'core',
462
+                ]);
463
+                $exceptionOccurred = true;
464
+            } catch (\OCP\Lock\LockedException $e) {
465
+                if ($this->useTransactions) {
466
+                    \OC::$server->getDatabaseConnection()->rollback();
467
+                }
468
+                throw $e;
469
+            }
470
+        }
471
+        $removedChildren = \array_diff(array_keys($existingChildren), $newChildNames);
472
+        foreach ($removedChildren as $childName) {
473
+            $child = $path ? $path . '/' . $childName : $childName;
474
+            $this->removeFromCache($child);
475
+        }
476
+        if ($this->useTransactions) {
477
+            \OC::$server->getDatabaseConnection()->commit();
478
+        }
479
+        if ($exceptionOccurred) {
480
+            // It might happen that the parallel scan process has already
481
+            // inserted mimetypes but those weren't available yet inside the transaction
482
+            // To make sure to have the updated mime types in such cases,
483
+            // we reload them here
484
+            \OC::$server->getMimeTypeLoader()->reset();
485
+        }
486
+        return $childQueue;
487
+    }
488
+
489
+    /**
490
+     * check if the file should be ignored when scanning
491
+     * NOTE: files with a '.part' extension are ignored as well!
492
+     *       prevents unfinished put requests to be scanned
493
+     *
494
+     * @param string $file
495
+     * @return boolean
496
+     */
497
+    public static function isPartialFile($file) {
498
+        if (pathinfo($file, PATHINFO_EXTENSION) === 'part') {
499
+            return true;
500
+        }
501
+        if (strpos($file, '.part/') !== false) {
502
+            return true;
503
+        }
504
+
505
+        return false;
506
+    }
507
+
508
+    /**
509
+     * walk over any folders that are not fully scanned yet and scan them
510
+     */
511
+    public function backgroundScan() {
512
+        if ($this->storage->instanceOfStorage(Jail::class)) {
513
+            // for jail storage wrappers (shares, groupfolders) we run the background scan on the source storage
514
+            // this is mainly done because the jail wrapper doesn't implement `getIncomplete` (because it would be inefficient).
515
+            //
516
+            // Running the scan on the source storage might scan more than "needed", but the unscanned files outside the jail will
517
+            // have to be scanned at some point anyway.
518
+            $unJailedScanner = $this->storage->getUnjailedStorage()->getScanner();
519
+            $unJailedScanner->backgroundScan();
520
+        } else {
521
+            if (!$this->cache->inCache('')) {
522
+                // if the storage isn't in the cache yet, just scan the root completely
523
+                $this->runBackgroundScanJob(function () {
524
+                    $this->scan('', self::SCAN_RECURSIVE, self::REUSE_ETAG);
525
+                }, '');
526
+            } else {
527
+                $lastPath = null;
528
+                // find any path marked as unscanned and run the scanner until no more paths are unscanned (or we get stuck)
529
+                while (($path = $this->cache->getIncomplete()) !== false && $path !== $lastPath) {
530
+                    $this->runBackgroundScanJob(function () use ($path) {
531
+                        $this->scan($path, self::SCAN_RECURSIVE_INCOMPLETE, self::REUSE_ETAG | self::REUSE_SIZE);
532
+                    }, $path);
533
+                    // FIXME: this won't proceed with the next item, needs revamping of getIncomplete()
534
+                    // to make this possible
535
+                    $lastPath = $path;
536
+                }
537
+            }
538
+        }
539
+    }
540
+
541
+    private function runBackgroundScanJob(callable $callback, $path) {
542
+        try {
543
+            $callback();
544
+            \OC_Hook::emit('Scanner', 'correctFolderSize', ['path' => $path]);
545
+            if ($this->cacheActive && $this->cache instanceof Cache) {
546
+                $this->cache->correctFolderSize($path, null, true);
547
+            }
548
+        } catch (\OCP\Files\StorageInvalidException $e) {
549
+            // skip unavailable storages
550
+        } catch (\OCP\Files\StorageNotAvailableException $e) {
551
+            // skip unavailable storages
552
+        } catch (\OCP\Files\ForbiddenException $e) {
553
+            // skip forbidden storages
554
+        } catch (\OCP\Lock\LockedException $e) {
555
+            // skip unavailable storages
556
+        }
557
+    }
558
+
559
+    /**
560
+     * Set whether the cache is affected by scan operations
561
+     *
562
+     * @param boolean $active The active state of the cache
563
+     */
564
+    public function setCacheActive($active) {
565
+        $this->cacheActive = $active;
566
+    }
567 567
 }
Please login to merge, or discard this patch.