Passed
Push — master ( 4aae71...b1ad3f )
by Morris
16:35 queued 12s
created
apps/files_trashbin/lib/Hooks.php 1 patch
Indentation   +17 added lines, -17 removed lines patch added patch discarded remove patch
@@ -31,22 +31,22 @@
 block discarded – undo
31 31
 
32 32
 class Hooks {
33 33
 
34
-	/**
35
-	 * clean up user specific settings if user gets deleted
36
-	 * @param array $params array with uid
37
-	 *
38
-	 * This function is connected to the pre_deleteUser signal of OC_Users
39
-	 * to remove the used space for the trash bin stored in the database
40
-	 */
41
-	public static function deleteUser_hook($params) {
42
-		$uid = $params['uid'];
43
-		Trashbin::deleteUser($uid);
44
-	}
34
+    /**
35
+     * clean up user specific settings if user gets deleted
36
+     * @param array $params array with uid
37
+     *
38
+     * This function is connected to the pre_deleteUser signal of OC_Users
39
+     * to remove the used space for the trash bin stored in the database
40
+     */
41
+    public static function deleteUser_hook($params) {
42
+        $uid = $params['uid'];
43
+        Trashbin::deleteUser($uid);
44
+    }
45 45
 
46
-	public static function post_write_hook($params) {
47
-		$user = \OC_User::getUser();
48
-		if (!empty($user)) {
49
-			Trashbin::resizeTrash($user);
50
-		}
51
-	}
46
+    public static function post_write_hook($params) {
47
+        $user = \OC_User::getUser();
48
+        if (!empty($user)) {
49
+            Trashbin::resizeTrash($user);
50
+        }
51
+    }
52 52
 }
Please login to merge, or discard this patch.
apps/files_trashbin/lib/Trashbin.php 2 patches
Indentation   +1063 added lines, -1063 removed lines patch added patch discarded remove patch
@@ -64,1067 +64,1067 @@
 block discarded – undo
64 64
 
65 65
 class Trashbin {
66 66
 
67
-	// unit: percentage; 50% of available disk space/quota
68
-	public const DEFAULTMAXSIZE = 50;
69
-
70
-	/**
71
-	 * Whether versions have already be rescanned during this PHP request
72
-	 *
73
-	 * @var bool
74
-	 */
75
-	private static $scannedVersions = false;
76
-
77
-	/**
78
-	 * Ensure we don't need to scan the file during the move to trash
79
-	 * by triggering the scan in the pre-hook
80
-	 *
81
-	 * @param array $params
82
-	 */
83
-	public static function ensureFileScannedHook($params) {
84
-		try {
85
-			self::getUidAndFilename($params['path']);
86
-		} catch (NotFoundException $e) {
87
-			// nothing to scan for non existing files
88
-		}
89
-	}
90
-
91
-	/**
92
-	 * get the UID of the owner of the file and the path to the file relative to
93
-	 * owners files folder
94
-	 *
95
-	 * @param string $filename
96
-	 * @return array
97
-	 * @throws \OC\User\NoUserException
98
-	 */
99
-	public static function getUidAndFilename($filename) {
100
-		$uid = Filesystem::getOwner($filename);
101
-		$userManager = \OC::$server->getUserManager();
102
-		// if the user with the UID doesn't exists, e.g. because the UID points
103
-		// to a remote user with a federated cloud ID we use the current logged-in
104
-		// user. We need a valid local user to move the file to the right trash bin
105
-		if (!$userManager->userExists($uid)) {
106
-			$uid = OC_User::getUser();
107
-		}
108
-		if (!$uid) {
109
-			// no owner, usually because of share link from ext storage
110
-			return [null, null];
111
-		}
112
-		Filesystem::initMountPoints($uid);
113
-		if ($uid !== OC_User::getUser()) {
114
-			$info = Filesystem::getFileInfo($filename);
115
-			$ownerView = new View('/' . $uid . '/files');
116
-			try {
117
-				$filename = $ownerView->getPath($info['fileid']);
118
-			} catch (NotFoundException $e) {
119
-				$filename = null;
120
-			}
121
-		}
122
-		return [$uid, $filename];
123
-	}
124
-
125
-	/**
126
-	 * get original location of files for user
127
-	 *
128
-	 * @param string $user
129
-	 * @return array (filename => array (timestamp => original location))
130
-	 */
131
-	public static function getLocations($user) {
132
-		$query = \OC::$server->getDatabaseConnection()->getQueryBuilder();
133
-		$query->select('id', 'timestamp', 'location')
134
-			->from('files_trash')
135
-			->where($query->expr()->eq('user', $query->createNamedParameter($user)));
136
-		$result = $query->executeQuery();
137
-		$array = [];
138
-		while ($row = $result->fetch()) {
139
-			if (isset($array[$row['id']])) {
140
-				$array[$row['id']][$row['timestamp']] = $row['location'];
141
-			} else {
142
-				$array[$row['id']] = [$row['timestamp'] => $row['location']];
143
-			}
144
-		}
145
-		$result->closeCursor();
146
-		return $array;
147
-	}
148
-
149
-	/**
150
-	 * get original location of file
151
-	 *
152
-	 * @param string $user
153
-	 * @param string $filename
154
-	 * @param string $timestamp
155
-	 * @return string original location
156
-	 */
157
-	public static function getLocation($user, $filename, $timestamp) {
158
-		$query = \OC::$server->getDatabaseConnection()->getQueryBuilder();
159
-		$query->select('location')
160
-			->from('files_trash')
161
-			->where($query->expr()->eq('user', $query->createNamedParameter($user)))
162
-			->andWhere($query->expr()->eq('id', $query->createNamedParameter($filename)))
163
-			->andWhere($query->expr()->eq('timestamp', $query->createNamedParameter($timestamp)));
164
-
165
-		$result = $query->executeQuery();
166
-		$row = $result->fetch();
167
-		$result->closeCursor();
168
-
169
-		if (isset($row['location'])) {
170
-			return $row['location'];
171
-		} else {
172
-			return false;
173
-		}
174
-	}
175
-
176
-	private static function setUpTrash($user) {
177
-		$view = new View('/' . $user);
178
-		if (!$view->is_dir('files_trashbin')) {
179
-			$view->mkdir('files_trashbin');
180
-		}
181
-		if (!$view->is_dir('files_trashbin/files')) {
182
-			$view->mkdir('files_trashbin/files');
183
-		}
184
-		if (!$view->is_dir('files_trashbin/versions')) {
185
-			$view->mkdir('files_trashbin/versions');
186
-		}
187
-		if (!$view->is_dir('files_trashbin/keys')) {
188
-			$view->mkdir('files_trashbin/keys');
189
-		}
190
-	}
191
-
192
-
193
-	/**
194
-	 * copy file to owners trash
195
-	 *
196
-	 * @param string $sourcePath
197
-	 * @param string $owner
198
-	 * @param string $targetPath
199
-	 * @param $user
200
-	 * @param integer $timestamp
201
-	 */
202
-	private static function copyFilesToUser($sourcePath, $owner, $targetPath, $user, $timestamp) {
203
-		self::setUpTrash($owner);
204
-
205
-		$targetFilename = basename($targetPath);
206
-		$targetLocation = dirname($targetPath);
207
-
208
-		$sourceFilename = basename($sourcePath);
209
-
210
-		$view = new View('/');
211
-
212
-		$target = $user . '/files_trashbin/files/' . $targetFilename . '.d' . $timestamp;
213
-		$source = $owner . '/files_trashbin/files/' . $sourceFilename . '.d' . $timestamp;
214
-		$free = $view->free_space($target);
215
-		$isUnknownOrUnlimitedFreeSpace = $free < 0;
216
-		$isEnoughFreeSpaceLeft = $view->filesize($source) < $free;
217
-		if ($isUnknownOrUnlimitedFreeSpace || $isEnoughFreeSpaceLeft) {
218
-			self::copy_recursive($source, $target, $view);
219
-		}
220
-
221
-
222
-		if ($view->file_exists($target)) {
223
-			$query = \OC::$server->getDatabaseConnection()->getQueryBuilder();
224
-			$query->insert('files_trash')
225
-				->setValue('id', $query->createNamedParameter($targetFilename))
226
-				->setValue('timestamp', $query->createNamedParameter($timestamp))
227
-				->setValue('location', $query->createNamedParameter($targetLocation))
228
-				->setValue('user', $query->createNamedParameter($user));
229
-			$result = $query->executeStatement();
230
-			if (!$result) {
231
-				\OC::$server->getLogger()->error('trash bin database couldn\'t be updated for the files owner', ['app' => 'files_trashbin']);
232
-			}
233
-		}
234
-	}
235
-
236
-
237
-	/**
238
-	 * move file to the trash bin
239
-	 *
240
-	 * @param string $file_path path to the deleted file/directory relative to the files root directory
241
-	 * @param bool $ownerOnly delete for owner only (if file gets moved out of a shared folder)
242
-	 *
243
-	 * @return bool
244
-	 */
245
-	public static function move2trash($file_path, $ownerOnly = false) {
246
-		// get the user for which the filesystem is setup
247
-		$root = Filesystem::getRoot();
248
-		[, $user] = explode('/', $root);
249
-		[$owner, $ownerPath] = self::getUidAndFilename($file_path);
250
-
251
-		// if no owner found (ex: ext storage + share link), will use the current user's trashbin then
252
-		if (is_null($owner)) {
253
-			$owner = $user;
254
-			$ownerPath = $file_path;
255
-		}
256
-
257
-		$ownerView = new View('/' . $owner);
258
-		// file has been deleted in between
259
-		if (is_null($ownerPath) || $ownerPath === '' || !$ownerView->file_exists('/files/' . $ownerPath)) {
260
-			return true;
261
-		}
262
-
263
-		self::setUpTrash($user);
264
-		if ($owner !== $user) {
265
-			// also setup for owner
266
-			self::setUpTrash($owner);
267
-		}
268
-
269
-		$path_parts = pathinfo($ownerPath);
270
-
271
-		$filename = $path_parts['basename'];
272
-		$location = $path_parts['dirname'];
273
-		/** @var ITimeFactory $timeFactory */
274
-		$timeFactory = \OC::$server->query(ITimeFactory::class);
275
-		$timestamp = $timeFactory->getTime();
276
-
277
-		$lockingProvider = \OC::$server->getLockingProvider();
278
-
279
-		// disable proxy to prevent recursive calls
280
-		$trashPath = '/files_trashbin/files/' . $filename . '.d' . $timestamp;
281
-		$gotLock = false;
282
-
283
-		while (!$gotLock) {
284
-			try {
285
-				/** @var \OC\Files\Storage\Storage $trashStorage */
286
-				[$trashStorage, $trashInternalPath] = $ownerView->resolvePath($trashPath);
287
-
288
-				$trashStorage->acquireLock($trashInternalPath, ILockingProvider::LOCK_EXCLUSIVE, $lockingProvider);
289
-				$gotLock = true;
290
-			} catch (LockedException $e) {
291
-				// a file with the same name is being deleted concurrently
292
-				// nudge the timestamp a bit to resolve the conflict
293
-
294
-				$timestamp = $timestamp + 1;
295
-
296
-				$trashPath = '/files_trashbin/files/' . $filename . '.d' . $timestamp;
297
-			}
298
-		}
299
-
300
-		/** @var \OC\Files\Storage\Storage $sourceStorage */
301
-		[$sourceStorage, $sourceInternalPath] = $ownerView->resolvePath('/files/' . $ownerPath);
302
-
303
-
304
-		if ($trashStorage->file_exists($trashInternalPath)) {
305
-			$trashStorage->unlink($trashInternalPath);
306
-		}
307
-
308
-		$config = \OC::$server->getConfig();
309
-		$systemTrashbinSize = (int)$config->getAppValue('files_trashbin', 'trashbin_size', '-1');
310
-		$userTrashbinSize = (int)$config->getUserValue($owner, 'files_trashbin', 'trashbin_size', '-1');
311
-		$configuredTrashbinSize = ($userTrashbinSize < 0) ? $systemTrashbinSize : $userTrashbinSize;
312
-		if ($configuredTrashbinSize >= 0 && $sourceStorage->filesize($sourceInternalPath) >= $configuredTrashbinSize) {
313
-			return false;
314
-		}
315
-
316
-		$trashStorage->getUpdater()->renameFromStorage($sourceStorage, $sourceInternalPath, $trashInternalPath);
317
-
318
-		try {
319
-			$moveSuccessful = true;
320
-
321
-			// when moving within the same object store, the cache update done above is enough to move the file
322
-			if (!($trashStorage->instanceOfStorage(ObjectStoreStorage::class) && $trashStorage->getId() === $sourceStorage->getId())) {
323
-				$trashStorage->moveFromStorage($sourceStorage, $sourceInternalPath, $trashInternalPath);
324
-			}
325
-		} catch (\OCA\Files_Trashbin\Exceptions\CopyRecursiveException $e) {
326
-			$moveSuccessful = false;
327
-			if ($trashStorage->file_exists($trashInternalPath)) {
328
-				$trashStorage->unlink($trashInternalPath);
329
-			}
330
-			\OC::$server->getLogger()->error('Couldn\'t move ' . $file_path . ' to the trash bin', ['app' => 'files_trashbin']);
331
-		}
332
-
333
-		if ($sourceStorage->file_exists($sourceInternalPath)) { // failed to delete the original file, abort
334
-			if ($sourceStorage->is_dir($sourceInternalPath)) {
335
-				$sourceStorage->rmdir($sourceInternalPath);
336
-			} else {
337
-				$sourceStorage->unlink($sourceInternalPath);
338
-			}
339
-
340
-			if ($sourceStorage->file_exists($sourceInternalPath)) {
341
-				// undo the cache move
342
-				$sourceStorage->getUpdater()->renameFromStorage($trashStorage, $trashInternalPath, $sourceInternalPath);
343
-			} else {
344
-				$trashStorage->getUpdater()->remove($trashInternalPath);
345
-			}
346
-			return false;
347
-		}
348
-
349
-		if ($moveSuccessful) {
350
-			$query = \OC::$server->getDatabaseConnection()->getQueryBuilder();
351
-			$query->insert('files_trash')
352
-				->setValue('id', $query->createNamedParameter($filename))
353
-				->setValue('timestamp', $query->createNamedParameter($timestamp))
354
-				->setValue('location', $query->createNamedParameter($location))
355
-				->setValue('user', $query->createNamedParameter($owner));
356
-			$result = $query->executeStatement();
357
-			if (!$result) {
358
-				\OC::$server->getLogger()->error('trash bin database couldn\'t be updated', ['app' => 'files_trashbin']);
359
-			}
360
-			\OCP\Util::emitHook('\OCA\Files_Trashbin\Trashbin', 'post_moveToTrash', ['filePath' => Filesystem::normalizePath($file_path),
361
-				'trashPath' => Filesystem::normalizePath($filename . '.d' . $timestamp)]);
362
-
363
-			self::retainVersions($filename, $owner, $ownerPath, $timestamp);
364
-
365
-			// if owner !== user we need to also add a copy to the users trash
366
-			if ($user !== $owner && $ownerOnly === false) {
367
-				self::copyFilesToUser($ownerPath, $owner, $file_path, $user, $timestamp);
368
-			}
369
-		}
370
-
371
-		$trashStorage->releaseLock($trashInternalPath, ILockingProvider::LOCK_EXCLUSIVE, $lockingProvider);
372
-
373
-		self::scheduleExpire($user);
374
-
375
-		// if owner !== user we also need to update the owners trash size
376
-		if ($owner !== $user) {
377
-			self::scheduleExpire($owner);
378
-		}
379
-
380
-		return $moveSuccessful;
381
-	}
382
-
383
-	/**
384
-	 * Move file versions to trash so that they can be restored later
385
-	 *
386
-	 * @param string $filename of deleted file
387
-	 * @param string $owner owner user id
388
-	 * @param string $ownerPath path relative to the owner's home storage
389
-	 * @param integer $timestamp when the file was deleted
390
-	 */
391
-	private static function retainVersions($filename, $owner, $ownerPath, $timestamp) {
392
-		if (\OCP\App::isEnabled('files_versions') && !empty($ownerPath)) {
393
-			$user = OC_User::getUser();
394
-			$rootView = new View('/');
395
-
396
-			if ($rootView->is_dir($owner . '/files_versions/' . $ownerPath)) {
397
-				if ($owner !== $user) {
398
-					self::copy_recursive($owner . '/files_versions/' . $ownerPath, $owner . '/files_trashbin/versions/' . basename($ownerPath) . '.d' . $timestamp, $rootView);
399
-				}
400
-				self::move($rootView, $owner . '/files_versions/' . $ownerPath, $user . '/files_trashbin/versions/' . $filename . '.d' . $timestamp);
401
-			} elseif ($versions = \OCA\Files_Versions\Storage::getVersions($owner, $ownerPath)) {
402
-				foreach ($versions as $v) {
403
-					if ($owner !== $user) {
404
-						self::copy($rootView, $owner . '/files_versions' . $v['path'] . '.v' . $v['version'], $owner . '/files_trashbin/versions/' . $v['name'] . '.v' . $v['version'] . '.d' . $timestamp);
405
-					}
406
-					self::move($rootView, $owner . '/files_versions' . $v['path'] . '.v' . $v['version'], $user . '/files_trashbin/versions/' . $filename . '.v' . $v['version'] . '.d' . $timestamp);
407
-				}
408
-			}
409
-		}
410
-	}
411
-
412
-	/**
413
-	 * Move a file or folder on storage level
414
-	 *
415
-	 * @param View $view
416
-	 * @param string $source
417
-	 * @param string $target
418
-	 * @return bool
419
-	 */
420
-	private static function move(View $view, $source, $target) {
421
-		/** @var \OC\Files\Storage\Storage $sourceStorage */
422
-		[$sourceStorage, $sourceInternalPath] = $view->resolvePath($source);
423
-		/** @var \OC\Files\Storage\Storage $targetStorage */
424
-		[$targetStorage, $targetInternalPath] = $view->resolvePath($target);
425
-		/** @var \OC\Files\Storage\Storage $ownerTrashStorage */
426
-
427
-		$result = $targetStorage->moveFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
428
-		if ($result) {
429
-			$targetStorage->getUpdater()->renameFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
430
-		}
431
-		return $result;
432
-	}
433
-
434
-	/**
435
-	 * Copy a file or folder on storage level
436
-	 *
437
-	 * @param View $view
438
-	 * @param string $source
439
-	 * @param string $target
440
-	 * @return bool
441
-	 */
442
-	private static function copy(View $view, $source, $target) {
443
-		/** @var \OC\Files\Storage\Storage $sourceStorage */
444
-		[$sourceStorage, $sourceInternalPath] = $view->resolvePath($source);
445
-		/** @var \OC\Files\Storage\Storage $targetStorage */
446
-		[$targetStorage, $targetInternalPath] = $view->resolvePath($target);
447
-		/** @var \OC\Files\Storage\Storage $ownerTrashStorage */
448
-
449
-		$result = $targetStorage->copyFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
450
-		if ($result) {
451
-			$targetStorage->getUpdater()->update($targetInternalPath);
452
-		}
453
-		return $result;
454
-	}
455
-
456
-	/**
457
-	 * Restore a file or folder from trash bin
458
-	 *
459
-	 * @param string $file path to the deleted file/folder relative to "files_trashbin/files/",
460
-	 * including the timestamp suffix ".d12345678"
461
-	 * @param string $filename name of the file/folder
462
-	 * @param int $timestamp time when the file/folder was deleted
463
-	 *
464
-	 * @return bool true on success, false otherwise
465
-	 */
466
-	public static function restore($file, $filename, $timestamp) {
467
-		$user = OC_User::getUser();
468
-		$view = new View('/' . $user);
469
-
470
-		$location = '';
471
-		if ($timestamp) {
472
-			$location = self::getLocation($user, $filename, $timestamp);
473
-			if ($location === false) {
474
-				\OC::$server->getLogger()->error('trash bin database inconsistent! ($user: ' . $user . ' $filename: ' . $filename . ', $timestamp: ' . $timestamp . ')', ['app' => 'files_trashbin']);
475
-			} else {
476
-				// if location no longer exists, restore file in the root directory
477
-				if ($location !== '/' &&
478
-					(!$view->is_dir('files/' . $location) ||
479
-						!$view->isCreatable('files/' . $location))
480
-				) {
481
-					$location = '';
482
-				}
483
-			}
484
-		}
485
-
486
-		// we need a  extension in case a file/dir with the same name already exists
487
-		$uniqueFilename = self::getUniqueFilename($location, $filename, $view);
488
-
489
-		$source = Filesystem::normalizePath('files_trashbin/files/' . $file);
490
-		$target = Filesystem::normalizePath('files/' . $location . '/' . $uniqueFilename);
491
-		if (!$view->file_exists($source)) {
492
-			return false;
493
-		}
494
-		$mtime = $view->filemtime($source);
495
-
496
-		// restore file
497
-		if (!$view->isCreatable(dirname($target))) {
498
-			throw new NotPermittedException("Can't restore trash item because the target folder is not writable");
499
-		}
500
-		$restoreResult = $view->rename($source, $target);
501
-
502
-		// handle the restore result
503
-		if ($restoreResult) {
504
-			$fakeRoot = $view->getRoot();
505
-			$view->chroot('/' . $user . '/files');
506
-			$view->touch('/' . $location . '/' . $uniqueFilename, $mtime);
507
-			$view->chroot($fakeRoot);
508
-			\OCP\Util::emitHook('\OCA\Files_Trashbin\Trashbin', 'post_restore', ['filePath' => Filesystem::normalizePath('/' . $location . '/' . $uniqueFilename),
509
-				'trashPath' => Filesystem::normalizePath($file)]);
510
-
511
-			self::restoreVersions($view, $file, $filename, $uniqueFilename, $location, $timestamp);
512
-
513
-			if ($timestamp) {
514
-				$query = \OC::$server->getDatabaseConnection()->getQueryBuilder();
515
-				$query->delete('files_trash')
516
-					->where($query->expr()->eq('user', $query->createNamedParameter($user)))
517
-					->andWhere($query->expr()->eq('id', $query->createNamedParameter($filename)))
518
-					->andWhere($query->expr()->eq('timestamp', $query->createNamedParameter($timestamp)));
519
-				$query->executeStatement();
520
-			}
521
-
522
-			return true;
523
-		}
524
-
525
-		return false;
526
-	}
527
-
528
-	/**
529
-	 * restore versions from trash bin
530
-	 *
531
-	 * @param View $view file view
532
-	 * @param string $file complete path to file
533
-	 * @param string $filename name of file once it was deleted
534
-	 * @param string $uniqueFilename new file name to restore the file without overwriting existing files
535
-	 * @param string $location location if file
536
-	 * @param int $timestamp deletion time
537
-	 * @return false|null
538
-	 */
539
-	private static function restoreVersions(View $view, $file, $filename, $uniqueFilename, $location, $timestamp) {
540
-		if (\OCP\App::isEnabled('files_versions')) {
541
-			$user = OC_User::getUser();
542
-			$rootView = new View('/');
543
-
544
-			$target = Filesystem::normalizePath('/' . $location . '/' . $uniqueFilename);
545
-
546
-			[$owner, $ownerPath] = self::getUidAndFilename($target);
547
-
548
-			// file has been deleted in between
549
-			if (empty($ownerPath)) {
550
-				return false;
551
-			}
552
-
553
-			if ($timestamp) {
554
-				$versionedFile = $filename;
555
-			} else {
556
-				$versionedFile = $file;
557
-			}
558
-
559
-			if ($view->is_dir('/files_trashbin/versions/' . $file)) {
560
-				$rootView->rename(Filesystem::normalizePath($user . '/files_trashbin/versions/' . $file), Filesystem::normalizePath($owner . '/files_versions/' . $ownerPath));
561
-			} elseif ($versions = self::getVersionsFromTrash($versionedFile, $timestamp, $user)) {
562
-				foreach ($versions as $v) {
563
-					if ($timestamp) {
564
-						$rootView->rename($user . '/files_trashbin/versions/' . $versionedFile . '.v' . $v . '.d' . $timestamp, $owner . '/files_versions/' . $ownerPath . '.v' . $v);
565
-					} else {
566
-						$rootView->rename($user . '/files_trashbin/versions/' . $versionedFile . '.v' . $v, $owner . '/files_versions/' . $ownerPath . '.v' . $v);
567
-					}
568
-				}
569
-			}
570
-		}
571
-	}
572
-
573
-	/**
574
-	 * delete all files from the trash
575
-	 */
576
-	public static function deleteAll() {
577
-		$user = OC_User::getUser();
578
-		$userRoot = \OC::$server->getUserFolder($user)->getParent();
579
-		$view = new View('/' . $user);
580
-		$fileInfos = $view->getDirectoryContent('files_trashbin/files');
581
-
582
-		try {
583
-			$trash = $userRoot->get('files_trashbin');
584
-		} catch (NotFoundException $e) {
585
-			return false;
586
-		}
587
-
588
-		// Array to store the relative path in (after the file is deleted, the view won't be able to relativise the path anymore)
589
-		$filePaths = [];
590
-		foreach ($fileInfos as $fileInfo) {
591
-			$filePaths[] = $view->getRelativePath($fileInfo->getPath());
592
-		}
593
-		unset($fileInfos); // save memory
594
-
595
-		// Bulk PreDelete-Hook
596
-		\OC_Hook::emit('\OCP\Trashbin', 'preDeleteAll', ['paths' => $filePaths]);
597
-
598
-		// Single-File Hooks
599
-		foreach ($filePaths as $path) {
600
-			self::emitTrashbinPreDelete($path);
601
-		}
602
-
603
-		// actual file deletion
604
-		$trash->delete();
605
-
606
-		$query = \OC::$server->getDatabaseConnection()->getQueryBuilder();
607
-		$query->delete('files_trash')
608
-			->where($query->expr()->eq('user', $query->createNamedParameter($user)));
609
-		$query->executeStatement();
610
-
611
-		// Bulk PostDelete-Hook
612
-		\OC_Hook::emit('\OCP\Trashbin', 'deleteAll', ['paths' => $filePaths]);
613
-
614
-		// Single-File Hooks
615
-		foreach ($filePaths as $path) {
616
-			self::emitTrashbinPostDelete($path);
617
-		}
618
-
619
-		$trash = $userRoot->newFolder('files_trashbin');
620
-		$trash->newFolder('files');
621
-
622
-		return true;
623
-	}
624
-
625
-	/**
626
-	 * wrapper function to emit the 'preDelete' hook of \OCP\Trashbin before a file is deleted
627
-	 *
628
-	 * @param string $path
629
-	 */
630
-	protected static function emitTrashbinPreDelete($path) {
631
-		\OC_Hook::emit('\OCP\Trashbin', 'preDelete', ['path' => $path]);
632
-	}
633
-
634
-	/**
635
-	 * wrapper function to emit the 'delete' hook of \OCP\Trashbin after a file has been deleted
636
-	 *
637
-	 * @param string $path
638
-	 */
639
-	protected static function emitTrashbinPostDelete($path) {
640
-		\OC_Hook::emit('\OCP\Trashbin', 'delete', ['path' => $path]);
641
-	}
642
-
643
-	/**
644
-	 * delete file from trash bin permanently
645
-	 *
646
-	 * @param string $filename path to the file
647
-	 * @param string $user
648
-	 * @param int $timestamp of deletion time
649
-	 *
650
-	 * @return int size of deleted files
651
-	 */
652
-	public static function delete($filename, $user, $timestamp = null) {
653
-		$userRoot = \OC::$server->getUserFolder($user)->getParent();
654
-		$view = new View('/' . $user);
655
-		$size = 0;
656
-
657
-		if ($timestamp) {
658
-			$query = \OC::$server->getDatabaseConnection()->getQueryBuilder();
659
-			$query->delete('files_trash')
660
-				->where($query->expr()->eq('user', $query->createNamedParameter($user)))
661
-				->andWhere($query->expr()->eq('id', $query->createNamedParameter($filename)))
662
-				->andWhere($query->expr()->eq('timestamp', $query->createNamedParameter($timestamp)));
663
-			$query->executeStatement();
664
-
665
-			$file = $filename . '.d' . $timestamp;
666
-		} else {
667
-			$file = $filename;
668
-		}
669
-
670
-		$size += self::deleteVersions($view, $file, $filename, $timestamp, $user);
671
-
672
-		try {
673
-			$node = $userRoot->get('/files_trashbin/files/' . $file);
674
-		} catch (NotFoundException $e) {
675
-			return $size;
676
-		}
677
-
678
-		if ($node instanceof Folder) {
679
-			$size += self::calculateSize(new View('/' . $user . '/files_trashbin/files/' . $file));
680
-		} elseif ($node instanceof File) {
681
-			$size += $view->filesize('/files_trashbin/files/' . $file);
682
-		}
683
-
684
-		self::emitTrashbinPreDelete('/files_trashbin/files/' . $file);
685
-		$node->delete();
686
-		self::emitTrashbinPostDelete('/files_trashbin/files/' . $file);
687
-
688
-		return $size;
689
-	}
690
-
691
-	/**
692
-	 * @param View $view
693
-	 * @param string $file
694
-	 * @param string $filename
695
-	 * @param integer|null $timestamp
696
-	 * @param string $user
697
-	 * @return int
698
-	 */
699
-	private static function deleteVersions(View $view, $file, $filename, $timestamp, $user) {
700
-		$size = 0;
701
-		if (\OCP\App::isEnabled('files_versions')) {
702
-			if ($view->is_dir('files_trashbin/versions/' . $file)) {
703
-				$size += self::calculateSize(new View('/' . $user . '/files_trashbin/versions/' . $file));
704
-				$view->unlink('files_trashbin/versions/' . $file);
705
-			} elseif ($versions = self::getVersionsFromTrash($filename, $timestamp, $user)) {
706
-				foreach ($versions as $v) {
707
-					if ($timestamp) {
708
-						$size += $view->filesize('/files_trashbin/versions/' . $filename . '.v' . $v . '.d' . $timestamp);
709
-						$view->unlink('/files_trashbin/versions/' . $filename . '.v' . $v . '.d' . $timestamp);
710
-					} else {
711
-						$size += $view->filesize('/files_trashbin/versions/' . $filename . '.v' . $v);
712
-						$view->unlink('/files_trashbin/versions/' . $filename . '.v' . $v);
713
-					}
714
-				}
715
-			}
716
-		}
717
-		return $size;
718
-	}
719
-
720
-	/**
721
-	 * check to see whether a file exists in trashbin
722
-	 *
723
-	 * @param string $filename path to the file
724
-	 * @param int $timestamp of deletion time
725
-	 * @return bool true if file exists, otherwise false
726
-	 */
727
-	public static function file_exists($filename, $timestamp = null) {
728
-		$user = OC_User::getUser();
729
-		$view = new View('/' . $user);
730
-
731
-		if ($timestamp) {
732
-			$filename = $filename . '.d' . $timestamp;
733
-		}
734
-
735
-		$target = Filesystem::normalizePath('files_trashbin/files/' . $filename);
736
-		return $view->file_exists($target);
737
-	}
738
-
739
-	/**
740
-	 * deletes used space for trash bin in db if user was deleted
741
-	 *
742
-	 * @param string $uid id of deleted user
743
-	 * @return bool result of db delete operation
744
-	 */
745
-	public static function deleteUser($uid) {
746
-		$query = \OC::$server->getDatabaseConnection()->getQueryBuilder();
747
-		$query->delete('files_trash')
748
-			->where($query->expr()->eq('user', $query->createNamedParameter($uid)));
749
-		return (bool) $query->executeStatement();
750
-	}
751
-
752
-	/**
753
-	 * calculate remaining free space for trash bin
754
-	 *
755
-	 * @param integer $trashbinSize current size of the trash bin
756
-	 * @param string $user
757
-	 * @return int available free space for trash bin
758
-	 */
759
-	private static function calculateFreeSpace($trashbinSize, $user) {
760
-		$config = \OC::$server->getConfig();
761
-		$userTrashbinSize = (int)$config->getUserValue($user, 'files_trashbin', 'trashbin_size', '-1');
762
-		if ($userTrashbinSize > -1) {
763
-			return $userTrashbinSize - $trashbinSize;
764
-		}
765
-		$systemTrashbinSize = (int)$config->getAppValue('files_trashbin', 'trashbin_size', '-1');
766
-		if ($systemTrashbinSize > -1) {
767
-			return $systemTrashbinSize - $trashbinSize;
768
-		}
769
-
770
-		$softQuota = true;
771
-		$userObject = \OC::$server->getUserManager()->get($user);
772
-		if (is_null($userObject)) {
773
-			return 0;
774
-		}
775
-		$quota = $userObject->getQuota();
776
-		if ($quota === null || $quota === 'none') {
777
-			$quota = Filesystem::free_space('/');
778
-			$softQuota = false;
779
-			// inf or unknown free space
780
-			if ($quota < 0) {
781
-				$quota = PHP_INT_MAX;
782
-			}
783
-		} else {
784
-			$quota = \OCP\Util::computerFileSize($quota);
785
-		}
786
-
787
-		// calculate available space for trash bin
788
-		// subtract size of files and current trash bin size from quota
789
-		if ($softQuota) {
790
-			$userFolder = \OC::$server->getUserFolder($user);
791
-			if (is_null($userFolder)) {
792
-				return 0;
793
-			}
794
-			$free = $quota - $userFolder->getSize(false); // remaining free space for user
795
-			if ($free > 0) {
796
-				$availableSpace = ($free * self::DEFAULTMAXSIZE / 100) - $trashbinSize; // how much space can be used for versions
797
-			} else {
798
-				$availableSpace = $free - $trashbinSize;
799
-			}
800
-		} else {
801
-			$availableSpace = $quota;
802
-		}
803
-
804
-		return $availableSpace;
805
-	}
806
-
807
-	/**
808
-	 * resize trash bin if necessary after a new file was added to Nextcloud
809
-	 *
810
-	 * @param string $user user id
811
-	 */
812
-	public static function resizeTrash($user) {
813
-		$size = self::getTrashbinSize($user);
814
-
815
-		$freeSpace = self::calculateFreeSpace($size, $user);
816
-
817
-		if ($freeSpace < 0) {
818
-			self::scheduleExpire($user);
819
-		}
820
-	}
821
-
822
-	/**
823
-	 * clean up the trash bin
824
-	 *
825
-	 * @param string $user
826
-	 */
827
-	public static function expire($user) {
828
-		$trashBinSize = self::getTrashbinSize($user);
829
-		$availableSpace = self::calculateFreeSpace($trashBinSize, $user);
830
-
831
-		$dirContent = Helper::getTrashFiles('/', $user, 'mtime');
832
-
833
-		// delete all files older then $retention_obligation
834
-		[$delSize, $count] = self::deleteExpiredFiles($dirContent, $user);
835
-
836
-		$availableSpace += $delSize;
837
-
838
-		// delete files from trash until we meet the trash bin size limit again
839
-		self::deleteFiles(array_slice($dirContent, $count), $user, $availableSpace);
840
-	}
841
-
842
-	/**
843
-	 * @param string $user
844
-	 */
845
-	private static function scheduleExpire($user) {
846
-		// let the admin disable auto expire
847
-		/** @var Application $application */
848
-		$application = \OC::$server->query(Application::class);
849
-		$expiration = $application->getContainer()->query('Expiration');
850
-		if ($expiration->isEnabled()) {
851
-			\OC::$server->getCommandBus()->push(new Expire($user));
852
-		}
853
-	}
854
-
855
-	/**
856
-	 * if the size limit for the trash bin is reached, we delete the oldest
857
-	 * files in the trash bin until we meet the limit again
858
-	 *
859
-	 * @param array $files
860
-	 * @param string $user
861
-	 * @param int $availableSpace available disc space
862
-	 * @return int size of deleted files
863
-	 */
864
-	protected static function deleteFiles($files, $user, $availableSpace) {
865
-		/** @var Application $application */
866
-		$application = \OC::$server->query(Application::class);
867
-		$expiration = $application->getContainer()->query('Expiration');
868
-		$size = 0;
869
-
870
-		if ($availableSpace < 0) {
871
-			foreach ($files as $file) {
872
-				if ($availableSpace < 0 && $expiration->isExpired($file['mtime'], true)) {
873
-					$tmp = self::delete($file['name'], $user, $file['mtime']);
874
-					\OC::$server->getLogger()->info('remove "' . $file['name'] . '" (' . $tmp . 'B) to meet the limit of trash bin size (50% of available quota)', ['app' => 'files_trashbin']);
875
-					$availableSpace += $tmp;
876
-					$size += $tmp;
877
-				} else {
878
-					break;
879
-				}
880
-			}
881
-		}
882
-		return $size;
883
-	}
884
-
885
-	/**
886
-	 * delete files older then max storage time
887
-	 *
888
-	 * @param array $files list of files sorted by mtime
889
-	 * @param string $user
890
-	 * @return integer[] size of deleted files and number of deleted files
891
-	 */
892
-	public static function deleteExpiredFiles($files, $user) {
893
-		/** @var Expiration $expiration */
894
-		$expiration = \OC::$server->query(Expiration::class);
895
-		$size = 0;
896
-		$count = 0;
897
-		foreach ($files as $file) {
898
-			$timestamp = $file['mtime'];
899
-			$filename = $file['name'];
900
-			if ($expiration->isExpired($timestamp)) {
901
-				try {
902
-					$size += self::delete($filename, $user, $timestamp);
903
-					$count++;
904
-				} catch (\OCP\Files\NotPermittedException $e) {
905
-					\OC::$server->getLogger()->logException($e, ['app' => 'files_trashbin', 'level' => \OCP\ILogger::WARN, 'message' => 'Removing "' . $filename . '" from trashbin failed.']);
906
-				}
907
-				\OC::$server->getLogger()->info(
908
-					'Remove "' . $filename . '" from trashbin because it exceeds max retention obligation term.',
909
-					['app' => 'files_trashbin']
910
-				);
911
-			} else {
912
-				break;
913
-			}
914
-		}
915
-
916
-		return [$size, $count];
917
-	}
918
-
919
-	/**
920
-	 * recursive copy to copy a whole directory
921
-	 *
922
-	 * @param string $source source path, relative to the users files directory
923
-	 * @param string $destination destination path relative to the users root directoy
924
-	 * @param View $view file view for the users root directory
925
-	 * @return int
926
-	 * @throws Exceptions\CopyRecursiveException
927
-	 */
928
-	private static function copy_recursive($source, $destination, View $view) {
929
-		$size = 0;
930
-		if ($view->is_dir($source)) {
931
-			$view->mkdir($destination);
932
-			$view->touch($destination, $view->filemtime($source));
933
-			foreach ($view->getDirectoryContent($source) as $i) {
934
-				$pathDir = $source . '/' . $i['name'];
935
-				if ($view->is_dir($pathDir)) {
936
-					$size += self::copy_recursive($pathDir, $destination . '/' . $i['name'], $view);
937
-				} else {
938
-					$size += $view->filesize($pathDir);
939
-					$result = $view->copy($pathDir, $destination . '/' . $i['name']);
940
-					if (!$result) {
941
-						throw new \OCA\Files_Trashbin\Exceptions\CopyRecursiveException();
942
-					}
943
-					$view->touch($destination . '/' . $i['name'], $view->filemtime($pathDir));
944
-				}
945
-			}
946
-		} else {
947
-			$size += $view->filesize($source);
948
-			$result = $view->copy($source, $destination);
949
-			if (!$result) {
950
-				throw new \OCA\Files_Trashbin\Exceptions\CopyRecursiveException();
951
-			}
952
-			$view->touch($destination, $view->filemtime($source));
953
-		}
954
-		return $size;
955
-	}
956
-
957
-	/**
958
-	 * find all versions which belong to the file we want to restore
959
-	 *
960
-	 * @param string $filename name of the file which should be restored
961
-	 * @param int $timestamp timestamp when the file was deleted
962
-	 * @return array
963
-	 */
964
-	private static function getVersionsFromTrash($filename, $timestamp, $user) {
965
-		$view = new View('/' . $user . '/files_trashbin/versions');
966
-		$versions = [];
967
-
968
-		/** @var \OC\Files\Storage\Storage $storage */
969
-		[$storage,] = $view->resolvePath('/');
970
-
971
-		//force rescan of versions, local storage may not have updated the cache
972
-		if (!self::$scannedVersions) {
973
-			$storage->getScanner()->scan('files_trashbin/versions');
974
-			self::$scannedVersions = true;
975
-		}
976
-
977
-		$pattern = \OC::$server->getDatabaseConnection()->escapeLikeParameter(basename($filename));
978
-		if ($timestamp) {
979
-			// fetch for old versions
980
-			$escapedTimestamp = \OC::$server->getDatabaseConnection()->escapeLikeParameter($timestamp);
981
-			$pattern .= '.v%.d' . $escapedTimestamp;
982
-			$offset = -strlen($escapedTimestamp) - 2;
983
-		} else {
984
-			$pattern .= '.v%';
985
-		}
986
-
987
-		// Manually fetch all versions from the file cache to be able to filter them by their parent
988
-		$cache = $storage->getCache('');
989
-		$query = new CacheQueryBuilder(
990
-			\OC::$server->getDatabaseConnection(),
991
-			\OC::$server->getSystemConfig(),
992
-			\OC::$server->getLogger(),
993
-			$cache
994
-		);
995
-		$normalizedParentPath = ltrim(Filesystem::normalizePath(dirname('files_trashbin/versions/'. $filename)), '/');
996
-		$parentId = $cache->getId($normalizedParentPath);
997
-		if ($parentId === -1) {
998
-			return [];
999
-		}
1000
-
1001
-		$query->selectFileCache()
1002
-			->whereStorageId()
1003
-			->andWhere($query->expr()->eq('parent', $query->createNamedParameter($parentId)))
1004
-			->andWhere($query->expr()->iLike('name', $query->createNamedParameter($pattern)));
1005
-
1006
-		$result = $query->executeQuery();
1007
-		$entries = $result->fetchAll();
1008
-		$result->closeCursor();
1009
-
1010
-		/** @var CacheEntry[] $matches */
1011
-		$matches = array_map(function (array $data) {
1012
-			return Cache::cacheEntryFromData($data, \OC::$server->getMimeTypeLoader());
1013
-		}, $entries);
1014
-
1015
-		foreach ($matches as $ma) {
1016
-			if ($timestamp) {
1017
-				$parts = explode('.v', substr($ma['path'], 0, $offset));
1018
-				$versions[] = end($parts);
1019
-			} else {
1020
-				$parts = explode('.v', $ma['path']);
1021
-				$versions[] = end($parts);
1022
-			}
1023
-		}
1024
-
1025
-		return $versions;
1026
-	}
1027
-
1028
-	/**
1029
-	 * find unique extension for restored file if a file with the same name already exists
1030
-	 *
1031
-	 * @param string $location where the file should be restored
1032
-	 * @param string $filename name of the file
1033
-	 * @param View $view filesystem view relative to users root directory
1034
-	 * @return string with unique extension
1035
-	 */
1036
-	private static function getUniqueFilename($location, $filename, View $view) {
1037
-		$ext = pathinfo($filename, PATHINFO_EXTENSION);
1038
-		$name = pathinfo($filename, PATHINFO_FILENAME);
1039
-		$l = \OC::$server->getL10N('files_trashbin');
1040
-
1041
-		$location = '/' . trim($location, '/');
1042
-
1043
-		// if extension is not empty we set a dot in front of it
1044
-		if ($ext !== '') {
1045
-			$ext = '.' . $ext;
1046
-		}
1047
-
1048
-		if ($view->file_exists('files' . $location . '/' . $filename)) {
1049
-			$i = 2;
1050
-			$uniqueName = $name . " (" . $l->t("restored") . ")" . $ext;
1051
-			while ($view->file_exists('files' . $location . '/' . $uniqueName)) {
1052
-				$uniqueName = $name . " (" . $l->t("restored") . " " . $i . ")" . $ext;
1053
-				$i++;
1054
-			}
1055
-
1056
-			return $uniqueName;
1057
-		}
1058
-
1059
-		return $filename;
1060
-	}
1061
-
1062
-	/**
1063
-	 * get the size from a given root folder
1064
-	 *
1065
-	 * @param View $view file view on the root folder
1066
-	 * @return integer size of the folder
1067
-	 */
1068
-	private static function calculateSize($view) {
1069
-		$root = \OC::$server->getConfig()->getSystemValue('datadirectory', \OC::$SERVERROOT . '/data') . $view->getAbsolutePath('');
1070
-		if (!file_exists($root)) {
1071
-			return 0;
1072
-		}
1073
-		$iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($root), \RecursiveIteratorIterator::CHILD_FIRST);
1074
-		$size = 0;
1075
-
1076
-		/**
1077
-		 * RecursiveDirectoryIterator on an NFS path isn't iterable with foreach
1078
-		 * This bug is fixed in PHP 5.5.9 or before
1079
-		 * See #8376
1080
-		 */
1081
-		$iterator->rewind();
1082
-		while ($iterator->valid()) {
1083
-			$path = $iterator->current();
1084
-			$relpath = substr($path, strlen($root) - 1);
1085
-			if (!$view->is_dir($relpath)) {
1086
-				$size += $view->filesize($relpath);
1087
-			}
1088
-			$iterator->next();
1089
-		}
1090
-		return $size;
1091
-	}
1092
-
1093
-	/**
1094
-	 * get current size of trash bin from a given user
1095
-	 *
1096
-	 * @param string $user user who owns the trash bin
1097
-	 * @return integer trash bin size
1098
-	 */
1099
-	private static function getTrashbinSize($user) {
1100
-		$view = new View('/' . $user);
1101
-		$fileInfo = $view->getFileInfo('/files_trashbin');
1102
-		return isset($fileInfo['size']) ? $fileInfo['size'] : 0;
1103
-	}
1104
-
1105
-	/**
1106
-	 * check if trash bin is empty for a given user
1107
-	 *
1108
-	 * @param string $user
1109
-	 * @return bool
1110
-	 */
1111
-	public static function isEmpty($user) {
1112
-		$view = new View('/' . $user . '/files_trashbin');
1113
-		if ($view->is_dir('/files') && $dh = $view->opendir('/files')) {
1114
-			while ($file = readdir($dh)) {
1115
-				if (!Filesystem::isIgnoredDir($file)) {
1116
-					return false;
1117
-				}
1118
-			}
1119
-		}
1120
-		return true;
1121
-	}
1122
-
1123
-	/**
1124
-	 * @param $path
1125
-	 * @return string
1126
-	 */
1127
-	public static function preview_icon($path) {
1128
-		return \OC::$server->getURLGenerator()->linkToRoute('core_ajax_trashbin_preview', ['x' => 32, 'y' => 32, 'file' => $path]);
1129
-	}
67
+    // unit: percentage; 50% of available disk space/quota
68
+    public const DEFAULTMAXSIZE = 50;
69
+
70
+    /**
71
+     * Whether versions have already be rescanned during this PHP request
72
+     *
73
+     * @var bool
74
+     */
75
+    private static $scannedVersions = false;
76
+
77
+    /**
78
+     * Ensure we don't need to scan the file during the move to trash
79
+     * by triggering the scan in the pre-hook
80
+     *
81
+     * @param array $params
82
+     */
83
+    public static function ensureFileScannedHook($params) {
84
+        try {
85
+            self::getUidAndFilename($params['path']);
86
+        } catch (NotFoundException $e) {
87
+            // nothing to scan for non existing files
88
+        }
89
+    }
90
+
91
+    /**
92
+     * get the UID of the owner of the file and the path to the file relative to
93
+     * owners files folder
94
+     *
95
+     * @param string $filename
96
+     * @return array
97
+     * @throws \OC\User\NoUserException
98
+     */
99
+    public static function getUidAndFilename($filename) {
100
+        $uid = Filesystem::getOwner($filename);
101
+        $userManager = \OC::$server->getUserManager();
102
+        // if the user with the UID doesn't exists, e.g. because the UID points
103
+        // to a remote user with a federated cloud ID we use the current logged-in
104
+        // user. We need a valid local user to move the file to the right trash bin
105
+        if (!$userManager->userExists($uid)) {
106
+            $uid = OC_User::getUser();
107
+        }
108
+        if (!$uid) {
109
+            // no owner, usually because of share link from ext storage
110
+            return [null, null];
111
+        }
112
+        Filesystem::initMountPoints($uid);
113
+        if ($uid !== OC_User::getUser()) {
114
+            $info = Filesystem::getFileInfo($filename);
115
+            $ownerView = new View('/' . $uid . '/files');
116
+            try {
117
+                $filename = $ownerView->getPath($info['fileid']);
118
+            } catch (NotFoundException $e) {
119
+                $filename = null;
120
+            }
121
+        }
122
+        return [$uid, $filename];
123
+    }
124
+
125
+    /**
126
+     * get original location of files for user
127
+     *
128
+     * @param string $user
129
+     * @return array (filename => array (timestamp => original location))
130
+     */
131
+    public static function getLocations($user) {
132
+        $query = \OC::$server->getDatabaseConnection()->getQueryBuilder();
133
+        $query->select('id', 'timestamp', 'location')
134
+            ->from('files_trash')
135
+            ->where($query->expr()->eq('user', $query->createNamedParameter($user)));
136
+        $result = $query->executeQuery();
137
+        $array = [];
138
+        while ($row = $result->fetch()) {
139
+            if (isset($array[$row['id']])) {
140
+                $array[$row['id']][$row['timestamp']] = $row['location'];
141
+            } else {
142
+                $array[$row['id']] = [$row['timestamp'] => $row['location']];
143
+            }
144
+        }
145
+        $result->closeCursor();
146
+        return $array;
147
+    }
148
+
149
+    /**
150
+     * get original location of file
151
+     *
152
+     * @param string $user
153
+     * @param string $filename
154
+     * @param string $timestamp
155
+     * @return string original location
156
+     */
157
+    public static function getLocation($user, $filename, $timestamp) {
158
+        $query = \OC::$server->getDatabaseConnection()->getQueryBuilder();
159
+        $query->select('location')
160
+            ->from('files_trash')
161
+            ->where($query->expr()->eq('user', $query->createNamedParameter($user)))
162
+            ->andWhere($query->expr()->eq('id', $query->createNamedParameter($filename)))
163
+            ->andWhere($query->expr()->eq('timestamp', $query->createNamedParameter($timestamp)));
164
+
165
+        $result = $query->executeQuery();
166
+        $row = $result->fetch();
167
+        $result->closeCursor();
168
+
169
+        if (isset($row['location'])) {
170
+            return $row['location'];
171
+        } else {
172
+            return false;
173
+        }
174
+    }
175
+
176
+    private static function setUpTrash($user) {
177
+        $view = new View('/' . $user);
178
+        if (!$view->is_dir('files_trashbin')) {
179
+            $view->mkdir('files_trashbin');
180
+        }
181
+        if (!$view->is_dir('files_trashbin/files')) {
182
+            $view->mkdir('files_trashbin/files');
183
+        }
184
+        if (!$view->is_dir('files_trashbin/versions')) {
185
+            $view->mkdir('files_trashbin/versions');
186
+        }
187
+        if (!$view->is_dir('files_trashbin/keys')) {
188
+            $view->mkdir('files_trashbin/keys');
189
+        }
190
+    }
191
+
192
+
193
+    /**
194
+     * copy file to owners trash
195
+     *
196
+     * @param string $sourcePath
197
+     * @param string $owner
198
+     * @param string $targetPath
199
+     * @param $user
200
+     * @param integer $timestamp
201
+     */
202
+    private static function copyFilesToUser($sourcePath, $owner, $targetPath, $user, $timestamp) {
203
+        self::setUpTrash($owner);
204
+
205
+        $targetFilename = basename($targetPath);
206
+        $targetLocation = dirname($targetPath);
207
+
208
+        $sourceFilename = basename($sourcePath);
209
+
210
+        $view = new View('/');
211
+
212
+        $target = $user . '/files_trashbin/files/' . $targetFilename . '.d' . $timestamp;
213
+        $source = $owner . '/files_trashbin/files/' . $sourceFilename . '.d' . $timestamp;
214
+        $free = $view->free_space($target);
215
+        $isUnknownOrUnlimitedFreeSpace = $free < 0;
216
+        $isEnoughFreeSpaceLeft = $view->filesize($source) < $free;
217
+        if ($isUnknownOrUnlimitedFreeSpace || $isEnoughFreeSpaceLeft) {
218
+            self::copy_recursive($source, $target, $view);
219
+        }
220
+
221
+
222
+        if ($view->file_exists($target)) {
223
+            $query = \OC::$server->getDatabaseConnection()->getQueryBuilder();
224
+            $query->insert('files_trash')
225
+                ->setValue('id', $query->createNamedParameter($targetFilename))
226
+                ->setValue('timestamp', $query->createNamedParameter($timestamp))
227
+                ->setValue('location', $query->createNamedParameter($targetLocation))
228
+                ->setValue('user', $query->createNamedParameter($user));
229
+            $result = $query->executeStatement();
230
+            if (!$result) {
231
+                \OC::$server->getLogger()->error('trash bin database couldn\'t be updated for the files owner', ['app' => 'files_trashbin']);
232
+            }
233
+        }
234
+    }
235
+
236
+
237
+    /**
238
+     * move file to the trash bin
239
+     *
240
+     * @param string $file_path path to the deleted file/directory relative to the files root directory
241
+     * @param bool $ownerOnly delete for owner only (if file gets moved out of a shared folder)
242
+     *
243
+     * @return bool
244
+     */
245
+    public static function move2trash($file_path, $ownerOnly = false) {
246
+        // get the user for which the filesystem is setup
247
+        $root = Filesystem::getRoot();
248
+        [, $user] = explode('/', $root);
249
+        [$owner, $ownerPath] = self::getUidAndFilename($file_path);
250
+
251
+        // if no owner found (ex: ext storage + share link), will use the current user's trashbin then
252
+        if (is_null($owner)) {
253
+            $owner = $user;
254
+            $ownerPath = $file_path;
255
+        }
256
+
257
+        $ownerView = new View('/' . $owner);
258
+        // file has been deleted in between
259
+        if (is_null($ownerPath) || $ownerPath === '' || !$ownerView->file_exists('/files/' . $ownerPath)) {
260
+            return true;
261
+        }
262
+
263
+        self::setUpTrash($user);
264
+        if ($owner !== $user) {
265
+            // also setup for owner
266
+            self::setUpTrash($owner);
267
+        }
268
+
269
+        $path_parts = pathinfo($ownerPath);
270
+
271
+        $filename = $path_parts['basename'];
272
+        $location = $path_parts['dirname'];
273
+        /** @var ITimeFactory $timeFactory */
274
+        $timeFactory = \OC::$server->query(ITimeFactory::class);
275
+        $timestamp = $timeFactory->getTime();
276
+
277
+        $lockingProvider = \OC::$server->getLockingProvider();
278
+
279
+        // disable proxy to prevent recursive calls
280
+        $trashPath = '/files_trashbin/files/' . $filename . '.d' . $timestamp;
281
+        $gotLock = false;
282
+
283
+        while (!$gotLock) {
284
+            try {
285
+                /** @var \OC\Files\Storage\Storage $trashStorage */
286
+                [$trashStorage, $trashInternalPath] = $ownerView->resolvePath($trashPath);
287
+
288
+                $trashStorage->acquireLock($trashInternalPath, ILockingProvider::LOCK_EXCLUSIVE, $lockingProvider);
289
+                $gotLock = true;
290
+            } catch (LockedException $e) {
291
+                // a file with the same name is being deleted concurrently
292
+                // nudge the timestamp a bit to resolve the conflict
293
+
294
+                $timestamp = $timestamp + 1;
295
+
296
+                $trashPath = '/files_trashbin/files/' . $filename . '.d' . $timestamp;
297
+            }
298
+        }
299
+
300
+        /** @var \OC\Files\Storage\Storage $sourceStorage */
301
+        [$sourceStorage, $sourceInternalPath] = $ownerView->resolvePath('/files/' . $ownerPath);
302
+
303
+
304
+        if ($trashStorage->file_exists($trashInternalPath)) {
305
+            $trashStorage->unlink($trashInternalPath);
306
+        }
307
+
308
+        $config = \OC::$server->getConfig();
309
+        $systemTrashbinSize = (int)$config->getAppValue('files_trashbin', 'trashbin_size', '-1');
310
+        $userTrashbinSize = (int)$config->getUserValue($owner, 'files_trashbin', 'trashbin_size', '-1');
311
+        $configuredTrashbinSize = ($userTrashbinSize < 0) ? $systemTrashbinSize : $userTrashbinSize;
312
+        if ($configuredTrashbinSize >= 0 && $sourceStorage->filesize($sourceInternalPath) >= $configuredTrashbinSize) {
313
+            return false;
314
+        }
315
+
316
+        $trashStorage->getUpdater()->renameFromStorage($sourceStorage, $sourceInternalPath, $trashInternalPath);
317
+
318
+        try {
319
+            $moveSuccessful = true;
320
+
321
+            // when moving within the same object store, the cache update done above is enough to move the file
322
+            if (!($trashStorage->instanceOfStorage(ObjectStoreStorage::class) && $trashStorage->getId() === $sourceStorage->getId())) {
323
+                $trashStorage->moveFromStorage($sourceStorage, $sourceInternalPath, $trashInternalPath);
324
+            }
325
+        } catch (\OCA\Files_Trashbin\Exceptions\CopyRecursiveException $e) {
326
+            $moveSuccessful = false;
327
+            if ($trashStorage->file_exists($trashInternalPath)) {
328
+                $trashStorage->unlink($trashInternalPath);
329
+            }
330
+            \OC::$server->getLogger()->error('Couldn\'t move ' . $file_path . ' to the trash bin', ['app' => 'files_trashbin']);
331
+        }
332
+
333
+        if ($sourceStorage->file_exists($sourceInternalPath)) { // failed to delete the original file, abort
334
+            if ($sourceStorage->is_dir($sourceInternalPath)) {
335
+                $sourceStorage->rmdir($sourceInternalPath);
336
+            } else {
337
+                $sourceStorage->unlink($sourceInternalPath);
338
+            }
339
+
340
+            if ($sourceStorage->file_exists($sourceInternalPath)) {
341
+                // undo the cache move
342
+                $sourceStorage->getUpdater()->renameFromStorage($trashStorage, $trashInternalPath, $sourceInternalPath);
343
+            } else {
344
+                $trashStorage->getUpdater()->remove($trashInternalPath);
345
+            }
346
+            return false;
347
+        }
348
+
349
+        if ($moveSuccessful) {
350
+            $query = \OC::$server->getDatabaseConnection()->getQueryBuilder();
351
+            $query->insert('files_trash')
352
+                ->setValue('id', $query->createNamedParameter($filename))
353
+                ->setValue('timestamp', $query->createNamedParameter($timestamp))
354
+                ->setValue('location', $query->createNamedParameter($location))
355
+                ->setValue('user', $query->createNamedParameter($owner));
356
+            $result = $query->executeStatement();
357
+            if (!$result) {
358
+                \OC::$server->getLogger()->error('trash bin database couldn\'t be updated', ['app' => 'files_trashbin']);
359
+            }
360
+            \OCP\Util::emitHook('\OCA\Files_Trashbin\Trashbin', 'post_moveToTrash', ['filePath' => Filesystem::normalizePath($file_path),
361
+                'trashPath' => Filesystem::normalizePath($filename . '.d' . $timestamp)]);
362
+
363
+            self::retainVersions($filename, $owner, $ownerPath, $timestamp);
364
+
365
+            // if owner !== user we need to also add a copy to the users trash
366
+            if ($user !== $owner && $ownerOnly === false) {
367
+                self::copyFilesToUser($ownerPath, $owner, $file_path, $user, $timestamp);
368
+            }
369
+        }
370
+
371
+        $trashStorage->releaseLock($trashInternalPath, ILockingProvider::LOCK_EXCLUSIVE, $lockingProvider);
372
+
373
+        self::scheduleExpire($user);
374
+
375
+        // if owner !== user we also need to update the owners trash size
376
+        if ($owner !== $user) {
377
+            self::scheduleExpire($owner);
378
+        }
379
+
380
+        return $moveSuccessful;
381
+    }
382
+
383
+    /**
384
+     * Move file versions to trash so that they can be restored later
385
+     *
386
+     * @param string $filename of deleted file
387
+     * @param string $owner owner user id
388
+     * @param string $ownerPath path relative to the owner's home storage
389
+     * @param integer $timestamp when the file was deleted
390
+     */
391
+    private static function retainVersions($filename, $owner, $ownerPath, $timestamp) {
392
+        if (\OCP\App::isEnabled('files_versions') && !empty($ownerPath)) {
393
+            $user = OC_User::getUser();
394
+            $rootView = new View('/');
395
+
396
+            if ($rootView->is_dir($owner . '/files_versions/' . $ownerPath)) {
397
+                if ($owner !== $user) {
398
+                    self::copy_recursive($owner . '/files_versions/' . $ownerPath, $owner . '/files_trashbin/versions/' . basename($ownerPath) . '.d' . $timestamp, $rootView);
399
+                }
400
+                self::move($rootView, $owner . '/files_versions/' . $ownerPath, $user . '/files_trashbin/versions/' . $filename . '.d' . $timestamp);
401
+            } elseif ($versions = \OCA\Files_Versions\Storage::getVersions($owner, $ownerPath)) {
402
+                foreach ($versions as $v) {
403
+                    if ($owner !== $user) {
404
+                        self::copy($rootView, $owner . '/files_versions' . $v['path'] . '.v' . $v['version'], $owner . '/files_trashbin/versions/' . $v['name'] . '.v' . $v['version'] . '.d' . $timestamp);
405
+                    }
406
+                    self::move($rootView, $owner . '/files_versions' . $v['path'] . '.v' . $v['version'], $user . '/files_trashbin/versions/' . $filename . '.v' . $v['version'] . '.d' . $timestamp);
407
+                }
408
+            }
409
+        }
410
+    }
411
+
412
+    /**
413
+     * Move a file or folder on storage level
414
+     *
415
+     * @param View $view
416
+     * @param string $source
417
+     * @param string $target
418
+     * @return bool
419
+     */
420
+    private static function move(View $view, $source, $target) {
421
+        /** @var \OC\Files\Storage\Storage $sourceStorage */
422
+        [$sourceStorage, $sourceInternalPath] = $view->resolvePath($source);
423
+        /** @var \OC\Files\Storage\Storage $targetStorage */
424
+        [$targetStorage, $targetInternalPath] = $view->resolvePath($target);
425
+        /** @var \OC\Files\Storage\Storage $ownerTrashStorage */
426
+
427
+        $result = $targetStorage->moveFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
428
+        if ($result) {
429
+            $targetStorage->getUpdater()->renameFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
430
+        }
431
+        return $result;
432
+    }
433
+
434
+    /**
435
+     * Copy a file or folder on storage level
436
+     *
437
+     * @param View $view
438
+     * @param string $source
439
+     * @param string $target
440
+     * @return bool
441
+     */
442
+    private static function copy(View $view, $source, $target) {
443
+        /** @var \OC\Files\Storage\Storage $sourceStorage */
444
+        [$sourceStorage, $sourceInternalPath] = $view->resolvePath($source);
445
+        /** @var \OC\Files\Storage\Storage $targetStorage */
446
+        [$targetStorage, $targetInternalPath] = $view->resolvePath($target);
447
+        /** @var \OC\Files\Storage\Storage $ownerTrashStorage */
448
+
449
+        $result = $targetStorage->copyFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
450
+        if ($result) {
451
+            $targetStorage->getUpdater()->update($targetInternalPath);
452
+        }
453
+        return $result;
454
+    }
455
+
456
+    /**
457
+     * Restore a file or folder from trash bin
458
+     *
459
+     * @param string $file path to the deleted file/folder relative to "files_trashbin/files/",
460
+     * including the timestamp suffix ".d12345678"
461
+     * @param string $filename name of the file/folder
462
+     * @param int $timestamp time when the file/folder was deleted
463
+     *
464
+     * @return bool true on success, false otherwise
465
+     */
466
+    public static function restore($file, $filename, $timestamp) {
467
+        $user = OC_User::getUser();
468
+        $view = new View('/' . $user);
469
+
470
+        $location = '';
471
+        if ($timestamp) {
472
+            $location = self::getLocation($user, $filename, $timestamp);
473
+            if ($location === false) {
474
+                \OC::$server->getLogger()->error('trash bin database inconsistent! ($user: ' . $user . ' $filename: ' . $filename . ', $timestamp: ' . $timestamp . ')', ['app' => 'files_trashbin']);
475
+            } else {
476
+                // if location no longer exists, restore file in the root directory
477
+                if ($location !== '/' &&
478
+                    (!$view->is_dir('files/' . $location) ||
479
+                        !$view->isCreatable('files/' . $location))
480
+                ) {
481
+                    $location = '';
482
+                }
483
+            }
484
+        }
485
+
486
+        // we need a  extension in case a file/dir with the same name already exists
487
+        $uniqueFilename = self::getUniqueFilename($location, $filename, $view);
488
+
489
+        $source = Filesystem::normalizePath('files_trashbin/files/' . $file);
490
+        $target = Filesystem::normalizePath('files/' . $location . '/' . $uniqueFilename);
491
+        if (!$view->file_exists($source)) {
492
+            return false;
493
+        }
494
+        $mtime = $view->filemtime($source);
495
+
496
+        // restore file
497
+        if (!$view->isCreatable(dirname($target))) {
498
+            throw new NotPermittedException("Can't restore trash item because the target folder is not writable");
499
+        }
500
+        $restoreResult = $view->rename($source, $target);
501
+
502
+        // handle the restore result
503
+        if ($restoreResult) {
504
+            $fakeRoot = $view->getRoot();
505
+            $view->chroot('/' . $user . '/files');
506
+            $view->touch('/' . $location . '/' . $uniqueFilename, $mtime);
507
+            $view->chroot($fakeRoot);
508
+            \OCP\Util::emitHook('\OCA\Files_Trashbin\Trashbin', 'post_restore', ['filePath' => Filesystem::normalizePath('/' . $location . '/' . $uniqueFilename),
509
+                'trashPath' => Filesystem::normalizePath($file)]);
510
+
511
+            self::restoreVersions($view, $file, $filename, $uniqueFilename, $location, $timestamp);
512
+
513
+            if ($timestamp) {
514
+                $query = \OC::$server->getDatabaseConnection()->getQueryBuilder();
515
+                $query->delete('files_trash')
516
+                    ->where($query->expr()->eq('user', $query->createNamedParameter($user)))
517
+                    ->andWhere($query->expr()->eq('id', $query->createNamedParameter($filename)))
518
+                    ->andWhere($query->expr()->eq('timestamp', $query->createNamedParameter($timestamp)));
519
+                $query->executeStatement();
520
+            }
521
+
522
+            return true;
523
+        }
524
+
525
+        return false;
526
+    }
527
+
528
+    /**
529
+     * restore versions from trash bin
530
+     *
531
+     * @param View $view file view
532
+     * @param string $file complete path to file
533
+     * @param string $filename name of file once it was deleted
534
+     * @param string $uniqueFilename new file name to restore the file without overwriting existing files
535
+     * @param string $location location if file
536
+     * @param int $timestamp deletion time
537
+     * @return false|null
538
+     */
539
+    private static function restoreVersions(View $view, $file, $filename, $uniqueFilename, $location, $timestamp) {
540
+        if (\OCP\App::isEnabled('files_versions')) {
541
+            $user = OC_User::getUser();
542
+            $rootView = new View('/');
543
+
544
+            $target = Filesystem::normalizePath('/' . $location . '/' . $uniqueFilename);
545
+
546
+            [$owner, $ownerPath] = self::getUidAndFilename($target);
547
+
548
+            // file has been deleted in between
549
+            if (empty($ownerPath)) {
550
+                return false;
551
+            }
552
+
553
+            if ($timestamp) {
554
+                $versionedFile = $filename;
555
+            } else {
556
+                $versionedFile = $file;
557
+            }
558
+
559
+            if ($view->is_dir('/files_trashbin/versions/' . $file)) {
560
+                $rootView->rename(Filesystem::normalizePath($user . '/files_trashbin/versions/' . $file), Filesystem::normalizePath($owner . '/files_versions/' . $ownerPath));
561
+            } elseif ($versions = self::getVersionsFromTrash($versionedFile, $timestamp, $user)) {
562
+                foreach ($versions as $v) {
563
+                    if ($timestamp) {
564
+                        $rootView->rename($user . '/files_trashbin/versions/' . $versionedFile . '.v' . $v . '.d' . $timestamp, $owner . '/files_versions/' . $ownerPath . '.v' . $v);
565
+                    } else {
566
+                        $rootView->rename($user . '/files_trashbin/versions/' . $versionedFile . '.v' . $v, $owner . '/files_versions/' . $ownerPath . '.v' . $v);
567
+                    }
568
+                }
569
+            }
570
+        }
571
+    }
572
+
573
+    /**
574
+     * delete all files from the trash
575
+     */
576
+    public static function deleteAll() {
577
+        $user = OC_User::getUser();
578
+        $userRoot = \OC::$server->getUserFolder($user)->getParent();
579
+        $view = new View('/' . $user);
580
+        $fileInfos = $view->getDirectoryContent('files_trashbin/files');
581
+
582
+        try {
583
+            $trash = $userRoot->get('files_trashbin');
584
+        } catch (NotFoundException $e) {
585
+            return false;
586
+        }
587
+
588
+        // Array to store the relative path in (after the file is deleted, the view won't be able to relativise the path anymore)
589
+        $filePaths = [];
590
+        foreach ($fileInfos as $fileInfo) {
591
+            $filePaths[] = $view->getRelativePath($fileInfo->getPath());
592
+        }
593
+        unset($fileInfos); // save memory
594
+
595
+        // Bulk PreDelete-Hook
596
+        \OC_Hook::emit('\OCP\Trashbin', 'preDeleteAll', ['paths' => $filePaths]);
597
+
598
+        // Single-File Hooks
599
+        foreach ($filePaths as $path) {
600
+            self::emitTrashbinPreDelete($path);
601
+        }
602
+
603
+        // actual file deletion
604
+        $trash->delete();
605
+
606
+        $query = \OC::$server->getDatabaseConnection()->getQueryBuilder();
607
+        $query->delete('files_trash')
608
+            ->where($query->expr()->eq('user', $query->createNamedParameter($user)));
609
+        $query->executeStatement();
610
+
611
+        // Bulk PostDelete-Hook
612
+        \OC_Hook::emit('\OCP\Trashbin', 'deleteAll', ['paths' => $filePaths]);
613
+
614
+        // Single-File Hooks
615
+        foreach ($filePaths as $path) {
616
+            self::emitTrashbinPostDelete($path);
617
+        }
618
+
619
+        $trash = $userRoot->newFolder('files_trashbin');
620
+        $trash->newFolder('files');
621
+
622
+        return true;
623
+    }
624
+
625
+    /**
626
+     * wrapper function to emit the 'preDelete' hook of \OCP\Trashbin before a file is deleted
627
+     *
628
+     * @param string $path
629
+     */
630
+    protected static function emitTrashbinPreDelete($path) {
631
+        \OC_Hook::emit('\OCP\Trashbin', 'preDelete', ['path' => $path]);
632
+    }
633
+
634
+    /**
635
+     * wrapper function to emit the 'delete' hook of \OCP\Trashbin after a file has been deleted
636
+     *
637
+     * @param string $path
638
+     */
639
+    protected static function emitTrashbinPostDelete($path) {
640
+        \OC_Hook::emit('\OCP\Trashbin', 'delete', ['path' => $path]);
641
+    }
642
+
643
+    /**
644
+     * delete file from trash bin permanently
645
+     *
646
+     * @param string $filename path to the file
647
+     * @param string $user
648
+     * @param int $timestamp of deletion time
649
+     *
650
+     * @return int size of deleted files
651
+     */
652
+    public static function delete($filename, $user, $timestamp = null) {
653
+        $userRoot = \OC::$server->getUserFolder($user)->getParent();
654
+        $view = new View('/' . $user);
655
+        $size = 0;
656
+
657
+        if ($timestamp) {
658
+            $query = \OC::$server->getDatabaseConnection()->getQueryBuilder();
659
+            $query->delete('files_trash')
660
+                ->where($query->expr()->eq('user', $query->createNamedParameter($user)))
661
+                ->andWhere($query->expr()->eq('id', $query->createNamedParameter($filename)))
662
+                ->andWhere($query->expr()->eq('timestamp', $query->createNamedParameter($timestamp)));
663
+            $query->executeStatement();
664
+
665
+            $file = $filename . '.d' . $timestamp;
666
+        } else {
667
+            $file = $filename;
668
+        }
669
+
670
+        $size += self::deleteVersions($view, $file, $filename, $timestamp, $user);
671
+
672
+        try {
673
+            $node = $userRoot->get('/files_trashbin/files/' . $file);
674
+        } catch (NotFoundException $e) {
675
+            return $size;
676
+        }
677
+
678
+        if ($node instanceof Folder) {
679
+            $size += self::calculateSize(new View('/' . $user . '/files_trashbin/files/' . $file));
680
+        } elseif ($node instanceof File) {
681
+            $size += $view->filesize('/files_trashbin/files/' . $file);
682
+        }
683
+
684
+        self::emitTrashbinPreDelete('/files_trashbin/files/' . $file);
685
+        $node->delete();
686
+        self::emitTrashbinPostDelete('/files_trashbin/files/' . $file);
687
+
688
+        return $size;
689
+    }
690
+
691
+    /**
692
+     * @param View $view
693
+     * @param string $file
694
+     * @param string $filename
695
+     * @param integer|null $timestamp
696
+     * @param string $user
697
+     * @return int
698
+     */
699
+    private static function deleteVersions(View $view, $file, $filename, $timestamp, $user) {
700
+        $size = 0;
701
+        if (\OCP\App::isEnabled('files_versions')) {
702
+            if ($view->is_dir('files_trashbin/versions/' . $file)) {
703
+                $size += self::calculateSize(new View('/' . $user . '/files_trashbin/versions/' . $file));
704
+                $view->unlink('files_trashbin/versions/' . $file);
705
+            } elseif ($versions = self::getVersionsFromTrash($filename, $timestamp, $user)) {
706
+                foreach ($versions as $v) {
707
+                    if ($timestamp) {
708
+                        $size += $view->filesize('/files_trashbin/versions/' . $filename . '.v' . $v . '.d' . $timestamp);
709
+                        $view->unlink('/files_trashbin/versions/' . $filename . '.v' . $v . '.d' . $timestamp);
710
+                    } else {
711
+                        $size += $view->filesize('/files_trashbin/versions/' . $filename . '.v' . $v);
712
+                        $view->unlink('/files_trashbin/versions/' . $filename . '.v' . $v);
713
+                    }
714
+                }
715
+            }
716
+        }
717
+        return $size;
718
+    }
719
+
720
+    /**
721
+     * check to see whether a file exists in trashbin
722
+     *
723
+     * @param string $filename path to the file
724
+     * @param int $timestamp of deletion time
725
+     * @return bool true if file exists, otherwise false
726
+     */
727
+    public static function file_exists($filename, $timestamp = null) {
728
+        $user = OC_User::getUser();
729
+        $view = new View('/' . $user);
730
+
731
+        if ($timestamp) {
732
+            $filename = $filename . '.d' . $timestamp;
733
+        }
734
+
735
+        $target = Filesystem::normalizePath('files_trashbin/files/' . $filename);
736
+        return $view->file_exists($target);
737
+    }
738
+
739
+    /**
740
+     * deletes used space for trash bin in db if user was deleted
741
+     *
742
+     * @param string $uid id of deleted user
743
+     * @return bool result of db delete operation
744
+     */
745
+    public static function deleteUser($uid) {
746
+        $query = \OC::$server->getDatabaseConnection()->getQueryBuilder();
747
+        $query->delete('files_trash')
748
+            ->where($query->expr()->eq('user', $query->createNamedParameter($uid)));
749
+        return (bool) $query->executeStatement();
750
+    }
751
+
752
+    /**
753
+     * calculate remaining free space for trash bin
754
+     *
755
+     * @param integer $trashbinSize current size of the trash bin
756
+     * @param string $user
757
+     * @return int available free space for trash bin
758
+     */
759
+    private static function calculateFreeSpace($trashbinSize, $user) {
760
+        $config = \OC::$server->getConfig();
761
+        $userTrashbinSize = (int)$config->getUserValue($user, 'files_trashbin', 'trashbin_size', '-1');
762
+        if ($userTrashbinSize > -1) {
763
+            return $userTrashbinSize - $trashbinSize;
764
+        }
765
+        $systemTrashbinSize = (int)$config->getAppValue('files_trashbin', 'trashbin_size', '-1');
766
+        if ($systemTrashbinSize > -1) {
767
+            return $systemTrashbinSize - $trashbinSize;
768
+        }
769
+
770
+        $softQuota = true;
771
+        $userObject = \OC::$server->getUserManager()->get($user);
772
+        if (is_null($userObject)) {
773
+            return 0;
774
+        }
775
+        $quota = $userObject->getQuota();
776
+        if ($quota === null || $quota === 'none') {
777
+            $quota = Filesystem::free_space('/');
778
+            $softQuota = false;
779
+            // inf or unknown free space
780
+            if ($quota < 0) {
781
+                $quota = PHP_INT_MAX;
782
+            }
783
+        } else {
784
+            $quota = \OCP\Util::computerFileSize($quota);
785
+        }
786
+
787
+        // calculate available space for trash bin
788
+        // subtract size of files and current trash bin size from quota
789
+        if ($softQuota) {
790
+            $userFolder = \OC::$server->getUserFolder($user);
791
+            if (is_null($userFolder)) {
792
+                return 0;
793
+            }
794
+            $free = $quota - $userFolder->getSize(false); // remaining free space for user
795
+            if ($free > 0) {
796
+                $availableSpace = ($free * self::DEFAULTMAXSIZE / 100) - $trashbinSize; // how much space can be used for versions
797
+            } else {
798
+                $availableSpace = $free - $trashbinSize;
799
+            }
800
+        } else {
801
+            $availableSpace = $quota;
802
+        }
803
+
804
+        return $availableSpace;
805
+    }
806
+
807
+    /**
808
+     * resize trash bin if necessary after a new file was added to Nextcloud
809
+     *
810
+     * @param string $user user id
811
+     */
812
+    public static function resizeTrash($user) {
813
+        $size = self::getTrashbinSize($user);
814
+
815
+        $freeSpace = self::calculateFreeSpace($size, $user);
816
+
817
+        if ($freeSpace < 0) {
818
+            self::scheduleExpire($user);
819
+        }
820
+    }
821
+
822
+    /**
823
+     * clean up the trash bin
824
+     *
825
+     * @param string $user
826
+     */
827
+    public static function expire($user) {
828
+        $trashBinSize = self::getTrashbinSize($user);
829
+        $availableSpace = self::calculateFreeSpace($trashBinSize, $user);
830
+
831
+        $dirContent = Helper::getTrashFiles('/', $user, 'mtime');
832
+
833
+        // delete all files older then $retention_obligation
834
+        [$delSize, $count] = self::deleteExpiredFiles($dirContent, $user);
835
+
836
+        $availableSpace += $delSize;
837
+
838
+        // delete files from trash until we meet the trash bin size limit again
839
+        self::deleteFiles(array_slice($dirContent, $count), $user, $availableSpace);
840
+    }
841
+
842
+    /**
843
+     * @param string $user
844
+     */
845
+    private static function scheduleExpire($user) {
846
+        // let the admin disable auto expire
847
+        /** @var Application $application */
848
+        $application = \OC::$server->query(Application::class);
849
+        $expiration = $application->getContainer()->query('Expiration');
850
+        if ($expiration->isEnabled()) {
851
+            \OC::$server->getCommandBus()->push(new Expire($user));
852
+        }
853
+    }
854
+
855
+    /**
856
+     * if the size limit for the trash bin is reached, we delete the oldest
857
+     * files in the trash bin until we meet the limit again
858
+     *
859
+     * @param array $files
860
+     * @param string $user
861
+     * @param int $availableSpace available disc space
862
+     * @return int size of deleted files
863
+     */
864
+    protected static function deleteFiles($files, $user, $availableSpace) {
865
+        /** @var Application $application */
866
+        $application = \OC::$server->query(Application::class);
867
+        $expiration = $application->getContainer()->query('Expiration');
868
+        $size = 0;
869
+
870
+        if ($availableSpace < 0) {
871
+            foreach ($files as $file) {
872
+                if ($availableSpace < 0 && $expiration->isExpired($file['mtime'], true)) {
873
+                    $tmp = self::delete($file['name'], $user, $file['mtime']);
874
+                    \OC::$server->getLogger()->info('remove "' . $file['name'] . '" (' . $tmp . 'B) to meet the limit of trash bin size (50% of available quota)', ['app' => 'files_trashbin']);
875
+                    $availableSpace += $tmp;
876
+                    $size += $tmp;
877
+                } else {
878
+                    break;
879
+                }
880
+            }
881
+        }
882
+        return $size;
883
+    }
884
+
885
+    /**
886
+     * delete files older then max storage time
887
+     *
888
+     * @param array $files list of files sorted by mtime
889
+     * @param string $user
890
+     * @return integer[] size of deleted files and number of deleted files
891
+     */
892
+    public static function deleteExpiredFiles($files, $user) {
893
+        /** @var Expiration $expiration */
894
+        $expiration = \OC::$server->query(Expiration::class);
895
+        $size = 0;
896
+        $count = 0;
897
+        foreach ($files as $file) {
898
+            $timestamp = $file['mtime'];
899
+            $filename = $file['name'];
900
+            if ($expiration->isExpired($timestamp)) {
901
+                try {
902
+                    $size += self::delete($filename, $user, $timestamp);
903
+                    $count++;
904
+                } catch (\OCP\Files\NotPermittedException $e) {
905
+                    \OC::$server->getLogger()->logException($e, ['app' => 'files_trashbin', 'level' => \OCP\ILogger::WARN, 'message' => 'Removing "' . $filename . '" from trashbin failed.']);
906
+                }
907
+                \OC::$server->getLogger()->info(
908
+                    'Remove "' . $filename . '" from trashbin because it exceeds max retention obligation term.',
909
+                    ['app' => 'files_trashbin']
910
+                );
911
+            } else {
912
+                break;
913
+            }
914
+        }
915
+
916
+        return [$size, $count];
917
+    }
918
+
919
+    /**
920
+     * recursive copy to copy a whole directory
921
+     *
922
+     * @param string $source source path, relative to the users files directory
923
+     * @param string $destination destination path relative to the users root directoy
924
+     * @param View $view file view for the users root directory
925
+     * @return int
926
+     * @throws Exceptions\CopyRecursiveException
927
+     */
928
+    private static function copy_recursive($source, $destination, View $view) {
929
+        $size = 0;
930
+        if ($view->is_dir($source)) {
931
+            $view->mkdir($destination);
932
+            $view->touch($destination, $view->filemtime($source));
933
+            foreach ($view->getDirectoryContent($source) as $i) {
934
+                $pathDir = $source . '/' . $i['name'];
935
+                if ($view->is_dir($pathDir)) {
936
+                    $size += self::copy_recursive($pathDir, $destination . '/' . $i['name'], $view);
937
+                } else {
938
+                    $size += $view->filesize($pathDir);
939
+                    $result = $view->copy($pathDir, $destination . '/' . $i['name']);
940
+                    if (!$result) {
941
+                        throw new \OCA\Files_Trashbin\Exceptions\CopyRecursiveException();
942
+                    }
943
+                    $view->touch($destination . '/' . $i['name'], $view->filemtime($pathDir));
944
+                }
945
+            }
946
+        } else {
947
+            $size += $view->filesize($source);
948
+            $result = $view->copy($source, $destination);
949
+            if (!$result) {
950
+                throw new \OCA\Files_Trashbin\Exceptions\CopyRecursiveException();
951
+            }
952
+            $view->touch($destination, $view->filemtime($source));
953
+        }
954
+        return $size;
955
+    }
956
+
957
+    /**
958
+     * find all versions which belong to the file we want to restore
959
+     *
960
+     * @param string $filename name of the file which should be restored
961
+     * @param int $timestamp timestamp when the file was deleted
962
+     * @return array
963
+     */
964
+    private static function getVersionsFromTrash($filename, $timestamp, $user) {
965
+        $view = new View('/' . $user . '/files_trashbin/versions');
966
+        $versions = [];
967
+
968
+        /** @var \OC\Files\Storage\Storage $storage */
969
+        [$storage,] = $view->resolvePath('/');
970
+
971
+        //force rescan of versions, local storage may not have updated the cache
972
+        if (!self::$scannedVersions) {
973
+            $storage->getScanner()->scan('files_trashbin/versions');
974
+            self::$scannedVersions = true;
975
+        }
976
+
977
+        $pattern = \OC::$server->getDatabaseConnection()->escapeLikeParameter(basename($filename));
978
+        if ($timestamp) {
979
+            // fetch for old versions
980
+            $escapedTimestamp = \OC::$server->getDatabaseConnection()->escapeLikeParameter($timestamp);
981
+            $pattern .= '.v%.d' . $escapedTimestamp;
982
+            $offset = -strlen($escapedTimestamp) - 2;
983
+        } else {
984
+            $pattern .= '.v%';
985
+        }
986
+
987
+        // Manually fetch all versions from the file cache to be able to filter them by their parent
988
+        $cache = $storage->getCache('');
989
+        $query = new CacheQueryBuilder(
990
+            \OC::$server->getDatabaseConnection(),
991
+            \OC::$server->getSystemConfig(),
992
+            \OC::$server->getLogger(),
993
+            $cache
994
+        );
995
+        $normalizedParentPath = ltrim(Filesystem::normalizePath(dirname('files_trashbin/versions/'. $filename)), '/');
996
+        $parentId = $cache->getId($normalizedParentPath);
997
+        if ($parentId === -1) {
998
+            return [];
999
+        }
1000
+
1001
+        $query->selectFileCache()
1002
+            ->whereStorageId()
1003
+            ->andWhere($query->expr()->eq('parent', $query->createNamedParameter($parentId)))
1004
+            ->andWhere($query->expr()->iLike('name', $query->createNamedParameter($pattern)));
1005
+
1006
+        $result = $query->executeQuery();
1007
+        $entries = $result->fetchAll();
1008
+        $result->closeCursor();
1009
+
1010
+        /** @var CacheEntry[] $matches */
1011
+        $matches = array_map(function (array $data) {
1012
+            return Cache::cacheEntryFromData($data, \OC::$server->getMimeTypeLoader());
1013
+        }, $entries);
1014
+
1015
+        foreach ($matches as $ma) {
1016
+            if ($timestamp) {
1017
+                $parts = explode('.v', substr($ma['path'], 0, $offset));
1018
+                $versions[] = end($parts);
1019
+            } else {
1020
+                $parts = explode('.v', $ma['path']);
1021
+                $versions[] = end($parts);
1022
+            }
1023
+        }
1024
+
1025
+        return $versions;
1026
+    }
1027
+
1028
+    /**
1029
+     * find unique extension for restored file if a file with the same name already exists
1030
+     *
1031
+     * @param string $location where the file should be restored
1032
+     * @param string $filename name of the file
1033
+     * @param View $view filesystem view relative to users root directory
1034
+     * @return string with unique extension
1035
+     */
1036
+    private static function getUniqueFilename($location, $filename, View $view) {
1037
+        $ext = pathinfo($filename, PATHINFO_EXTENSION);
1038
+        $name = pathinfo($filename, PATHINFO_FILENAME);
1039
+        $l = \OC::$server->getL10N('files_trashbin');
1040
+
1041
+        $location = '/' . trim($location, '/');
1042
+
1043
+        // if extension is not empty we set a dot in front of it
1044
+        if ($ext !== '') {
1045
+            $ext = '.' . $ext;
1046
+        }
1047
+
1048
+        if ($view->file_exists('files' . $location . '/' . $filename)) {
1049
+            $i = 2;
1050
+            $uniqueName = $name . " (" . $l->t("restored") . ")" . $ext;
1051
+            while ($view->file_exists('files' . $location . '/' . $uniqueName)) {
1052
+                $uniqueName = $name . " (" . $l->t("restored") . " " . $i . ")" . $ext;
1053
+                $i++;
1054
+            }
1055
+
1056
+            return $uniqueName;
1057
+        }
1058
+
1059
+        return $filename;
1060
+    }
1061
+
1062
+    /**
1063
+     * get the size from a given root folder
1064
+     *
1065
+     * @param View $view file view on the root folder
1066
+     * @return integer size of the folder
1067
+     */
1068
+    private static function calculateSize($view) {
1069
+        $root = \OC::$server->getConfig()->getSystemValue('datadirectory', \OC::$SERVERROOT . '/data') . $view->getAbsolutePath('');
1070
+        if (!file_exists($root)) {
1071
+            return 0;
1072
+        }
1073
+        $iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($root), \RecursiveIteratorIterator::CHILD_FIRST);
1074
+        $size = 0;
1075
+
1076
+        /**
1077
+         * RecursiveDirectoryIterator on an NFS path isn't iterable with foreach
1078
+         * This bug is fixed in PHP 5.5.9 or before
1079
+         * See #8376
1080
+         */
1081
+        $iterator->rewind();
1082
+        while ($iterator->valid()) {
1083
+            $path = $iterator->current();
1084
+            $relpath = substr($path, strlen($root) - 1);
1085
+            if (!$view->is_dir($relpath)) {
1086
+                $size += $view->filesize($relpath);
1087
+            }
1088
+            $iterator->next();
1089
+        }
1090
+        return $size;
1091
+    }
1092
+
1093
+    /**
1094
+     * get current size of trash bin from a given user
1095
+     *
1096
+     * @param string $user user who owns the trash bin
1097
+     * @return integer trash bin size
1098
+     */
1099
+    private static function getTrashbinSize($user) {
1100
+        $view = new View('/' . $user);
1101
+        $fileInfo = $view->getFileInfo('/files_trashbin');
1102
+        return isset($fileInfo['size']) ? $fileInfo['size'] : 0;
1103
+    }
1104
+
1105
+    /**
1106
+     * check if trash bin is empty for a given user
1107
+     *
1108
+     * @param string $user
1109
+     * @return bool
1110
+     */
1111
+    public static function isEmpty($user) {
1112
+        $view = new View('/' . $user . '/files_trashbin');
1113
+        if ($view->is_dir('/files') && $dh = $view->opendir('/files')) {
1114
+            while ($file = readdir($dh)) {
1115
+                if (!Filesystem::isIgnoredDir($file)) {
1116
+                    return false;
1117
+                }
1118
+            }
1119
+        }
1120
+        return true;
1121
+    }
1122
+
1123
+    /**
1124
+     * @param $path
1125
+     * @return string
1126
+     */
1127
+    public static function preview_icon($path) {
1128
+        return \OC::$server->getURLGenerator()->linkToRoute('core_ajax_trashbin_preview', ['x' => 32, 'y' => 32, 'file' => $path]);
1129
+    }
1130 1130
 }
Please login to merge, or discard this patch.
Spacing   +73 added lines, -73 removed lines patch added patch discarded remove patch
@@ -112,7 +112,7 @@  discard block
 block discarded – undo
112 112
 		Filesystem::initMountPoints($uid);
113 113
 		if ($uid !== OC_User::getUser()) {
114 114
 			$info = Filesystem::getFileInfo($filename);
115
-			$ownerView = new View('/' . $uid . '/files');
115
+			$ownerView = new View('/'.$uid.'/files');
116 116
 			try {
117 117
 				$filename = $ownerView->getPath($info['fileid']);
118 118
 			} catch (NotFoundException $e) {
@@ -174,7 +174,7 @@  discard block
 block discarded – undo
174 174
 	}
175 175
 
176 176
 	private static function setUpTrash($user) {
177
-		$view = new View('/' . $user);
177
+		$view = new View('/'.$user);
178 178
 		if (!$view->is_dir('files_trashbin')) {
179 179
 			$view->mkdir('files_trashbin');
180 180
 		}
@@ -209,8 +209,8 @@  discard block
 block discarded – undo
209 209
 
210 210
 		$view = new View('/');
211 211
 
212
-		$target = $user . '/files_trashbin/files/' . $targetFilename . '.d' . $timestamp;
213
-		$source = $owner . '/files_trashbin/files/' . $sourceFilename . '.d' . $timestamp;
212
+		$target = $user.'/files_trashbin/files/'.$targetFilename.'.d'.$timestamp;
213
+		$source = $owner.'/files_trashbin/files/'.$sourceFilename.'.d'.$timestamp;
214 214
 		$free = $view->free_space($target);
215 215
 		$isUnknownOrUnlimitedFreeSpace = $free < 0;
216 216
 		$isEnoughFreeSpaceLeft = $view->filesize($source) < $free;
@@ -254,9 +254,9 @@  discard block
 block discarded – undo
254 254
 			$ownerPath = $file_path;
255 255
 		}
256 256
 
257
-		$ownerView = new View('/' . $owner);
257
+		$ownerView = new View('/'.$owner);
258 258
 		// file has been deleted in between
259
-		if (is_null($ownerPath) || $ownerPath === '' || !$ownerView->file_exists('/files/' . $ownerPath)) {
259
+		if (is_null($ownerPath) || $ownerPath === '' || !$ownerView->file_exists('/files/'.$ownerPath)) {
260 260
 			return true;
261 261
 		}
262 262
 
@@ -277,7 +277,7 @@  discard block
 block discarded – undo
277 277
 		$lockingProvider = \OC::$server->getLockingProvider();
278 278
 
279 279
 		// disable proxy to prevent recursive calls
280
-		$trashPath = '/files_trashbin/files/' . $filename . '.d' . $timestamp;
280
+		$trashPath = '/files_trashbin/files/'.$filename.'.d'.$timestamp;
281 281
 		$gotLock = false;
282 282
 
283 283
 		while (!$gotLock) {
@@ -293,12 +293,12 @@  discard block
 block discarded – undo
293 293
 
294 294
 				$timestamp = $timestamp + 1;
295 295
 
296
-				$trashPath = '/files_trashbin/files/' . $filename . '.d' . $timestamp;
296
+				$trashPath = '/files_trashbin/files/'.$filename.'.d'.$timestamp;
297 297
 			}
298 298
 		}
299 299
 
300 300
 		/** @var \OC\Files\Storage\Storage $sourceStorage */
301
-		[$sourceStorage, $sourceInternalPath] = $ownerView->resolvePath('/files/' . $ownerPath);
301
+		[$sourceStorage, $sourceInternalPath] = $ownerView->resolvePath('/files/'.$ownerPath);
302 302
 
303 303
 
304 304
 		if ($trashStorage->file_exists($trashInternalPath)) {
@@ -306,8 +306,8 @@  discard block
 block discarded – undo
306 306
 		}
307 307
 
308 308
 		$config = \OC::$server->getConfig();
309
-		$systemTrashbinSize = (int)$config->getAppValue('files_trashbin', 'trashbin_size', '-1');
310
-		$userTrashbinSize = (int)$config->getUserValue($owner, 'files_trashbin', 'trashbin_size', '-1');
309
+		$systemTrashbinSize = (int) $config->getAppValue('files_trashbin', 'trashbin_size', '-1');
310
+		$userTrashbinSize = (int) $config->getUserValue($owner, 'files_trashbin', 'trashbin_size', '-1');
311 311
 		$configuredTrashbinSize = ($userTrashbinSize < 0) ? $systemTrashbinSize : $userTrashbinSize;
312 312
 		if ($configuredTrashbinSize >= 0 && $sourceStorage->filesize($sourceInternalPath) >= $configuredTrashbinSize) {
313 313
 			return false;
@@ -327,7 +327,7 @@  discard block
 block discarded – undo
327 327
 			if ($trashStorage->file_exists($trashInternalPath)) {
328 328
 				$trashStorage->unlink($trashInternalPath);
329 329
 			}
330
-			\OC::$server->getLogger()->error('Couldn\'t move ' . $file_path . ' to the trash bin', ['app' => 'files_trashbin']);
330
+			\OC::$server->getLogger()->error('Couldn\'t move '.$file_path.' to the trash bin', ['app' => 'files_trashbin']);
331 331
 		}
332 332
 
333 333
 		if ($sourceStorage->file_exists($sourceInternalPath)) { // failed to delete the original file, abort
@@ -358,7 +358,7 @@  discard block
 block discarded – undo
358 358
 				\OC::$server->getLogger()->error('trash bin database couldn\'t be updated', ['app' => 'files_trashbin']);
359 359
 			}
360 360
 			\OCP\Util::emitHook('\OCA\Files_Trashbin\Trashbin', 'post_moveToTrash', ['filePath' => Filesystem::normalizePath($file_path),
361
-				'trashPath' => Filesystem::normalizePath($filename . '.d' . $timestamp)]);
361
+				'trashPath' => Filesystem::normalizePath($filename.'.d'.$timestamp)]);
362 362
 
363 363
 			self::retainVersions($filename, $owner, $ownerPath, $timestamp);
364 364
 
@@ -393,17 +393,17 @@  discard block
 block discarded – undo
393 393
 			$user = OC_User::getUser();
394 394
 			$rootView = new View('/');
395 395
 
396
-			if ($rootView->is_dir($owner . '/files_versions/' . $ownerPath)) {
396
+			if ($rootView->is_dir($owner.'/files_versions/'.$ownerPath)) {
397 397
 				if ($owner !== $user) {
398
-					self::copy_recursive($owner . '/files_versions/' . $ownerPath, $owner . '/files_trashbin/versions/' . basename($ownerPath) . '.d' . $timestamp, $rootView);
398
+					self::copy_recursive($owner.'/files_versions/'.$ownerPath, $owner.'/files_trashbin/versions/'.basename($ownerPath).'.d'.$timestamp, $rootView);
399 399
 				}
400
-				self::move($rootView, $owner . '/files_versions/' . $ownerPath, $user . '/files_trashbin/versions/' . $filename . '.d' . $timestamp);
400
+				self::move($rootView, $owner.'/files_versions/'.$ownerPath, $user.'/files_trashbin/versions/'.$filename.'.d'.$timestamp);
401 401
 			} elseif ($versions = \OCA\Files_Versions\Storage::getVersions($owner, $ownerPath)) {
402 402
 				foreach ($versions as $v) {
403 403
 					if ($owner !== $user) {
404
-						self::copy($rootView, $owner . '/files_versions' . $v['path'] . '.v' . $v['version'], $owner . '/files_trashbin/versions/' . $v['name'] . '.v' . $v['version'] . '.d' . $timestamp);
404
+						self::copy($rootView, $owner.'/files_versions'.$v['path'].'.v'.$v['version'], $owner.'/files_trashbin/versions/'.$v['name'].'.v'.$v['version'].'.d'.$timestamp);
405 405
 					}
406
-					self::move($rootView, $owner . '/files_versions' . $v['path'] . '.v' . $v['version'], $user . '/files_trashbin/versions/' . $filename . '.v' . $v['version'] . '.d' . $timestamp);
406
+					self::move($rootView, $owner.'/files_versions'.$v['path'].'.v'.$v['version'], $user.'/files_trashbin/versions/'.$filename.'.v'.$v['version'].'.d'.$timestamp);
407 407
 				}
408 408
 			}
409 409
 		}
@@ -465,18 +465,18 @@  discard block
 block discarded – undo
465 465
 	 */
466 466
 	public static function restore($file, $filename, $timestamp) {
467 467
 		$user = OC_User::getUser();
468
-		$view = new View('/' . $user);
468
+		$view = new View('/'.$user);
469 469
 
470 470
 		$location = '';
471 471
 		if ($timestamp) {
472 472
 			$location = self::getLocation($user, $filename, $timestamp);
473 473
 			if ($location === false) {
474
-				\OC::$server->getLogger()->error('trash bin database inconsistent! ($user: ' . $user . ' $filename: ' . $filename . ', $timestamp: ' . $timestamp . ')', ['app' => 'files_trashbin']);
474
+				\OC::$server->getLogger()->error('trash bin database inconsistent! ($user: '.$user.' $filename: '.$filename.', $timestamp: '.$timestamp.')', ['app' => 'files_trashbin']);
475 475
 			} else {
476 476
 				// if location no longer exists, restore file in the root directory
477 477
 				if ($location !== '/' &&
478
-					(!$view->is_dir('files/' . $location) ||
479
-						!$view->isCreatable('files/' . $location))
478
+					(!$view->is_dir('files/'.$location) ||
479
+						!$view->isCreatable('files/'.$location))
480 480
 				) {
481 481
 					$location = '';
482 482
 				}
@@ -486,8 +486,8 @@  discard block
 block discarded – undo
486 486
 		// we need a  extension in case a file/dir with the same name already exists
487 487
 		$uniqueFilename = self::getUniqueFilename($location, $filename, $view);
488 488
 
489
-		$source = Filesystem::normalizePath('files_trashbin/files/' . $file);
490
-		$target = Filesystem::normalizePath('files/' . $location . '/' . $uniqueFilename);
489
+		$source = Filesystem::normalizePath('files_trashbin/files/'.$file);
490
+		$target = Filesystem::normalizePath('files/'.$location.'/'.$uniqueFilename);
491 491
 		if (!$view->file_exists($source)) {
492 492
 			return false;
493 493
 		}
@@ -502,10 +502,10 @@  discard block
 block discarded – undo
502 502
 		// handle the restore result
503 503
 		if ($restoreResult) {
504 504
 			$fakeRoot = $view->getRoot();
505
-			$view->chroot('/' . $user . '/files');
506
-			$view->touch('/' . $location . '/' . $uniqueFilename, $mtime);
505
+			$view->chroot('/'.$user.'/files');
506
+			$view->touch('/'.$location.'/'.$uniqueFilename, $mtime);
507 507
 			$view->chroot($fakeRoot);
508
-			\OCP\Util::emitHook('\OCA\Files_Trashbin\Trashbin', 'post_restore', ['filePath' => Filesystem::normalizePath('/' . $location . '/' . $uniqueFilename),
508
+			\OCP\Util::emitHook('\OCA\Files_Trashbin\Trashbin', 'post_restore', ['filePath' => Filesystem::normalizePath('/'.$location.'/'.$uniqueFilename),
509 509
 				'trashPath' => Filesystem::normalizePath($file)]);
510 510
 
511 511
 			self::restoreVersions($view, $file, $filename, $uniqueFilename, $location, $timestamp);
@@ -541,7 +541,7 @@  discard block
 block discarded – undo
541 541
 			$user = OC_User::getUser();
542 542
 			$rootView = new View('/');
543 543
 
544
-			$target = Filesystem::normalizePath('/' . $location . '/' . $uniqueFilename);
544
+			$target = Filesystem::normalizePath('/'.$location.'/'.$uniqueFilename);
545 545
 
546 546
 			[$owner, $ownerPath] = self::getUidAndFilename($target);
547 547
 
@@ -556,14 +556,14 @@  discard block
 block discarded – undo
556 556
 				$versionedFile = $file;
557 557
 			}
558 558
 
559
-			if ($view->is_dir('/files_trashbin/versions/' . $file)) {
560
-				$rootView->rename(Filesystem::normalizePath($user . '/files_trashbin/versions/' . $file), Filesystem::normalizePath($owner . '/files_versions/' . $ownerPath));
559
+			if ($view->is_dir('/files_trashbin/versions/'.$file)) {
560
+				$rootView->rename(Filesystem::normalizePath($user.'/files_trashbin/versions/'.$file), Filesystem::normalizePath($owner.'/files_versions/'.$ownerPath));
561 561
 			} elseif ($versions = self::getVersionsFromTrash($versionedFile, $timestamp, $user)) {
562 562
 				foreach ($versions as $v) {
563 563
 					if ($timestamp) {
564
-						$rootView->rename($user . '/files_trashbin/versions/' . $versionedFile . '.v' . $v . '.d' . $timestamp, $owner . '/files_versions/' . $ownerPath . '.v' . $v);
564
+						$rootView->rename($user.'/files_trashbin/versions/'.$versionedFile.'.v'.$v.'.d'.$timestamp, $owner.'/files_versions/'.$ownerPath.'.v'.$v);
565 565
 					} else {
566
-						$rootView->rename($user . '/files_trashbin/versions/' . $versionedFile . '.v' . $v, $owner . '/files_versions/' . $ownerPath . '.v' . $v);
566
+						$rootView->rename($user.'/files_trashbin/versions/'.$versionedFile.'.v'.$v, $owner.'/files_versions/'.$ownerPath.'.v'.$v);
567 567
 					}
568 568
 				}
569 569
 			}
@@ -576,7 +576,7 @@  discard block
 block discarded – undo
576 576
 	public static function deleteAll() {
577 577
 		$user = OC_User::getUser();
578 578
 		$userRoot = \OC::$server->getUserFolder($user)->getParent();
579
-		$view = new View('/' . $user);
579
+		$view = new View('/'.$user);
580 580
 		$fileInfos = $view->getDirectoryContent('files_trashbin/files');
581 581
 
582 582
 		try {
@@ -651,7 +651,7 @@  discard block
 block discarded – undo
651 651
 	 */
652 652
 	public static function delete($filename, $user, $timestamp = null) {
653 653
 		$userRoot = \OC::$server->getUserFolder($user)->getParent();
654
-		$view = new View('/' . $user);
654
+		$view = new View('/'.$user);
655 655
 		$size = 0;
656 656
 
657 657
 		if ($timestamp) {
@@ -662,7 +662,7 @@  discard block
 block discarded – undo
662 662
 				->andWhere($query->expr()->eq('timestamp', $query->createNamedParameter($timestamp)));
663 663
 			$query->executeStatement();
664 664
 
665
-			$file = $filename . '.d' . $timestamp;
665
+			$file = $filename.'.d'.$timestamp;
666 666
 		} else {
667 667
 			$file = $filename;
668 668
 		}
@@ -670,20 +670,20 @@  discard block
 block discarded – undo
670 670
 		$size += self::deleteVersions($view, $file, $filename, $timestamp, $user);
671 671
 
672 672
 		try {
673
-			$node = $userRoot->get('/files_trashbin/files/' . $file);
673
+			$node = $userRoot->get('/files_trashbin/files/'.$file);
674 674
 		} catch (NotFoundException $e) {
675 675
 			return $size;
676 676
 		}
677 677
 
678 678
 		if ($node instanceof Folder) {
679
-			$size += self::calculateSize(new View('/' . $user . '/files_trashbin/files/' . $file));
679
+			$size += self::calculateSize(new View('/'.$user.'/files_trashbin/files/'.$file));
680 680
 		} elseif ($node instanceof File) {
681
-			$size += $view->filesize('/files_trashbin/files/' . $file);
681
+			$size += $view->filesize('/files_trashbin/files/'.$file);
682 682
 		}
683 683
 
684
-		self::emitTrashbinPreDelete('/files_trashbin/files/' . $file);
684
+		self::emitTrashbinPreDelete('/files_trashbin/files/'.$file);
685 685
 		$node->delete();
686
-		self::emitTrashbinPostDelete('/files_trashbin/files/' . $file);
686
+		self::emitTrashbinPostDelete('/files_trashbin/files/'.$file);
687 687
 
688 688
 		return $size;
689 689
 	}
@@ -699,17 +699,17 @@  discard block
 block discarded – undo
699 699
 	private static function deleteVersions(View $view, $file, $filename, $timestamp, $user) {
700 700
 		$size = 0;
701 701
 		if (\OCP\App::isEnabled('files_versions')) {
702
-			if ($view->is_dir('files_trashbin/versions/' . $file)) {
703
-				$size += self::calculateSize(new View('/' . $user . '/files_trashbin/versions/' . $file));
704
-				$view->unlink('files_trashbin/versions/' . $file);
702
+			if ($view->is_dir('files_trashbin/versions/'.$file)) {
703
+				$size += self::calculateSize(new View('/'.$user.'/files_trashbin/versions/'.$file));
704
+				$view->unlink('files_trashbin/versions/'.$file);
705 705
 			} elseif ($versions = self::getVersionsFromTrash($filename, $timestamp, $user)) {
706 706
 				foreach ($versions as $v) {
707 707
 					if ($timestamp) {
708
-						$size += $view->filesize('/files_trashbin/versions/' . $filename . '.v' . $v . '.d' . $timestamp);
709
-						$view->unlink('/files_trashbin/versions/' . $filename . '.v' . $v . '.d' . $timestamp);
708
+						$size += $view->filesize('/files_trashbin/versions/'.$filename.'.v'.$v.'.d'.$timestamp);
709
+						$view->unlink('/files_trashbin/versions/'.$filename.'.v'.$v.'.d'.$timestamp);
710 710
 					} else {
711
-						$size += $view->filesize('/files_trashbin/versions/' . $filename . '.v' . $v);
712
-						$view->unlink('/files_trashbin/versions/' . $filename . '.v' . $v);
711
+						$size += $view->filesize('/files_trashbin/versions/'.$filename.'.v'.$v);
712
+						$view->unlink('/files_trashbin/versions/'.$filename.'.v'.$v);
713 713
 					}
714 714
 				}
715 715
 			}
@@ -726,13 +726,13 @@  discard block
 block discarded – undo
726 726
 	 */
727 727
 	public static function file_exists($filename, $timestamp = null) {
728 728
 		$user = OC_User::getUser();
729
-		$view = new View('/' . $user);
729
+		$view = new View('/'.$user);
730 730
 
731 731
 		if ($timestamp) {
732
-			$filename = $filename . '.d' . $timestamp;
732
+			$filename = $filename.'.d'.$timestamp;
733 733
 		}
734 734
 
735
-		$target = Filesystem::normalizePath('files_trashbin/files/' . $filename);
735
+		$target = Filesystem::normalizePath('files_trashbin/files/'.$filename);
736 736
 		return $view->file_exists($target);
737 737
 	}
738 738
 
@@ -758,11 +758,11 @@  discard block
 block discarded – undo
758 758
 	 */
759 759
 	private static function calculateFreeSpace($trashbinSize, $user) {
760 760
 		$config = \OC::$server->getConfig();
761
-		$userTrashbinSize = (int)$config->getUserValue($user, 'files_trashbin', 'trashbin_size', '-1');
761
+		$userTrashbinSize = (int) $config->getUserValue($user, 'files_trashbin', 'trashbin_size', '-1');
762 762
 		if ($userTrashbinSize > -1) {
763 763
 			return $userTrashbinSize - $trashbinSize;
764 764
 		}
765
-		$systemTrashbinSize = (int)$config->getAppValue('files_trashbin', 'trashbin_size', '-1');
765
+		$systemTrashbinSize = (int) $config->getAppValue('files_trashbin', 'trashbin_size', '-1');
766 766
 		if ($systemTrashbinSize > -1) {
767 767
 			return $systemTrashbinSize - $trashbinSize;
768 768
 		}
@@ -871,7 +871,7 @@  discard block
 block discarded – undo
871 871
 			foreach ($files as $file) {
872 872
 				if ($availableSpace < 0 && $expiration->isExpired($file['mtime'], true)) {
873 873
 					$tmp = self::delete($file['name'], $user, $file['mtime']);
874
-					\OC::$server->getLogger()->info('remove "' . $file['name'] . '" (' . $tmp . 'B) to meet the limit of trash bin size (50% of available quota)', ['app' => 'files_trashbin']);
874
+					\OC::$server->getLogger()->info('remove "'.$file['name'].'" ('.$tmp.'B) to meet the limit of trash bin size (50% of available quota)', ['app' => 'files_trashbin']);
875 875
 					$availableSpace += $tmp;
876 876
 					$size += $tmp;
877 877
 				} else {
@@ -902,10 +902,10 @@  discard block
 block discarded – undo
902 902
 					$size += self::delete($filename, $user, $timestamp);
903 903
 					$count++;
904 904
 				} catch (\OCP\Files\NotPermittedException $e) {
905
-					\OC::$server->getLogger()->logException($e, ['app' => 'files_trashbin', 'level' => \OCP\ILogger::WARN, 'message' => 'Removing "' . $filename . '" from trashbin failed.']);
905
+					\OC::$server->getLogger()->logException($e, ['app' => 'files_trashbin', 'level' => \OCP\ILogger::WARN, 'message' => 'Removing "'.$filename.'" from trashbin failed.']);
906 906
 				}
907 907
 				\OC::$server->getLogger()->info(
908
-					'Remove "' . $filename . '" from trashbin because it exceeds max retention obligation term.',
908
+					'Remove "'.$filename.'" from trashbin because it exceeds max retention obligation term.',
909 909
 					['app' => 'files_trashbin']
910 910
 				);
911 911
 			} else {
@@ -931,16 +931,16 @@  discard block
 block discarded – undo
931 931
 			$view->mkdir($destination);
932 932
 			$view->touch($destination, $view->filemtime($source));
933 933
 			foreach ($view->getDirectoryContent($source) as $i) {
934
-				$pathDir = $source . '/' . $i['name'];
934
+				$pathDir = $source.'/'.$i['name'];
935 935
 				if ($view->is_dir($pathDir)) {
936
-					$size += self::copy_recursive($pathDir, $destination . '/' . $i['name'], $view);
936
+					$size += self::copy_recursive($pathDir, $destination.'/'.$i['name'], $view);
937 937
 				} else {
938 938
 					$size += $view->filesize($pathDir);
939
-					$result = $view->copy($pathDir, $destination . '/' . $i['name']);
939
+					$result = $view->copy($pathDir, $destination.'/'.$i['name']);
940 940
 					if (!$result) {
941 941
 						throw new \OCA\Files_Trashbin\Exceptions\CopyRecursiveException();
942 942
 					}
943
-					$view->touch($destination . '/' . $i['name'], $view->filemtime($pathDir));
943
+					$view->touch($destination.'/'.$i['name'], $view->filemtime($pathDir));
944 944
 				}
945 945
 			}
946 946
 		} else {
@@ -962,11 +962,11 @@  discard block
 block discarded – undo
962 962
 	 * @return array
963 963
 	 */
964 964
 	private static function getVersionsFromTrash($filename, $timestamp, $user) {
965
-		$view = new View('/' . $user . '/files_trashbin/versions');
965
+		$view = new View('/'.$user.'/files_trashbin/versions');
966 966
 		$versions = [];
967 967
 
968 968
 		/** @var \OC\Files\Storage\Storage $storage */
969
-		[$storage,] = $view->resolvePath('/');
969
+		[$storage, ] = $view->resolvePath('/');
970 970
 
971 971
 		//force rescan of versions, local storage may not have updated the cache
972 972
 		if (!self::$scannedVersions) {
@@ -978,7 +978,7 @@  discard block
 block discarded – undo
978 978
 		if ($timestamp) {
979 979
 			// fetch for old versions
980 980
 			$escapedTimestamp = \OC::$server->getDatabaseConnection()->escapeLikeParameter($timestamp);
981
-			$pattern .= '.v%.d' . $escapedTimestamp;
981
+			$pattern .= '.v%.d'.$escapedTimestamp;
982 982
 			$offset = -strlen($escapedTimestamp) - 2;
983 983
 		} else {
984 984
 			$pattern .= '.v%';
@@ -992,7 +992,7 @@  discard block
 block discarded – undo
992 992
 			\OC::$server->getLogger(),
993 993
 			$cache
994 994
 		);
995
-		$normalizedParentPath = ltrim(Filesystem::normalizePath(dirname('files_trashbin/versions/'. $filename)), '/');
995
+		$normalizedParentPath = ltrim(Filesystem::normalizePath(dirname('files_trashbin/versions/'.$filename)), '/');
996 996
 		$parentId = $cache->getId($normalizedParentPath);
997 997
 		if ($parentId === -1) {
998 998
 			return [];
@@ -1008,7 +1008,7 @@  discard block
 block discarded – undo
1008 1008
 		$result->closeCursor();
1009 1009
 
1010 1010
 		/** @var CacheEntry[] $matches */
1011
-		$matches = array_map(function (array $data) {
1011
+		$matches = array_map(function(array $data) {
1012 1012
 			return Cache::cacheEntryFromData($data, \OC::$server->getMimeTypeLoader());
1013 1013
 		}, $entries);
1014 1014
 
@@ -1038,18 +1038,18 @@  discard block
 block discarded – undo
1038 1038
 		$name = pathinfo($filename, PATHINFO_FILENAME);
1039 1039
 		$l = \OC::$server->getL10N('files_trashbin');
1040 1040
 
1041
-		$location = '/' . trim($location, '/');
1041
+		$location = '/'.trim($location, '/');
1042 1042
 
1043 1043
 		// if extension is not empty we set a dot in front of it
1044 1044
 		if ($ext !== '') {
1045
-			$ext = '.' . $ext;
1045
+			$ext = '.'.$ext;
1046 1046
 		}
1047 1047
 
1048
-		if ($view->file_exists('files' . $location . '/' . $filename)) {
1048
+		if ($view->file_exists('files'.$location.'/'.$filename)) {
1049 1049
 			$i = 2;
1050
-			$uniqueName = $name . " (" . $l->t("restored") . ")" . $ext;
1051
-			while ($view->file_exists('files' . $location . '/' . $uniqueName)) {
1052
-				$uniqueName = $name . " (" . $l->t("restored") . " " . $i . ")" . $ext;
1050
+			$uniqueName = $name." (".$l->t("restored").")".$ext;
1051
+			while ($view->file_exists('files'.$location.'/'.$uniqueName)) {
1052
+				$uniqueName = $name." (".$l->t("restored")." ".$i.")".$ext;
1053 1053
 				$i++;
1054 1054
 			}
1055 1055
 
@@ -1066,7 +1066,7 @@  discard block
 block discarded – undo
1066 1066
 	 * @return integer size of the folder
1067 1067
 	 */
1068 1068
 	private static function calculateSize($view) {
1069
-		$root = \OC::$server->getConfig()->getSystemValue('datadirectory', \OC::$SERVERROOT . '/data') . $view->getAbsolutePath('');
1069
+		$root = \OC::$server->getConfig()->getSystemValue('datadirectory', \OC::$SERVERROOT.'/data').$view->getAbsolutePath('');
1070 1070
 		if (!file_exists($root)) {
1071 1071
 			return 0;
1072 1072
 		}
@@ -1097,7 +1097,7 @@  discard block
 block discarded – undo
1097 1097
 	 * @return integer trash bin size
1098 1098
 	 */
1099 1099
 	private static function getTrashbinSize($user) {
1100
-		$view = new View('/' . $user);
1100
+		$view = new View('/'.$user);
1101 1101
 		$fileInfo = $view->getFileInfo('/files_trashbin');
1102 1102
 		return isset($fileInfo['size']) ? $fileInfo['size'] : 0;
1103 1103
 	}
@@ -1109,7 +1109,7 @@  discard block
 block discarded – undo
1109 1109
 	 * @return bool
1110 1110
 	 */
1111 1111
 	public static function isEmpty($user) {
1112
-		$view = new View('/' . $user . '/files_trashbin');
1112
+		$view = new View('/'.$user.'/files_trashbin');
1113 1113
 		if ($view->is_dir('/files') && $dh = $view->opendir('/files')) {
1114 1114
 			while ($file = readdir($dh)) {
1115 1115
 				if (!Filesystem::isIgnoredDir($file)) {
Please login to merge, or discard this patch.
apps/files_versions/lib/Hooks.php 2 patches
Indentation   +96 added lines, -96 removed lines patch added patch discarded remove patch
@@ -40,108 +40,108 @@
 block discarded – undo
40 40
 use OCP\Util;
41 41
 
42 42
 class Hooks {
43
-	public static function connectHooks() {
44
-		// Listen to write signals
45
-		Util::connectHook('OC_Filesystem', 'write', Hooks::class, 'write_hook');
46
-		// Listen to delete and rename signals
47
-		Util::connectHook('OC_Filesystem', 'post_delete', Hooks::class, 'remove_hook');
48
-		Util::connectHook('OC_Filesystem', 'delete', Hooks::class, 'pre_remove_hook');
49
-		Util::connectHook('OC_Filesystem', 'post_rename', Hooks::class, 'rename_hook');
50
-		Util::connectHook('OC_Filesystem', 'post_copy', Hooks::class, 'copy_hook');
51
-		Util::connectHook('OC_Filesystem', 'rename', Hooks::class, 'pre_renameOrCopy_hook');
52
-		Util::connectHook('OC_Filesystem', 'copy', Hooks::class, 'pre_renameOrCopy_hook');
53
-	}
43
+    public static function connectHooks() {
44
+        // Listen to write signals
45
+        Util::connectHook('OC_Filesystem', 'write', Hooks::class, 'write_hook');
46
+        // Listen to delete and rename signals
47
+        Util::connectHook('OC_Filesystem', 'post_delete', Hooks::class, 'remove_hook');
48
+        Util::connectHook('OC_Filesystem', 'delete', Hooks::class, 'pre_remove_hook');
49
+        Util::connectHook('OC_Filesystem', 'post_rename', Hooks::class, 'rename_hook');
50
+        Util::connectHook('OC_Filesystem', 'post_copy', Hooks::class, 'copy_hook');
51
+        Util::connectHook('OC_Filesystem', 'rename', Hooks::class, 'pre_renameOrCopy_hook');
52
+        Util::connectHook('OC_Filesystem', 'copy', Hooks::class, 'pre_renameOrCopy_hook');
53
+    }
54 54
 
55
-	/**
56
-	 * listen to write event.
57
-	 */
58
-	public static function write_hook($params) {
59
-		$path = $params[Filesystem::signal_param_path];
60
-		if ($path !== '') {
61
-			Storage::store($path);
62
-		}
63
-	}
55
+    /**
56
+     * listen to write event.
57
+     */
58
+    public static function write_hook($params) {
59
+        $path = $params[Filesystem::signal_param_path];
60
+        if ($path !== '') {
61
+            Storage::store($path);
62
+        }
63
+    }
64 64
 
65 65
 
66
-	/**
67
-	 * Erase versions of deleted file
68
-	 * @param array $params
69
-	 *
70
-	 * This function is connected to the delete signal of OC_Filesystem
71
-	 * cleanup the versions directory if the actual file gets deleted
72
-	 */
73
-	public static function remove_hook($params) {
74
-		$path = $params[Filesystem::signal_param_path];
75
-		if ($path !== '') {
76
-			Storage::delete($path);
77
-		}
78
-	}
66
+    /**
67
+     * Erase versions of deleted file
68
+     * @param array $params
69
+     *
70
+     * This function is connected to the delete signal of OC_Filesystem
71
+     * cleanup the versions directory if the actual file gets deleted
72
+     */
73
+    public static function remove_hook($params) {
74
+        $path = $params[Filesystem::signal_param_path];
75
+        if ($path !== '') {
76
+            Storage::delete($path);
77
+        }
78
+    }
79 79
 
80
-	/**
81
-	 * mark file as "deleted" so that we can clean up the versions if the file is gone
82
-	 * @param array $params
83
-	 */
84
-	public static function pre_remove_hook($params) {
85
-		$path = $params[Filesystem::signal_param_path];
86
-		if ($path !== '') {
87
-			Storage::markDeletedFile($path);
88
-		}
89
-	}
80
+    /**
81
+     * mark file as "deleted" so that we can clean up the versions if the file is gone
82
+     * @param array $params
83
+     */
84
+    public static function pre_remove_hook($params) {
85
+        $path = $params[Filesystem::signal_param_path];
86
+        if ($path !== '') {
87
+            Storage::markDeletedFile($path);
88
+        }
89
+    }
90 90
 
91
-	/**
92
-	 * rename/move versions of renamed/moved files
93
-	 * @param array $params array with oldpath and newpath
94
-	 *
95
-	 * This function is connected to the rename signal of OC_Filesystem and adjust the name and location
96
-	 * of the stored versions along the actual file
97
-	 */
98
-	public static function rename_hook($params) {
99
-		$oldpath = $params['oldpath'];
100
-		$newpath = $params['newpath'];
101
-		if ($oldpath !== '' && $newpath !== '') {
102
-			Storage::renameOrCopy($oldpath, $newpath, 'rename');
103
-		}
104
-	}
91
+    /**
92
+     * rename/move versions of renamed/moved files
93
+     * @param array $params array with oldpath and newpath
94
+     *
95
+     * This function is connected to the rename signal of OC_Filesystem and adjust the name and location
96
+     * of the stored versions along the actual file
97
+     */
98
+    public static function rename_hook($params) {
99
+        $oldpath = $params['oldpath'];
100
+        $newpath = $params['newpath'];
101
+        if ($oldpath !== '' && $newpath !== '') {
102
+            Storage::renameOrCopy($oldpath, $newpath, 'rename');
103
+        }
104
+    }
105 105
 
106
-	/**
107
-	 * copy versions of copied files
108
-	 * @param array $params array with oldpath and newpath
109
-	 *
110
-	 * This function is connected to the copy signal of OC_Filesystem and copies the
111
-	 * the stored versions to the new location
112
-	 */
113
-	public static function copy_hook($params) {
114
-		$oldpath = $params['oldpath'];
115
-		$newpath = $params['newpath'];
116
-		if ($oldpath !== '' && $newpath !== '') {
117
-			Storage::renameOrCopy($oldpath, $newpath, 'copy');
118
-		}
119
-	}
106
+    /**
107
+     * copy versions of copied files
108
+     * @param array $params array with oldpath and newpath
109
+     *
110
+     * This function is connected to the copy signal of OC_Filesystem and copies the
111
+     * the stored versions to the new location
112
+     */
113
+    public static function copy_hook($params) {
114
+        $oldpath = $params['oldpath'];
115
+        $newpath = $params['newpath'];
116
+        if ($oldpath !== '' && $newpath !== '') {
117
+            Storage::renameOrCopy($oldpath, $newpath, 'copy');
118
+        }
119
+    }
120 120
 
121
-	/**
122
-	 * Remember owner and the owner path of the source file.
123
-	 * If the file already exists, then it was a upload of a existing file
124
-	 * over the web interface and we call Storage::store() directly
125
-	 *
126
-	 * @param array $params array with oldpath and newpath
127
-	 *
128
-	 */
129
-	public static function pre_renameOrCopy_hook($params) {
130
-		// if we rename a movable mount point, then the versions don't have
131
-		// to be renamed
132
-		$absOldPath = Filesystem::normalizePath('/' . \OC_User::getUser() . '/files' . $params['oldpath']);
133
-		$manager = Filesystem::getMountManager();
134
-		$mount = $manager->find($absOldPath);
135
-		$internalPath = $mount->getInternalPath($absOldPath);
136
-		if ($internalPath === '' and $mount instanceof MoveableMount) {
137
-			return;
138
-		}
121
+    /**
122
+     * Remember owner and the owner path of the source file.
123
+     * If the file already exists, then it was a upload of a existing file
124
+     * over the web interface and we call Storage::store() directly
125
+     *
126
+     * @param array $params array with oldpath and newpath
127
+     *
128
+     */
129
+    public static function pre_renameOrCopy_hook($params) {
130
+        // if we rename a movable mount point, then the versions don't have
131
+        // to be renamed
132
+        $absOldPath = Filesystem::normalizePath('/' . \OC_User::getUser() . '/files' . $params['oldpath']);
133
+        $manager = Filesystem::getMountManager();
134
+        $mount = $manager->find($absOldPath);
135
+        $internalPath = $mount->getInternalPath($absOldPath);
136
+        if ($internalPath === '' and $mount instanceof MoveableMount) {
137
+            return;
138
+        }
139 139
 
140
-		$view = new View(\OC_User::getUser() . '/files');
141
-		if ($view->file_exists($params['newpath'])) {
142
-			Storage::store($params['newpath']);
143
-		} else {
144
-			Storage::setSourcePathAndUser($params['oldpath']);
145
-		}
146
-	}
140
+        $view = new View(\OC_User::getUser() . '/files');
141
+        if ($view->file_exists($params['newpath'])) {
142
+            Storage::store($params['newpath']);
143
+        } else {
144
+            Storage::setSourcePathAndUser($params['oldpath']);
145
+        }
146
+    }
147 147
 }
Please login to merge, or discard this patch.
Spacing   +2 added lines, -2 removed lines patch added patch discarded remove patch
@@ -129,7 +129,7 @@  discard block
 block discarded – undo
129 129
 	public static function pre_renameOrCopy_hook($params) {
130 130
 		// if we rename a movable mount point, then the versions don't have
131 131
 		// to be renamed
132
-		$absOldPath = Filesystem::normalizePath('/' . \OC_User::getUser() . '/files' . $params['oldpath']);
132
+		$absOldPath = Filesystem::normalizePath('/'.\OC_User::getUser().'/files'.$params['oldpath']);
133 133
 		$manager = Filesystem::getMountManager();
134 134
 		$mount = $manager->find($absOldPath);
135 135
 		$internalPath = $mount->getInternalPath($absOldPath);
@@ -137,7 +137,7 @@  discard block
 block discarded – undo
137 137
 			return;
138 138
 		}
139 139
 
140
-		$view = new View(\OC_User::getUser() . '/files');
140
+		$view = new View(\OC_User::getUser().'/files');
141 141
 		if ($view->file_exists($params['newpath'])) {
142 142
 			Storage::store($params['newpath']);
143 143
 		} else {
Please login to merge, or discard this patch.
apps/files_versions/lib/Storage.php 1 patch
Indentation   +803 added lines, -803 removed lines patch added patch discarded remove patch
@@ -59,807 +59,807 @@
 block discarded – undo
59 59
 use OCP\Lock\ILockingProvider;
60 60
 
61 61
 class Storage {
62
-	public const DEFAULTENABLED = true;
63
-	public const DEFAULTMAXSIZE = 50; // unit: percentage; 50% of available disk space/quota
64
-	public const VERSIONS_ROOT = 'files_versions/';
65
-
66
-	public const DELETE_TRIGGER_MASTER_REMOVED = 0;
67
-	public const DELETE_TRIGGER_RETENTION_CONSTRAINT = 1;
68
-	public const DELETE_TRIGGER_QUOTA_EXCEEDED = 2;
69
-
70
-	// files for which we can remove the versions after the delete operation was successful
71
-	private static $deletedFiles = [];
72
-
73
-	private static $sourcePathAndUser = [];
74
-
75
-	private static $max_versions_per_interval = [
76
-		//first 10sec, one version every 2sec
77
-		1 => ['intervalEndsAfter' => 10,      'step' => 2],
78
-		//next minute, one version every 10sec
79
-		2 => ['intervalEndsAfter' => 60,      'step' => 10],
80
-		//next hour, one version every minute
81
-		3 => ['intervalEndsAfter' => 3600,    'step' => 60],
82
-		//next 24h, one version every hour
83
-		4 => ['intervalEndsAfter' => 86400,   'step' => 3600],
84
-		//next 30days, one version per day
85
-		5 => ['intervalEndsAfter' => 2592000, 'step' => 86400],
86
-		//until the end one version per week
87
-		6 => ['intervalEndsAfter' => -1,      'step' => 604800],
88
-	];
89
-
90
-	/** @var \OCA\Files_Versions\AppInfo\Application */
91
-	private static $application;
92
-
93
-	/**
94
-	 * get the UID of the owner of the file and the path to the file relative to
95
-	 * owners files folder
96
-	 *
97
-	 * @param string $filename
98
-	 * @return array
99
-	 * @throws \OC\User\NoUserException
100
-	 */
101
-	public static function getUidAndFilename($filename) {
102
-		$uid = Filesystem::getOwner($filename);
103
-		$userManager = \OC::$server->getUserManager();
104
-		// if the user with the UID doesn't exists, e.g. because the UID points
105
-		// to a remote user with a federated cloud ID we use the current logged-in
106
-		// user. We need a valid local user to create the versions
107
-		if (!$userManager->userExists($uid)) {
108
-			$uid = OC_User::getUser();
109
-		}
110
-		Filesystem::initMountPoints($uid);
111
-		if ($uid !== OC_User::getUser()) {
112
-			$info = Filesystem::getFileInfo($filename);
113
-			$ownerView = new View('/'.$uid.'/files');
114
-			try {
115
-				$filename = $ownerView->getPath($info['fileid']);
116
-				// make sure that the file name doesn't end with a trailing slash
117
-				// can for example happen single files shared across servers
118
-				$filename = rtrim($filename, '/');
119
-			} catch (NotFoundException $e) {
120
-				$filename = null;
121
-			}
122
-		}
123
-		return [$uid, $filename];
124
-	}
125
-
126
-	/**
127
-	 * Remember the owner and the owner path of the source file
128
-	 *
129
-	 * @param string $source source path
130
-	 */
131
-	public static function setSourcePathAndUser($source) {
132
-		[$uid, $path] = self::getUidAndFilename($source);
133
-		self::$sourcePathAndUser[$source] = ['uid' => $uid, 'path' => $path];
134
-	}
135
-
136
-	/**
137
-	 * Gets the owner and the owner path from the source path
138
-	 *
139
-	 * @param string $source source path
140
-	 * @return array with user id and path
141
-	 */
142
-	public static function getSourcePathAndUser($source) {
143
-		if (isset(self::$sourcePathAndUser[$source])) {
144
-			$uid = self::$sourcePathAndUser[$source]['uid'];
145
-			$path = self::$sourcePathAndUser[$source]['path'];
146
-			unset(self::$sourcePathAndUser[$source]);
147
-		} else {
148
-			$uid = $path = false;
149
-		}
150
-		return [$uid, $path];
151
-	}
152
-
153
-	/**
154
-	 * get current size of all versions from a given user
155
-	 *
156
-	 * @param string $user user who owns the versions
157
-	 * @return int versions size
158
-	 */
159
-	private static function getVersionsSize($user) {
160
-		$view = new View('/' . $user);
161
-		$fileInfo = $view->getFileInfo('/files_versions');
162
-		return isset($fileInfo['size']) ? $fileInfo['size'] : 0;
163
-	}
164
-
165
-	/**
166
-	 * store a new version of a file.
167
-	 */
168
-	public static function store($filename) {
169
-
170
-		// if the file gets streamed we need to remove the .part extension
171
-		// to get the right target
172
-		$ext = pathinfo($filename, PATHINFO_EXTENSION);
173
-		if ($ext === 'part') {
174
-			$filename = substr($filename, 0, -5);
175
-		}
176
-
177
-		// we only handle existing files
178
-		if (! Filesystem::file_exists($filename) || Filesystem::is_dir($filename)) {
179
-			return false;
180
-		}
181
-
182
-		[$uid, $filename] = self::getUidAndFilename($filename);
183
-
184
-		$files_view = new View('/'.$uid .'/files');
185
-
186
-		$eventDispatcher = \OC::$server->getEventDispatcher();
187
-		$fileInfo = $files_view->getFileInfo($filename);
188
-		$id = $fileInfo->getId();
189
-		$nodes = \OC::$server->getRootFolder()->getUserFolder($uid)->getById($id);
190
-		foreach ($nodes as $node) {
191
-			$event = new CreateVersionEvent($node);
192
-			$eventDispatcher->dispatch('OCA\Files_Versions::createVersion', $event);
193
-			if ($event->shouldCreateVersion() === false) {
194
-				return false;
195
-			}
196
-		}
197
-
198
-		// no use making versions for empty files
199
-		if ($fileInfo->getSize() === 0) {
200
-			return false;
201
-		}
202
-
203
-		/** @var IVersionManager $versionManager */
204
-		$versionManager = \OC::$server->query(IVersionManager::class);
205
-		$userManager = \OC::$server->getUserManager();
206
-		$user = $userManager->get($uid);
207
-
208
-		$versionManager->createVersion($user, $fileInfo);
209
-	}
210
-
211
-
212
-	/**
213
-	 * mark file as deleted so that we can remove the versions if the file is gone
214
-	 * @param string $path
215
-	 */
216
-	public static function markDeletedFile($path) {
217
-		[$uid, $filename] = self::getUidAndFilename($path);
218
-		self::$deletedFiles[$path] = [
219
-			'uid' => $uid,
220
-			'filename' => $filename];
221
-	}
222
-
223
-	/**
224
-	 * delete the version from the storage and cache
225
-	 *
226
-	 * @param View $view
227
-	 * @param string $path
228
-	 */
229
-	protected static function deleteVersion($view, $path) {
230
-		$view->unlink($path);
231
-		/**
232
-		 * @var \OC\Files\Storage\Storage $storage
233
-		 * @var string $internalPath
234
-		 */
235
-		[$storage, $internalPath] = $view->resolvePath($path);
236
-		$cache = $storage->getCache($internalPath);
237
-		$cache->remove($internalPath);
238
-	}
239
-
240
-	/**
241
-	 * Delete versions of a file
242
-	 */
243
-	public static function delete($path) {
244
-		$deletedFile = self::$deletedFiles[$path];
245
-		$uid = $deletedFile['uid'];
246
-		$filename = $deletedFile['filename'];
247
-
248
-		if (!Filesystem::file_exists($path)) {
249
-			$view = new View('/' . $uid . '/files_versions');
250
-
251
-			$versions = self::getVersions($uid, $filename);
252
-			if (!empty($versions)) {
253
-				foreach ($versions as $v) {
254
-					\OC_Hook::emit('\OCP\Versions', 'preDelete', ['path' => $path . $v['version'], 'trigger' => self::DELETE_TRIGGER_MASTER_REMOVED]);
255
-					self::deleteVersion($view, $filename . '.v' . $v['version']);
256
-					\OC_Hook::emit('\OCP\Versions', 'delete', ['path' => $path . $v['version'], 'trigger' => self::DELETE_TRIGGER_MASTER_REMOVED]);
257
-				}
258
-			}
259
-		}
260
-		unset(self::$deletedFiles[$path]);
261
-	}
262
-
263
-	/**
264
-	 * Rename or copy versions of a file of the given paths
265
-	 *
266
-	 * @param string $sourcePath source path of the file to move, relative to
267
-	 * the currently logged in user's "files" folder
268
-	 * @param string $targetPath target path of the file to move, relative to
269
-	 * the currently logged in user's "files" folder
270
-	 * @param string $operation can be 'copy' or 'rename'
271
-	 */
272
-	public static function renameOrCopy($sourcePath, $targetPath, $operation) {
273
-		[$sourceOwner, $sourcePath] = self::getSourcePathAndUser($sourcePath);
274
-
275
-		// it was a upload of a existing file if no old path exists
276
-		// in this case the pre-hook already called the store method and we can
277
-		// stop here
278
-		if ($sourcePath === false) {
279
-			return true;
280
-		}
281
-
282
-		[$targetOwner, $targetPath] = self::getUidAndFilename($targetPath);
283
-
284
-		$sourcePath = ltrim($sourcePath, '/');
285
-		$targetPath = ltrim($targetPath, '/');
286
-
287
-		$rootView = new View('');
288
-
289
-		// did we move a directory ?
290
-		if ($rootView->is_dir('/' . $targetOwner . '/files/' . $targetPath)) {
291
-			// does the directory exists for versions too ?
292
-			if ($rootView->is_dir('/' . $sourceOwner . '/files_versions/' . $sourcePath)) {
293
-				// create missing dirs if necessary
294
-				self::createMissingDirectories($targetPath, new View('/'. $targetOwner));
295
-
296
-				// move the directory containing the versions
297
-				$rootView->$operation(
298
-					'/' . $sourceOwner . '/files_versions/' . $sourcePath,
299
-					'/' . $targetOwner . '/files_versions/' . $targetPath
300
-				);
301
-			}
302
-		} elseif ($versions = Storage::getVersions($sourceOwner, '/' . $sourcePath)) {
303
-			// create missing dirs if necessary
304
-			self::createMissingDirectories($targetPath, new View('/'. $targetOwner));
305
-
306
-			foreach ($versions as $v) {
307
-				// move each version one by one to the target directory
308
-				$rootView->$operation(
309
-					'/' . $sourceOwner . '/files_versions/' . $sourcePath.'.v' . $v['version'],
310
-					'/' . $targetOwner . '/files_versions/' . $targetPath.'.v'.$v['version']
311
-				);
312
-			}
313
-		}
314
-
315
-		// if we moved versions directly for a file, schedule expiration check for that file
316
-		if (!$rootView->is_dir('/' . $targetOwner . '/files/' . $targetPath)) {
317
-			self::scheduleExpire($targetOwner, $targetPath);
318
-		}
319
-	}
320
-
321
-	/**
322
-	 * Rollback to an old version of a file.
323
-	 *
324
-	 * @param string $file file name
325
-	 * @param int $revision revision timestamp
326
-	 * @return bool
327
-	 */
328
-	public static function rollback(string $file, int $revision, IUser $user) {
329
-
330
-		// add expected leading slash
331
-		$filename = '/' . ltrim($file, '/');
332
-
333
-		// Fetch the userfolder to trigger view hooks
334
-		$userFolder = \OC::$server->getUserFolder($user->getUID());
335
-
336
-		$users_view = new View('/'.$user->getUID());
337
-		$files_view = new View('/'. $user->getUID().'/files');
338
-
339
-		$versionCreated = false;
340
-
341
-		$fileInfo = $files_view->getFileInfo($file);
342
-
343
-		// check if user has the permissions to revert a version
344
-		if (!$fileInfo->isUpdateable()) {
345
-			return false;
346
-		}
347
-
348
-		//first create a new version
349
-		$version = 'files_versions'.$filename.'.v'.$users_view->filemtime('files'.$filename);
350
-		if (!$users_view->file_exists($version)) {
351
-			$users_view->copy('files'.$filename, 'files_versions'.$filename.'.v'.$users_view->filemtime('files'.$filename));
352
-			$versionCreated = true;
353
-		}
354
-
355
-		$fileToRestore = 'files_versions' . $filename . '.v' . $revision;
356
-
357
-		// Restore encrypted version of the old file for the newly restored file
358
-		// This has to happen manually here since the file is manually copied below
359
-		$oldVersion = $users_view->getFileInfo($fileToRestore)->getEncryptedVersion();
360
-		$oldFileInfo = $users_view->getFileInfo($fileToRestore);
361
-		$cache = $fileInfo->getStorage()->getCache();
362
-		$cache->update(
363
-			$fileInfo->getId(), [
364
-				'encrypted' => $oldVersion,
365
-				'encryptedVersion' => $oldVersion,
366
-				'size' => $oldFileInfo->getSize()
367
-			]
368
-		);
369
-
370
-		// rollback
371
-		if (self::copyFileContents($users_view, $fileToRestore, 'files' . $filename)) {
372
-			$files_view->touch($file, $revision);
373
-			Storage::scheduleExpire($user->getUID(), $file);
374
-
375
-			$node = $userFolder->get($file);
376
-
377
-			// TODO: move away from those legacy hooks!
378
-			\OC_Hook::emit('\OCP\Versions', 'rollback', [
379
-				'path' => $filename,
380
-				'revision' => $revision,
381
-				'node' => $node,
382
-			]);
383
-			return true;
384
-		} elseif ($versionCreated) {
385
-			self::deleteVersion($users_view, $version);
386
-		}
387
-
388
-		return false;
389
-	}
390
-
391
-	/**
392
-	 * Stream copy file contents from $path1 to $path2
393
-	 *
394
-	 * @param View $view view to use for copying
395
-	 * @param string $path1 source file to copy
396
-	 * @param string $path2 target file
397
-	 *
398
-	 * @return bool true for success, false otherwise
399
-	 */
400
-	private static function copyFileContents($view, $path1, $path2) {
401
-		/** @var \OC\Files\Storage\Storage $storage1 */
402
-		[$storage1, $internalPath1] = $view->resolvePath($path1);
403
-		/** @var \OC\Files\Storage\Storage $storage2 */
404
-		[$storage2, $internalPath2] = $view->resolvePath($path2);
405
-
406
-		$view->lockFile($path1, ILockingProvider::LOCK_EXCLUSIVE);
407
-		$view->lockFile($path2, ILockingProvider::LOCK_EXCLUSIVE);
408
-
409
-		// TODO add a proper way of overwriting a file while maintaining file ids
410
-		if ($storage1->instanceOfStorage('\OC\Files\ObjectStore\ObjectStoreStorage') || $storage2->instanceOfStorage('\OC\Files\ObjectStore\ObjectStoreStorage')) {
411
-			$source = $storage1->fopen($internalPath1, 'r');
412
-			$target = $storage2->fopen($internalPath2, 'w');
413
-			[, $result] = \OC_Helper::streamCopy($source, $target);
414
-			fclose($source);
415
-			fclose($target);
416
-
417
-			if ($result !== false) {
418
-				$storage1->unlink($internalPath1);
419
-			}
420
-		} else {
421
-			$result = $storage2->moveFromStorage($storage1, $internalPath1, $internalPath2);
422
-		}
423
-
424
-		$view->unlockFile($path1, ILockingProvider::LOCK_EXCLUSIVE);
425
-		$view->unlockFile($path2, ILockingProvider::LOCK_EXCLUSIVE);
426
-
427
-		return ($result !== false);
428
-	}
429
-
430
-	/**
431
-	 * get a list of all available versions of a file in descending chronological order
432
-	 * @param string $uid user id from the owner of the file
433
-	 * @param string $filename file to find versions of, relative to the user files dir
434
-	 * @param string $userFullPath
435
-	 * @return array versions newest version first
436
-	 */
437
-	public static function getVersions($uid, $filename, $userFullPath = '') {
438
-		$versions = [];
439
-		if (empty($filename)) {
440
-			return $versions;
441
-		}
442
-		// fetch for old versions
443
-		$view = new View('/' . $uid . '/');
444
-
445
-		$pathinfo = pathinfo($filename);
446
-		$versionedFile = $pathinfo['basename'];
447
-
448
-		$dir = Filesystem::normalizePath(self::VERSIONS_ROOT . '/' . $pathinfo['dirname']);
449
-
450
-		$dirContent = false;
451
-		if ($view->is_dir($dir)) {
452
-			$dirContent = $view->opendir($dir);
453
-		}
454
-
455
-		if ($dirContent === false) {
456
-			return $versions;
457
-		}
458
-
459
-		if (is_resource($dirContent)) {
460
-			while (($entryName = readdir($dirContent)) !== false) {
461
-				if (!Filesystem::isIgnoredDir($entryName)) {
462
-					$pathparts = pathinfo($entryName);
463
-					$filename = $pathparts['filename'];
464
-					if ($filename === $versionedFile) {
465
-						$pathparts = pathinfo($entryName);
466
-						$timestamp = substr($pathparts['extension'], 1);
467
-						$filename = $pathparts['filename'];
468
-						$key = $timestamp . '#' . $filename;
469
-						$versions[$key]['version'] = $timestamp;
470
-						$versions[$key]['humanReadableTimestamp'] = self::getHumanReadableTimestamp($timestamp);
471
-						if (empty($userFullPath)) {
472
-							$versions[$key]['preview'] = '';
473
-						} else {
474
-							$versions[$key]['preview'] = \OC::$server->getURLGenerator('files_version.Preview.getPreview', ['file' => $userFullPath, 'version' => $timestamp]);
475
-						}
476
-						$versions[$key]['path'] = Filesystem::normalizePath($pathinfo['dirname'] . '/' . $filename);
477
-						$versions[$key]['name'] = $versionedFile;
478
-						$versions[$key]['size'] = $view->filesize($dir . '/' . $entryName);
479
-						$versions[$key]['mimetype'] = \OC::$server->getMimeTypeDetector()->detectPath($versionedFile);
480
-					}
481
-				}
482
-			}
483
-			closedir($dirContent);
484
-		}
485
-
486
-		// sort with newest version first
487
-		krsort($versions);
488
-
489
-		return $versions;
490
-	}
491
-
492
-	/**
493
-	 * Expire versions that older than max version retention time
494
-	 * @param string $uid
495
-	 */
496
-	public static function expireOlderThanMaxForUser($uid) {
497
-		$expiration = self::getExpiration();
498
-		$threshold = $expiration->getMaxAgeAsTimestamp();
499
-		$versions = self::getAllVersions($uid);
500
-		if (!$threshold || empty($versions['all'])) {
501
-			return;
502
-		}
503
-
504
-		$toDelete = [];
505
-		foreach (array_reverse($versions['all']) as $key => $version) {
506
-			if ((int)$version['version'] < $threshold) {
507
-				$toDelete[$key] = $version;
508
-			} else {
509
-				//Versions are sorted by time - nothing mo to iterate.
510
-				break;
511
-			}
512
-		}
513
-
514
-		$view = new View('/' . $uid . '/files_versions');
515
-		if (!empty($toDelete)) {
516
-			foreach ($toDelete as $version) {
517
-				\OC_Hook::emit('\OCP\Versions', 'preDelete', ['path' => $version['path'].'.v'.$version['version'], 'trigger' => self::DELETE_TRIGGER_RETENTION_CONSTRAINT]);
518
-				self::deleteVersion($view, $version['path'] . '.v' . $version['version']);
519
-				\OC_Hook::emit('\OCP\Versions', 'delete', ['path' => $version['path'].'.v'.$version['version'], 'trigger' => self::DELETE_TRIGGER_RETENTION_CONSTRAINT]);
520
-			}
521
-		}
522
-	}
523
-
524
-	/**
525
-	 * translate a timestamp into a string like "5 days ago"
526
-	 * @param int $timestamp
527
-	 * @return string for example "5 days ago"
528
-	 */
529
-	private static function getHumanReadableTimestamp($timestamp) {
530
-		$diff = time() - $timestamp;
531
-
532
-		if ($diff < 60) { // first minute
533
-			return  $diff . " seconds ago";
534
-		} elseif ($diff < 3600) { //first hour
535
-			return round($diff / 60) . " minutes ago";
536
-		} elseif ($diff < 86400) { // first day
537
-			return round($diff / 3600) . " hours ago";
538
-		} elseif ($diff < 604800) { //first week
539
-			return round($diff / 86400) . " days ago";
540
-		} elseif ($diff < 2419200) { //first month
541
-			return round($diff / 604800) . " weeks ago";
542
-		} elseif ($diff < 29030400) { // first year
543
-			return round($diff / 2419200) . " months ago";
544
-		} else {
545
-			return round($diff / 29030400) . " years ago";
546
-		}
547
-	}
548
-
549
-	/**
550
-	 * returns all stored file versions from a given user
551
-	 * @param string $uid id of the user
552
-	 * @return array with contains two arrays 'all' which contains all versions sorted by age and 'by_file' which contains all versions sorted by filename
553
-	 */
554
-	private static function getAllVersions($uid) {
555
-		$view = new View('/' . $uid . '/');
556
-		$dirs = [self::VERSIONS_ROOT];
557
-		$versions = [];
558
-
559
-		while (!empty($dirs)) {
560
-			$dir = array_pop($dirs);
561
-			$files = $view->getDirectoryContent($dir);
562
-
563
-			foreach ($files as $file) {
564
-				$fileData = $file->getData();
565
-				$filePath = $dir . '/' . $fileData['name'];
566
-				if ($file['type'] === 'dir') {
567
-					$dirs[] = $filePath;
568
-				} else {
569
-					$versionsBegin = strrpos($filePath, '.v');
570
-					$relPathStart = strlen(self::VERSIONS_ROOT);
571
-					$version = substr($filePath, $versionsBegin + 2);
572
-					$relpath = substr($filePath, $relPathStart, $versionsBegin - $relPathStart);
573
-					$key = $version . '#' . $relpath;
574
-					$versions[$key] = ['path' => $relpath, 'timestamp' => $version];
575
-				}
576
-			}
577
-		}
578
-
579
-		// newest version first
580
-		krsort($versions);
581
-
582
-		$result = [
583
-			'all' => [],
584
-			'by_file' => [],
585
-		];
586
-
587
-		foreach ($versions as $key => $value) {
588
-			$size = $view->filesize(self::VERSIONS_ROOT.'/'.$value['path'].'.v'.$value['timestamp']);
589
-			$filename = $value['path'];
590
-
591
-			$result['all'][$key]['version'] = $value['timestamp'];
592
-			$result['all'][$key]['path'] = $filename;
593
-			$result['all'][$key]['size'] = $size;
594
-
595
-			$result['by_file'][$filename][$key]['version'] = $value['timestamp'];
596
-			$result['by_file'][$filename][$key]['path'] = $filename;
597
-			$result['by_file'][$filename][$key]['size'] = $size;
598
-		}
599
-
600
-		return $result;
601
-	}
602
-
603
-	/**
604
-	 * get list of files we want to expire
605
-	 * @param array $versions list of versions
606
-	 * @param integer $time
607
-	 * @param bool $quotaExceeded is versions storage limit reached
608
-	 * @return array containing the list of to deleted versions and the size of them
609
-	 */
610
-	protected static function getExpireList($time, $versions, $quotaExceeded = false) {
611
-		$expiration = self::getExpiration();
612
-
613
-		if ($expiration->shouldAutoExpire()) {
614
-			[$toDelete, $size] = self::getAutoExpireList($time, $versions);
615
-		} else {
616
-			$size = 0;
617
-			$toDelete = [];  // versions we want to delete
618
-		}
619
-
620
-		foreach ($versions as $key => $version) {
621
-			if ($expiration->isExpired($version['version'], $quotaExceeded) && !isset($toDelete[$key])) {
622
-				$size += $version['size'];
623
-				$toDelete[$key] = $version['path'] . '.v' . $version['version'];
624
-			}
625
-		}
626
-
627
-		return [$toDelete, $size];
628
-	}
629
-
630
-	/**
631
-	 * get list of files we want to expire
632
-	 * @param array $versions list of versions
633
-	 * @param integer $time
634
-	 * @return array containing the list of to deleted versions and the size of them
635
-	 */
636
-	protected static function getAutoExpireList($time, $versions) {
637
-		$size = 0;
638
-		$toDelete = [];  // versions we want to delete
639
-
640
-		$interval = 1;
641
-		$step = Storage::$max_versions_per_interval[$interval]['step'];
642
-		if (Storage::$max_versions_per_interval[$interval]['intervalEndsAfter'] === -1) {
643
-			$nextInterval = -1;
644
-		} else {
645
-			$nextInterval = $time - Storage::$max_versions_per_interval[$interval]['intervalEndsAfter'];
646
-		}
647
-
648
-		$firstVersion = reset($versions);
649
-
650
-		if ($firstVersion === false) {
651
-			return [$toDelete, $size];
652
-		}
653
-
654
-		$firstKey = key($versions);
655
-		$prevTimestamp = $firstVersion['version'];
656
-		$nextVersion = $firstVersion['version'] - $step;
657
-		unset($versions[$firstKey]);
658
-
659
-		foreach ($versions as $key => $version) {
660
-			$newInterval = true;
661
-			while ($newInterval) {
662
-				if ($nextInterval === -1 || $prevTimestamp > $nextInterval) {
663
-					if ($version['version'] > $nextVersion) {
664
-						//distance between two version too small, mark to delete
665
-						$toDelete[$key] = $version['path'] . '.v' . $version['version'];
666
-						$size += $version['size'];
667
-						\OC::$server->getLogger()->info('Mark to expire '. $version['path'] .' next version should be ' . $nextVersion . " or smaller. (prevTimestamp: " . $prevTimestamp . "; step: " . $step, ['app' => 'files_versions']);
668
-					} else {
669
-						$nextVersion = $version['version'] - $step;
670
-						$prevTimestamp = $version['version'];
671
-					}
672
-					$newInterval = false; // version checked so we can move to the next one
673
-				} else { // time to move on to the next interval
674
-					$interval++;
675
-					$step = Storage::$max_versions_per_interval[$interval]['step'];
676
-					$nextVersion = $prevTimestamp - $step;
677
-					if (Storage::$max_versions_per_interval[$interval]['intervalEndsAfter'] === -1) {
678
-						$nextInterval = -1;
679
-					} else {
680
-						$nextInterval = $time - Storage::$max_versions_per_interval[$interval]['intervalEndsAfter'];
681
-					}
682
-					$newInterval = true; // we changed the interval -> check same version with new interval
683
-				}
684
-			}
685
-		}
686
-
687
-		return [$toDelete, $size];
688
-	}
689
-
690
-	/**
691
-	 * Schedule versions expiration for the given file
692
-	 *
693
-	 * @param string $uid owner of the file
694
-	 * @param string $fileName file/folder for which to schedule expiration
695
-	 */
696
-	public static function scheduleExpire($uid, $fileName) {
697
-		// let the admin disable auto expire
698
-		$expiration = self::getExpiration();
699
-		if ($expiration->isEnabled()) {
700
-			$command = new Expire($uid, $fileName);
701
-			\OC::$server->getCommandBus()->push($command);
702
-		}
703
-	}
704
-
705
-	/**
706
-	 * Expire versions which exceed the quota.
707
-	 *
708
-	 * This will setup the filesystem for the given user but will not
709
-	 * tear it down afterwards.
710
-	 *
711
-	 * @param string $filename path to file to expire
712
-	 * @param string $uid user for which to expire the version
713
-	 * @return bool|int|null
714
-	 */
715
-	public static function expire($filename, $uid) {
716
-		$expiration = self::getExpiration();
717
-
718
-		if ($expiration->isEnabled()) {
719
-			// get available disk space for user
720
-			$user = \OC::$server->getUserManager()->get($uid);
721
-			if (is_null($user)) {
722
-				\OC::$server->getLogger()->error('Backends provided no user object for ' . $uid, ['app' => 'files_versions']);
723
-				throw new \OC\User\NoUserException('Backends provided no user object for ' . $uid);
724
-			}
725
-
726
-			\OC_Util::setupFS($uid);
727
-
728
-			try {
729
-				if (!Filesystem::file_exists($filename)) {
730
-					return false;
731
-				}
732
-			} catch (StorageNotAvailableException $e) {
733
-				// if we can't check that the file hasn't been deleted we can only assume that it hasn't
734
-				// note that this `StorageNotAvailableException` is about the file the versions originate from,
735
-				// not the storage that the versions are stored on
736
-			}
737
-
738
-			if (empty($filename)) {
739
-				// file maybe renamed or deleted
740
-				return false;
741
-			}
742
-			$versionsFileview = new View('/'.$uid.'/files_versions');
743
-
744
-			$softQuota = true;
745
-			$quota = $user->getQuota();
746
-			if ($quota === null || $quota === 'none') {
747
-				$quota = Filesystem::free_space('/');
748
-				$softQuota = false;
749
-			} else {
750
-				$quota = \OCP\Util::computerFileSize($quota);
751
-			}
752
-
753
-			// make sure that we have the current size of the version history
754
-			$versionsSize = self::getVersionsSize($uid);
755
-
756
-			// calculate available space for version history
757
-			// subtract size of files and current versions size from quota
758
-			if ($quota >= 0) {
759
-				if ($softQuota) {
760
-					$userFolder = \OC::$server->getUserFolder($uid);
761
-					if (is_null($userFolder)) {
762
-						$availableSpace = 0;
763
-					} else {
764
-						$free = $quota - $userFolder->getSize(false); // remaining free space for user
765
-						if ($free > 0) {
766
-							$availableSpace = ($free * self::DEFAULTMAXSIZE / 100) - $versionsSize; // how much space can be used for versions
767
-						} else {
768
-							$availableSpace = $free - $versionsSize;
769
-						}
770
-					}
771
-				} else {
772
-					$availableSpace = $quota;
773
-				}
774
-			} else {
775
-				$availableSpace = PHP_INT_MAX;
776
-			}
777
-
778
-			$allVersions = Storage::getVersions($uid, $filename);
779
-
780
-			$time = time();
781
-			[$toDelete, $sizeOfDeletedVersions] = self::getExpireList($time, $allVersions, $availableSpace <= 0);
782
-
783
-			$availableSpace = $availableSpace + $sizeOfDeletedVersions;
784
-			$versionsSize = $versionsSize - $sizeOfDeletedVersions;
785
-
786
-			// if still not enough free space we rearrange the versions from all files
787
-			if ($availableSpace <= 0) {
788
-				$result = self::getAllVersions($uid);
789
-				$allVersions = $result['all'];
790
-
791
-				foreach ($result['by_file'] as $versions) {
792
-					[$toDeleteNew, $size] = self::getExpireList($time, $versions, $availableSpace <= 0);
793
-					$toDelete = array_merge($toDelete, $toDeleteNew);
794
-					$sizeOfDeletedVersions += $size;
795
-				}
796
-				$availableSpace = $availableSpace + $sizeOfDeletedVersions;
797
-				$versionsSize = $versionsSize - $sizeOfDeletedVersions;
798
-			}
799
-
800
-			$logger = \OC::$server->getLogger();
801
-			foreach ($toDelete as $key => $path) {
802
-				\OC_Hook::emit('\OCP\Versions', 'preDelete', ['path' => $path, 'trigger' => self::DELETE_TRIGGER_QUOTA_EXCEEDED]);
803
-				self::deleteVersion($versionsFileview, $path);
804
-				\OC_Hook::emit('\OCP\Versions', 'delete', ['path' => $path, 'trigger' => self::DELETE_TRIGGER_QUOTA_EXCEEDED]);
805
-				unset($allVersions[$key]); // update array with the versions we keep
806
-				$logger->info('Expire: ' . $path, ['app' => 'files_versions']);
807
-			}
808
-
809
-			// Check if enough space is available after versions are rearranged.
810
-			// If not we delete the oldest versions until we meet the size limit for versions,
811
-			// but always keep the two latest versions
812
-			$numOfVersions = count($allVersions) - 2 ;
813
-			$i = 0;
814
-			// sort oldest first and make sure that we start at the first element
815
-			ksort($allVersions);
816
-			reset($allVersions);
817
-			while ($availableSpace < 0 && $i < $numOfVersions) {
818
-				$version = current($allVersions);
819
-				\OC_Hook::emit('\OCP\Versions', 'preDelete', ['path' => $version['path'].'.v'.$version['version'], 'trigger' => self::DELETE_TRIGGER_QUOTA_EXCEEDED]);
820
-				self::deleteVersion($versionsFileview, $version['path'] . '.v' . $version['version']);
821
-				\OC_Hook::emit('\OCP\Versions', 'delete', ['path' => $version['path'].'.v'.$version['version'], 'trigger' => self::DELETE_TRIGGER_QUOTA_EXCEEDED]);
822
-				\OC::$server->getLogger()->info('running out of space! Delete oldest version: ' . $version['path'].'.v'.$version['version'], ['app' => 'files_versions']);
823
-				$versionsSize -= $version['size'];
824
-				$availableSpace += $version['size'];
825
-				next($allVersions);
826
-				$i++;
827
-			}
828
-
829
-			return $versionsSize; // finally return the new size of the version history
830
-		}
831
-
832
-		return false;
833
-	}
834
-
835
-	/**
836
-	 * Create recursively missing directories inside of files_versions
837
-	 * that match the given path to a file.
838
-	 *
839
-	 * @param string $filename $path to a file, relative to the user's
840
-	 * "files" folder
841
-	 * @param View $view view on data/user/
842
-	 */
843
-	public static function createMissingDirectories($filename, $view) {
844
-		$dirname = Filesystem::normalizePath(dirname($filename));
845
-		$dirParts = explode('/', $dirname);
846
-		$dir = "/files_versions";
847
-		foreach ($dirParts as $part) {
848
-			$dir = $dir . '/' . $part;
849
-			if (!$view->file_exists($dir)) {
850
-				$view->mkdir($dir);
851
-			}
852
-		}
853
-	}
854
-
855
-	/**
856
-	 * Static workaround
857
-	 * @return Expiration
858
-	 */
859
-	protected static function getExpiration() {
860
-		if (self::$application === null) {
861
-			self::$application = \OC::$server->query(Application::class);
862
-		}
863
-		return self::$application->getContainer()->query(Expiration::class);
864
-	}
62
+    public const DEFAULTENABLED = true;
63
+    public const DEFAULTMAXSIZE = 50; // unit: percentage; 50% of available disk space/quota
64
+    public const VERSIONS_ROOT = 'files_versions/';
65
+
66
+    public const DELETE_TRIGGER_MASTER_REMOVED = 0;
67
+    public const DELETE_TRIGGER_RETENTION_CONSTRAINT = 1;
68
+    public const DELETE_TRIGGER_QUOTA_EXCEEDED = 2;
69
+
70
+    // files for which we can remove the versions after the delete operation was successful
71
+    private static $deletedFiles = [];
72
+
73
+    private static $sourcePathAndUser = [];
74
+
75
+    private static $max_versions_per_interval = [
76
+        //first 10sec, one version every 2sec
77
+        1 => ['intervalEndsAfter' => 10,      'step' => 2],
78
+        //next minute, one version every 10sec
79
+        2 => ['intervalEndsAfter' => 60,      'step' => 10],
80
+        //next hour, one version every minute
81
+        3 => ['intervalEndsAfter' => 3600,    'step' => 60],
82
+        //next 24h, one version every hour
83
+        4 => ['intervalEndsAfter' => 86400,   'step' => 3600],
84
+        //next 30days, one version per day
85
+        5 => ['intervalEndsAfter' => 2592000, 'step' => 86400],
86
+        //until the end one version per week
87
+        6 => ['intervalEndsAfter' => -1,      'step' => 604800],
88
+    ];
89
+
90
+    /** @var \OCA\Files_Versions\AppInfo\Application */
91
+    private static $application;
92
+
93
+    /**
94
+     * get the UID of the owner of the file and the path to the file relative to
95
+     * owners files folder
96
+     *
97
+     * @param string $filename
98
+     * @return array
99
+     * @throws \OC\User\NoUserException
100
+     */
101
+    public static function getUidAndFilename($filename) {
102
+        $uid = Filesystem::getOwner($filename);
103
+        $userManager = \OC::$server->getUserManager();
104
+        // if the user with the UID doesn't exists, e.g. because the UID points
105
+        // to a remote user with a federated cloud ID we use the current logged-in
106
+        // user. We need a valid local user to create the versions
107
+        if (!$userManager->userExists($uid)) {
108
+            $uid = OC_User::getUser();
109
+        }
110
+        Filesystem::initMountPoints($uid);
111
+        if ($uid !== OC_User::getUser()) {
112
+            $info = Filesystem::getFileInfo($filename);
113
+            $ownerView = new View('/'.$uid.'/files');
114
+            try {
115
+                $filename = $ownerView->getPath($info['fileid']);
116
+                // make sure that the file name doesn't end with a trailing slash
117
+                // can for example happen single files shared across servers
118
+                $filename = rtrim($filename, '/');
119
+            } catch (NotFoundException $e) {
120
+                $filename = null;
121
+            }
122
+        }
123
+        return [$uid, $filename];
124
+    }
125
+
126
+    /**
127
+     * Remember the owner and the owner path of the source file
128
+     *
129
+     * @param string $source source path
130
+     */
131
+    public static function setSourcePathAndUser($source) {
132
+        [$uid, $path] = self::getUidAndFilename($source);
133
+        self::$sourcePathAndUser[$source] = ['uid' => $uid, 'path' => $path];
134
+    }
135
+
136
+    /**
137
+     * Gets the owner and the owner path from the source path
138
+     *
139
+     * @param string $source source path
140
+     * @return array with user id and path
141
+     */
142
+    public static function getSourcePathAndUser($source) {
143
+        if (isset(self::$sourcePathAndUser[$source])) {
144
+            $uid = self::$sourcePathAndUser[$source]['uid'];
145
+            $path = self::$sourcePathAndUser[$source]['path'];
146
+            unset(self::$sourcePathAndUser[$source]);
147
+        } else {
148
+            $uid = $path = false;
149
+        }
150
+        return [$uid, $path];
151
+    }
152
+
153
+    /**
154
+     * get current size of all versions from a given user
155
+     *
156
+     * @param string $user user who owns the versions
157
+     * @return int versions size
158
+     */
159
+    private static function getVersionsSize($user) {
160
+        $view = new View('/' . $user);
161
+        $fileInfo = $view->getFileInfo('/files_versions');
162
+        return isset($fileInfo['size']) ? $fileInfo['size'] : 0;
163
+    }
164
+
165
+    /**
166
+     * store a new version of a file.
167
+     */
168
+    public static function store($filename) {
169
+
170
+        // if the file gets streamed we need to remove the .part extension
171
+        // to get the right target
172
+        $ext = pathinfo($filename, PATHINFO_EXTENSION);
173
+        if ($ext === 'part') {
174
+            $filename = substr($filename, 0, -5);
175
+        }
176
+
177
+        // we only handle existing files
178
+        if (! Filesystem::file_exists($filename) || Filesystem::is_dir($filename)) {
179
+            return false;
180
+        }
181
+
182
+        [$uid, $filename] = self::getUidAndFilename($filename);
183
+
184
+        $files_view = new View('/'.$uid .'/files');
185
+
186
+        $eventDispatcher = \OC::$server->getEventDispatcher();
187
+        $fileInfo = $files_view->getFileInfo($filename);
188
+        $id = $fileInfo->getId();
189
+        $nodes = \OC::$server->getRootFolder()->getUserFolder($uid)->getById($id);
190
+        foreach ($nodes as $node) {
191
+            $event = new CreateVersionEvent($node);
192
+            $eventDispatcher->dispatch('OCA\Files_Versions::createVersion', $event);
193
+            if ($event->shouldCreateVersion() === false) {
194
+                return false;
195
+            }
196
+        }
197
+
198
+        // no use making versions for empty files
199
+        if ($fileInfo->getSize() === 0) {
200
+            return false;
201
+        }
202
+
203
+        /** @var IVersionManager $versionManager */
204
+        $versionManager = \OC::$server->query(IVersionManager::class);
205
+        $userManager = \OC::$server->getUserManager();
206
+        $user = $userManager->get($uid);
207
+
208
+        $versionManager->createVersion($user, $fileInfo);
209
+    }
210
+
211
+
212
+    /**
213
+     * mark file as deleted so that we can remove the versions if the file is gone
214
+     * @param string $path
215
+     */
216
+    public static function markDeletedFile($path) {
217
+        [$uid, $filename] = self::getUidAndFilename($path);
218
+        self::$deletedFiles[$path] = [
219
+            'uid' => $uid,
220
+            'filename' => $filename];
221
+    }
222
+
223
+    /**
224
+     * delete the version from the storage and cache
225
+     *
226
+     * @param View $view
227
+     * @param string $path
228
+     */
229
+    protected static function deleteVersion($view, $path) {
230
+        $view->unlink($path);
231
+        /**
232
+         * @var \OC\Files\Storage\Storage $storage
233
+         * @var string $internalPath
234
+         */
235
+        [$storage, $internalPath] = $view->resolvePath($path);
236
+        $cache = $storage->getCache($internalPath);
237
+        $cache->remove($internalPath);
238
+    }
239
+
240
+    /**
241
+     * Delete versions of a file
242
+     */
243
+    public static function delete($path) {
244
+        $deletedFile = self::$deletedFiles[$path];
245
+        $uid = $deletedFile['uid'];
246
+        $filename = $deletedFile['filename'];
247
+
248
+        if (!Filesystem::file_exists($path)) {
249
+            $view = new View('/' . $uid . '/files_versions');
250
+
251
+            $versions = self::getVersions($uid, $filename);
252
+            if (!empty($versions)) {
253
+                foreach ($versions as $v) {
254
+                    \OC_Hook::emit('\OCP\Versions', 'preDelete', ['path' => $path . $v['version'], 'trigger' => self::DELETE_TRIGGER_MASTER_REMOVED]);
255
+                    self::deleteVersion($view, $filename . '.v' . $v['version']);
256
+                    \OC_Hook::emit('\OCP\Versions', 'delete', ['path' => $path . $v['version'], 'trigger' => self::DELETE_TRIGGER_MASTER_REMOVED]);
257
+                }
258
+            }
259
+        }
260
+        unset(self::$deletedFiles[$path]);
261
+    }
262
+
263
+    /**
264
+     * Rename or copy versions of a file of the given paths
265
+     *
266
+     * @param string $sourcePath source path of the file to move, relative to
267
+     * the currently logged in user's "files" folder
268
+     * @param string $targetPath target path of the file to move, relative to
269
+     * the currently logged in user's "files" folder
270
+     * @param string $operation can be 'copy' or 'rename'
271
+     */
272
+    public static function renameOrCopy($sourcePath, $targetPath, $operation) {
273
+        [$sourceOwner, $sourcePath] = self::getSourcePathAndUser($sourcePath);
274
+
275
+        // it was a upload of a existing file if no old path exists
276
+        // in this case the pre-hook already called the store method and we can
277
+        // stop here
278
+        if ($sourcePath === false) {
279
+            return true;
280
+        }
281
+
282
+        [$targetOwner, $targetPath] = self::getUidAndFilename($targetPath);
283
+
284
+        $sourcePath = ltrim($sourcePath, '/');
285
+        $targetPath = ltrim($targetPath, '/');
286
+
287
+        $rootView = new View('');
288
+
289
+        // did we move a directory ?
290
+        if ($rootView->is_dir('/' . $targetOwner . '/files/' . $targetPath)) {
291
+            // does the directory exists for versions too ?
292
+            if ($rootView->is_dir('/' . $sourceOwner . '/files_versions/' . $sourcePath)) {
293
+                // create missing dirs if necessary
294
+                self::createMissingDirectories($targetPath, new View('/'. $targetOwner));
295
+
296
+                // move the directory containing the versions
297
+                $rootView->$operation(
298
+                    '/' . $sourceOwner . '/files_versions/' . $sourcePath,
299
+                    '/' . $targetOwner . '/files_versions/' . $targetPath
300
+                );
301
+            }
302
+        } elseif ($versions = Storage::getVersions($sourceOwner, '/' . $sourcePath)) {
303
+            // create missing dirs if necessary
304
+            self::createMissingDirectories($targetPath, new View('/'. $targetOwner));
305
+
306
+            foreach ($versions as $v) {
307
+                // move each version one by one to the target directory
308
+                $rootView->$operation(
309
+                    '/' . $sourceOwner . '/files_versions/' . $sourcePath.'.v' . $v['version'],
310
+                    '/' . $targetOwner . '/files_versions/' . $targetPath.'.v'.$v['version']
311
+                );
312
+            }
313
+        }
314
+
315
+        // if we moved versions directly for a file, schedule expiration check for that file
316
+        if (!$rootView->is_dir('/' . $targetOwner . '/files/' . $targetPath)) {
317
+            self::scheduleExpire($targetOwner, $targetPath);
318
+        }
319
+    }
320
+
321
+    /**
322
+     * Rollback to an old version of a file.
323
+     *
324
+     * @param string $file file name
325
+     * @param int $revision revision timestamp
326
+     * @return bool
327
+     */
328
+    public static function rollback(string $file, int $revision, IUser $user) {
329
+
330
+        // add expected leading slash
331
+        $filename = '/' . ltrim($file, '/');
332
+
333
+        // Fetch the userfolder to trigger view hooks
334
+        $userFolder = \OC::$server->getUserFolder($user->getUID());
335
+
336
+        $users_view = new View('/'.$user->getUID());
337
+        $files_view = new View('/'. $user->getUID().'/files');
338
+
339
+        $versionCreated = false;
340
+
341
+        $fileInfo = $files_view->getFileInfo($file);
342
+
343
+        // check if user has the permissions to revert a version
344
+        if (!$fileInfo->isUpdateable()) {
345
+            return false;
346
+        }
347
+
348
+        //first create a new version
349
+        $version = 'files_versions'.$filename.'.v'.$users_view->filemtime('files'.$filename);
350
+        if (!$users_view->file_exists($version)) {
351
+            $users_view->copy('files'.$filename, 'files_versions'.$filename.'.v'.$users_view->filemtime('files'.$filename));
352
+            $versionCreated = true;
353
+        }
354
+
355
+        $fileToRestore = 'files_versions' . $filename . '.v' . $revision;
356
+
357
+        // Restore encrypted version of the old file for the newly restored file
358
+        // This has to happen manually here since the file is manually copied below
359
+        $oldVersion = $users_view->getFileInfo($fileToRestore)->getEncryptedVersion();
360
+        $oldFileInfo = $users_view->getFileInfo($fileToRestore);
361
+        $cache = $fileInfo->getStorage()->getCache();
362
+        $cache->update(
363
+            $fileInfo->getId(), [
364
+                'encrypted' => $oldVersion,
365
+                'encryptedVersion' => $oldVersion,
366
+                'size' => $oldFileInfo->getSize()
367
+            ]
368
+        );
369
+
370
+        // rollback
371
+        if (self::copyFileContents($users_view, $fileToRestore, 'files' . $filename)) {
372
+            $files_view->touch($file, $revision);
373
+            Storage::scheduleExpire($user->getUID(), $file);
374
+
375
+            $node = $userFolder->get($file);
376
+
377
+            // TODO: move away from those legacy hooks!
378
+            \OC_Hook::emit('\OCP\Versions', 'rollback', [
379
+                'path' => $filename,
380
+                'revision' => $revision,
381
+                'node' => $node,
382
+            ]);
383
+            return true;
384
+        } elseif ($versionCreated) {
385
+            self::deleteVersion($users_view, $version);
386
+        }
387
+
388
+        return false;
389
+    }
390
+
391
+    /**
392
+     * Stream copy file contents from $path1 to $path2
393
+     *
394
+     * @param View $view view to use for copying
395
+     * @param string $path1 source file to copy
396
+     * @param string $path2 target file
397
+     *
398
+     * @return bool true for success, false otherwise
399
+     */
400
+    private static function copyFileContents($view, $path1, $path2) {
401
+        /** @var \OC\Files\Storage\Storage $storage1 */
402
+        [$storage1, $internalPath1] = $view->resolvePath($path1);
403
+        /** @var \OC\Files\Storage\Storage $storage2 */
404
+        [$storage2, $internalPath2] = $view->resolvePath($path2);
405
+
406
+        $view->lockFile($path1, ILockingProvider::LOCK_EXCLUSIVE);
407
+        $view->lockFile($path2, ILockingProvider::LOCK_EXCLUSIVE);
408
+
409
+        // TODO add a proper way of overwriting a file while maintaining file ids
410
+        if ($storage1->instanceOfStorage('\OC\Files\ObjectStore\ObjectStoreStorage') || $storage2->instanceOfStorage('\OC\Files\ObjectStore\ObjectStoreStorage')) {
411
+            $source = $storage1->fopen($internalPath1, 'r');
412
+            $target = $storage2->fopen($internalPath2, 'w');
413
+            [, $result] = \OC_Helper::streamCopy($source, $target);
414
+            fclose($source);
415
+            fclose($target);
416
+
417
+            if ($result !== false) {
418
+                $storage1->unlink($internalPath1);
419
+            }
420
+        } else {
421
+            $result = $storage2->moveFromStorage($storage1, $internalPath1, $internalPath2);
422
+        }
423
+
424
+        $view->unlockFile($path1, ILockingProvider::LOCK_EXCLUSIVE);
425
+        $view->unlockFile($path2, ILockingProvider::LOCK_EXCLUSIVE);
426
+
427
+        return ($result !== false);
428
+    }
429
+
430
+    /**
431
+     * get a list of all available versions of a file in descending chronological order
432
+     * @param string $uid user id from the owner of the file
433
+     * @param string $filename file to find versions of, relative to the user files dir
434
+     * @param string $userFullPath
435
+     * @return array versions newest version first
436
+     */
437
+    public static function getVersions($uid, $filename, $userFullPath = '') {
438
+        $versions = [];
439
+        if (empty($filename)) {
440
+            return $versions;
441
+        }
442
+        // fetch for old versions
443
+        $view = new View('/' . $uid . '/');
444
+
445
+        $pathinfo = pathinfo($filename);
446
+        $versionedFile = $pathinfo['basename'];
447
+
448
+        $dir = Filesystem::normalizePath(self::VERSIONS_ROOT . '/' . $pathinfo['dirname']);
449
+
450
+        $dirContent = false;
451
+        if ($view->is_dir($dir)) {
452
+            $dirContent = $view->opendir($dir);
453
+        }
454
+
455
+        if ($dirContent === false) {
456
+            return $versions;
457
+        }
458
+
459
+        if (is_resource($dirContent)) {
460
+            while (($entryName = readdir($dirContent)) !== false) {
461
+                if (!Filesystem::isIgnoredDir($entryName)) {
462
+                    $pathparts = pathinfo($entryName);
463
+                    $filename = $pathparts['filename'];
464
+                    if ($filename === $versionedFile) {
465
+                        $pathparts = pathinfo($entryName);
466
+                        $timestamp = substr($pathparts['extension'], 1);
467
+                        $filename = $pathparts['filename'];
468
+                        $key = $timestamp . '#' . $filename;
469
+                        $versions[$key]['version'] = $timestamp;
470
+                        $versions[$key]['humanReadableTimestamp'] = self::getHumanReadableTimestamp($timestamp);
471
+                        if (empty($userFullPath)) {
472
+                            $versions[$key]['preview'] = '';
473
+                        } else {
474
+                            $versions[$key]['preview'] = \OC::$server->getURLGenerator('files_version.Preview.getPreview', ['file' => $userFullPath, 'version' => $timestamp]);
475
+                        }
476
+                        $versions[$key]['path'] = Filesystem::normalizePath($pathinfo['dirname'] . '/' . $filename);
477
+                        $versions[$key]['name'] = $versionedFile;
478
+                        $versions[$key]['size'] = $view->filesize($dir . '/' . $entryName);
479
+                        $versions[$key]['mimetype'] = \OC::$server->getMimeTypeDetector()->detectPath($versionedFile);
480
+                    }
481
+                }
482
+            }
483
+            closedir($dirContent);
484
+        }
485
+
486
+        // sort with newest version first
487
+        krsort($versions);
488
+
489
+        return $versions;
490
+    }
491
+
492
+    /**
493
+     * Expire versions that older than max version retention time
494
+     * @param string $uid
495
+     */
496
+    public static function expireOlderThanMaxForUser($uid) {
497
+        $expiration = self::getExpiration();
498
+        $threshold = $expiration->getMaxAgeAsTimestamp();
499
+        $versions = self::getAllVersions($uid);
500
+        if (!$threshold || empty($versions['all'])) {
501
+            return;
502
+        }
503
+
504
+        $toDelete = [];
505
+        foreach (array_reverse($versions['all']) as $key => $version) {
506
+            if ((int)$version['version'] < $threshold) {
507
+                $toDelete[$key] = $version;
508
+            } else {
509
+                //Versions are sorted by time - nothing mo to iterate.
510
+                break;
511
+            }
512
+        }
513
+
514
+        $view = new View('/' . $uid . '/files_versions');
515
+        if (!empty($toDelete)) {
516
+            foreach ($toDelete as $version) {
517
+                \OC_Hook::emit('\OCP\Versions', 'preDelete', ['path' => $version['path'].'.v'.$version['version'], 'trigger' => self::DELETE_TRIGGER_RETENTION_CONSTRAINT]);
518
+                self::deleteVersion($view, $version['path'] . '.v' . $version['version']);
519
+                \OC_Hook::emit('\OCP\Versions', 'delete', ['path' => $version['path'].'.v'.$version['version'], 'trigger' => self::DELETE_TRIGGER_RETENTION_CONSTRAINT]);
520
+            }
521
+        }
522
+    }
523
+
524
+    /**
525
+     * translate a timestamp into a string like "5 days ago"
526
+     * @param int $timestamp
527
+     * @return string for example "5 days ago"
528
+     */
529
+    private static function getHumanReadableTimestamp($timestamp) {
530
+        $diff = time() - $timestamp;
531
+
532
+        if ($diff < 60) { // first minute
533
+            return  $diff . " seconds ago";
534
+        } elseif ($diff < 3600) { //first hour
535
+            return round($diff / 60) . " minutes ago";
536
+        } elseif ($diff < 86400) { // first day
537
+            return round($diff / 3600) . " hours ago";
538
+        } elseif ($diff < 604800) { //first week
539
+            return round($diff / 86400) . " days ago";
540
+        } elseif ($diff < 2419200) { //first month
541
+            return round($diff / 604800) . " weeks ago";
542
+        } elseif ($diff < 29030400) { // first year
543
+            return round($diff / 2419200) . " months ago";
544
+        } else {
545
+            return round($diff / 29030400) . " years ago";
546
+        }
547
+    }
548
+
549
+    /**
550
+     * returns all stored file versions from a given user
551
+     * @param string $uid id of the user
552
+     * @return array with contains two arrays 'all' which contains all versions sorted by age and 'by_file' which contains all versions sorted by filename
553
+     */
554
+    private static function getAllVersions($uid) {
555
+        $view = new View('/' . $uid . '/');
556
+        $dirs = [self::VERSIONS_ROOT];
557
+        $versions = [];
558
+
559
+        while (!empty($dirs)) {
560
+            $dir = array_pop($dirs);
561
+            $files = $view->getDirectoryContent($dir);
562
+
563
+            foreach ($files as $file) {
564
+                $fileData = $file->getData();
565
+                $filePath = $dir . '/' . $fileData['name'];
566
+                if ($file['type'] === 'dir') {
567
+                    $dirs[] = $filePath;
568
+                } else {
569
+                    $versionsBegin = strrpos($filePath, '.v');
570
+                    $relPathStart = strlen(self::VERSIONS_ROOT);
571
+                    $version = substr($filePath, $versionsBegin + 2);
572
+                    $relpath = substr($filePath, $relPathStart, $versionsBegin - $relPathStart);
573
+                    $key = $version . '#' . $relpath;
574
+                    $versions[$key] = ['path' => $relpath, 'timestamp' => $version];
575
+                }
576
+            }
577
+        }
578
+
579
+        // newest version first
580
+        krsort($versions);
581
+
582
+        $result = [
583
+            'all' => [],
584
+            'by_file' => [],
585
+        ];
586
+
587
+        foreach ($versions as $key => $value) {
588
+            $size = $view->filesize(self::VERSIONS_ROOT.'/'.$value['path'].'.v'.$value['timestamp']);
589
+            $filename = $value['path'];
590
+
591
+            $result['all'][$key]['version'] = $value['timestamp'];
592
+            $result['all'][$key]['path'] = $filename;
593
+            $result['all'][$key]['size'] = $size;
594
+
595
+            $result['by_file'][$filename][$key]['version'] = $value['timestamp'];
596
+            $result['by_file'][$filename][$key]['path'] = $filename;
597
+            $result['by_file'][$filename][$key]['size'] = $size;
598
+        }
599
+
600
+        return $result;
601
+    }
602
+
603
+    /**
604
+     * get list of files we want to expire
605
+     * @param array $versions list of versions
606
+     * @param integer $time
607
+     * @param bool $quotaExceeded is versions storage limit reached
608
+     * @return array containing the list of to deleted versions and the size of them
609
+     */
610
+    protected static function getExpireList($time, $versions, $quotaExceeded = false) {
611
+        $expiration = self::getExpiration();
612
+
613
+        if ($expiration->shouldAutoExpire()) {
614
+            [$toDelete, $size] = self::getAutoExpireList($time, $versions);
615
+        } else {
616
+            $size = 0;
617
+            $toDelete = [];  // versions we want to delete
618
+        }
619
+
620
+        foreach ($versions as $key => $version) {
621
+            if ($expiration->isExpired($version['version'], $quotaExceeded) && !isset($toDelete[$key])) {
622
+                $size += $version['size'];
623
+                $toDelete[$key] = $version['path'] . '.v' . $version['version'];
624
+            }
625
+        }
626
+
627
+        return [$toDelete, $size];
628
+    }
629
+
630
+    /**
631
+     * get list of files we want to expire
632
+     * @param array $versions list of versions
633
+     * @param integer $time
634
+     * @return array containing the list of to deleted versions and the size of them
635
+     */
636
+    protected static function getAutoExpireList($time, $versions) {
637
+        $size = 0;
638
+        $toDelete = [];  // versions we want to delete
639
+
640
+        $interval = 1;
641
+        $step = Storage::$max_versions_per_interval[$interval]['step'];
642
+        if (Storage::$max_versions_per_interval[$interval]['intervalEndsAfter'] === -1) {
643
+            $nextInterval = -1;
644
+        } else {
645
+            $nextInterval = $time - Storage::$max_versions_per_interval[$interval]['intervalEndsAfter'];
646
+        }
647
+
648
+        $firstVersion = reset($versions);
649
+
650
+        if ($firstVersion === false) {
651
+            return [$toDelete, $size];
652
+        }
653
+
654
+        $firstKey = key($versions);
655
+        $prevTimestamp = $firstVersion['version'];
656
+        $nextVersion = $firstVersion['version'] - $step;
657
+        unset($versions[$firstKey]);
658
+
659
+        foreach ($versions as $key => $version) {
660
+            $newInterval = true;
661
+            while ($newInterval) {
662
+                if ($nextInterval === -1 || $prevTimestamp > $nextInterval) {
663
+                    if ($version['version'] > $nextVersion) {
664
+                        //distance between two version too small, mark to delete
665
+                        $toDelete[$key] = $version['path'] . '.v' . $version['version'];
666
+                        $size += $version['size'];
667
+                        \OC::$server->getLogger()->info('Mark to expire '. $version['path'] .' next version should be ' . $nextVersion . " or smaller. (prevTimestamp: " . $prevTimestamp . "; step: " . $step, ['app' => 'files_versions']);
668
+                    } else {
669
+                        $nextVersion = $version['version'] - $step;
670
+                        $prevTimestamp = $version['version'];
671
+                    }
672
+                    $newInterval = false; // version checked so we can move to the next one
673
+                } else { // time to move on to the next interval
674
+                    $interval++;
675
+                    $step = Storage::$max_versions_per_interval[$interval]['step'];
676
+                    $nextVersion = $prevTimestamp - $step;
677
+                    if (Storage::$max_versions_per_interval[$interval]['intervalEndsAfter'] === -1) {
678
+                        $nextInterval = -1;
679
+                    } else {
680
+                        $nextInterval = $time - Storage::$max_versions_per_interval[$interval]['intervalEndsAfter'];
681
+                    }
682
+                    $newInterval = true; // we changed the interval -> check same version with new interval
683
+                }
684
+            }
685
+        }
686
+
687
+        return [$toDelete, $size];
688
+    }
689
+
690
+    /**
691
+     * Schedule versions expiration for the given file
692
+     *
693
+     * @param string $uid owner of the file
694
+     * @param string $fileName file/folder for which to schedule expiration
695
+     */
696
+    public static function scheduleExpire($uid, $fileName) {
697
+        // let the admin disable auto expire
698
+        $expiration = self::getExpiration();
699
+        if ($expiration->isEnabled()) {
700
+            $command = new Expire($uid, $fileName);
701
+            \OC::$server->getCommandBus()->push($command);
702
+        }
703
+    }
704
+
705
+    /**
706
+     * Expire versions which exceed the quota.
707
+     *
708
+     * This will setup the filesystem for the given user but will not
709
+     * tear it down afterwards.
710
+     *
711
+     * @param string $filename path to file to expire
712
+     * @param string $uid user for which to expire the version
713
+     * @return bool|int|null
714
+     */
715
+    public static function expire($filename, $uid) {
716
+        $expiration = self::getExpiration();
717
+
718
+        if ($expiration->isEnabled()) {
719
+            // get available disk space for user
720
+            $user = \OC::$server->getUserManager()->get($uid);
721
+            if (is_null($user)) {
722
+                \OC::$server->getLogger()->error('Backends provided no user object for ' . $uid, ['app' => 'files_versions']);
723
+                throw new \OC\User\NoUserException('Backends provided no user object for ' . $uid);
724
+            }
725
+
726
+            \OC_Util::setupFS($uid);
727
+
728
+            try {
729
+                if (!Filesystem::file_exists($filename)) {
730
+                    return false;
731
+                }
732
+            } catch (StorageNotAvailableException $e) {
733
+                // if we can't check that the file hasn't been deleted we can only assume that it hasn't
734
+                // note that this `StorageNotAvailableException` is about the file the versions originate from,
735
+                // not the storage that the versions are stored on
736
+            }
737
+
738
+            if (empty($filename)) {
739
+                // file maybe renamed or deleted
740
+                return false;
741
+            }
742
+            $versionsFileview = new View('/'.$uid.'/files_versions');
743
+
744
+            $softQuota = true;
745
+            $quota = $user->getQuota();
746
+            if ($quota === null || $quota === 'none') {
747
+                $quota = Filesystem::free_space('/');
748
+                $softQuota = false;
749
+            } else {
750
+                $quota = \OCP\Util::computerFileSize($quota);
751
+            }
752
+
753
+            // make sure that we have the current size of the version history
754
+            $versionsSize = self::getVersionsSize($uid);
755
+
756
+            // calculate available space for version history
757
+            // subtract size of files and current versions size from quota
758
+            if ($quota >= 0) {
759
+                if ($softQuota) {
760
+                    $userFolder = \OC::$server->getUserFolder($uid);
761
+                    if (is_null($userFolder)) {
762
+                        $availableSpace = 0;
763
+                    } else {
764
+                        $free = $quota - $userFolder->getSize(false); // remaining free space for user
765
+                        if ($free > 0) {
766
+                            $availableSpace = ($free * self::DEFAULTMAXSIZE / 100) - $versionsSize; // how much space can be used for versions
767
+                        } else {
768
+                            $availableSpace = $free - $versionsSize;
769
+                        }
770
+                    }
771
+                } else {
772
+                    $availableSpace = $quota;
773
+                }
774
+            } else {
775
+                $availableSpace = PHP_INT_MAX;
776
+            }
777
+
778
+            $allVersions = Storage::getVersions($uid, $filename);
779
+
780
+            $time = time();
781
+            [$toDelete, $sizeOfDeletedVersions] = self::getExpireList($time, $allVersions, $availableSpace <= 0);
782
+
783
+            $availableSpace = $availableSpace + $sizeOfDeletedVersions;
784
+            $versionsSize = $versionsSize - $sizeOfDeletedVersions;
785
+
786
+            // if still not enough free space we rearrange the versions from all files
787
+            if ($availableSpace <= 0) {
788
+                $result = self::getAllVersions($uid);
789
+                $allVersions = $result['all'];
790
+
791
+                foreach ($result['by_file'] as $versions) {
792
+                    [$toDeleteNew, $size] = self::getExpireList($time, $versions, $availableSpace <= 0);
793
+                    $toDelete = array_merge($toDelete, $toDeleteNew);
794
+                    $sizeOfDeletedVersions += $size;
795
+                }
796
+                $availableSpace = $availableSpace + $sizeOfDeletedVersions;
797
+                $versionsSize = $versionsSize - $sizeOfDeletedVersions;
798
+            }
799
+
800
+            $logger = \OC::$server->getLogger();
801
+            foreach ($toDelete as $key => $path) {
802
+                \OC_Hook::emit('\OCP\Versions', 'preDelete', ['path' => $path, 'trigger' => self::DELETE_TRIGGER_QUOTA_EXCEEDED]);
803
+                self::deleteVersion($versionsFileview, $path);
804
+                \OC_Hook::emit('\OCP\Versions', 'delete', ['path' => $path, 'trigger' => self::DELETE_TRIGGER_QUOTA_EXCEEDED]);
805
+                unset($allVersions[$key]); // update array with the versions we keep
806
+                $logger->info('Expire: ' . $path, ['app' => 'files_versions']);
807
+            }
808
+
809
+            // Check if enough space is available after versions are rearranged.
810
+            // If not we delete the oldest versions until we meet the size limit for versions,
811
+            // but always keep the two latest versions
812
+            $numOfVersions = count($allVersions) - 2 ;
813
+            $i = 0;
814
+            // sort oldest first and make sure that we start at the first element
815
+            ksort($allVersions);
816
+            reset($allVersions);
817
+            while ($availableSpace < 0 && $i < $numOfVersions) {
818
+                $version = current($allVersions);
819
+                \OC_Hook::emit('\OCP\Versions', 'preDelete', ['path' => $version['path'].'.v'.$version['version'], 'trigger' => self::DELETE_TRIGGER_QUOTA_EXCEEDED]);
820
+                self::deleteVersion($versionsFileview, $version['path'] . '.v' . $version['version']);
821
+                \OC_Hook::emit('\OCP\Versions', 'delete', ['path' => $version['path'].'.v'.$version['version'], 'trigger' => self::DELETE_TRIGGER_QUOTA_EXCEEDED]);
822
+                \OC::$server->getLogger()->info('running out of space! Delete oldest version: ' . $version['path'].'.v'.$version['version'], ['app' => 'files_versions']);
823
+                $versionsSize -= $version['size'];
824
+                $availableSpace += $version['size'];
825
+                next($allVersions);
826
+                $i++;
827
+            }
828
+
829
+            return $versionsSize; // finally return the new size of the version history
830
+        }
831
+
832
+        return false;
833
+    }
834
+
835
+    /**
836
+     * Create recursively missing directories inside of files_versions
837
+     * that match the given path to a file.
838
+     *
839
+     * @param string $filename $path to a file, relative to the user's
840
+     * "files" folder
841
+     * @param View $view view on data/user/
842
+     */
843
+    public static function createMissingDirectories($filename, $view) {
844
+        $dirname = Filesystem::normalizePath(dirname($filename));
845
+        $dirParts = explode('/', $dirname);
846
+        $dir = "/files_versions";
847
+        foreach ($dirParts as $part) {
848
+            $dir = $dir . '/' . $part;
849
+            if (!$view->file_exists($dir)) {
850
+                $view->mkdir($dir);
851
+            }
852
+        }
853
+    }
854
+
855
+    /**
856
+     * Static workaround
857
+     * @return Expiration
858
+     */
859
+    protected static function getExpiration() {
860
+        if (self::$application === null) {
861
+            self::$application = \OC::$server->query(Application::class);
862
+        }
863
+        return self::$application->getContainer()->query(Expiration::class);
864
+    }
865 865
 }
Please login to merge, or discard this patch.
apps/files_sharing/lib/Updater.php 2 patches
Indentation   +65 added lines, -65 removed lines patch added patch discarded remove patch
@@ -31,80 +31,80 @@
 block discarded – undo
31 31
 
32 32
 class Updater {
33 33
 
34
-	/**
35
-	 * @param array $params
36
-	 */
37
-	public static function renameHook($params) {
38
-		self::renameChildren($params['oldpath'], $params['newpath']);
39
-		self::moveShareToShare($params['newpath']);
40
-	}
34
+    /**
35
+     * @param array $params
36
+     */
37
+    public static function renameHook($params) {
38
+        self::renameChildren($params['oldpath'], $params['newpath']);
39
+        self::moveShareToShare($params['newpath']);
40
+    }
41 41
 
42
-	/**
43
-	 * Fix for https://github.com/owncloud/core/issues/20769
44
-	 *
45
-	 * The owner is allowed to move their files (if they are shared) into a receiving folder
46
-	 * In this case we need to update the parent of the moved share. Since they are
47
-	 * effectively handing over ownership of the file the rest of the code needs to know
48
-	 * they need to build up the reshare tree.
49
-	 *
50
-	 * @param string $path
51
-	 */
52
-	private static function moveShareToShare($path) {
53
-		$userFolder = \OC::$server->getUserFolder();
42
+    /**
43
+     * Fix for https://github.com/owncloud/core/issues/20769
44
+     *
45
+     * The owner is allowed to move their files (if they are shared) into a receiving folder
46
+     * In this case we need to update the parent of the moved share. Since they are
47
+     * effectively handing over ownership of the file the rest of the code needs to know
48
+     * they need to build up the reshare tree.
49
+     *
50
+     * @param string $path
51
+     */
52
+    private static function moveShareToShare($path) {
53
+        $userFolder = \OC::$server->getUserFolder();
54 54
 
55
-		// If the user folder can't be constructed (e.g. link share) just return.
56
-		if ($userFolder === null) {
57
-			return;
58
-		}
55
+        // If the user folder can't be constructed (e.g. link share) just return.
56
+        if ($userFolder === null) {
57
+            return;
58
+        }
59 59
 
60
-		$src = $userFolder->get($path);
60
+        $src = $userFolder->get($path);
61 61
 
62
-		$shareManager = \OC::$server->getShareManager();
62
+        $shareManager = \OC::$server->getShareManager();
63 63
 
64
-		$shares = $shareManager->getSharesBy($userFolder->getOwner()->getUID(), IShare::TYPE_USER, $src, false, -1);
65
-		$shares = array_merge($shares, $shareManager->getSharesBy($userFolder->getOwner()->getUID(), IShare::TYPE_GROUP, $src, false, -1));
66
-		$shares = array_merge($shares, $shareManager->getSharesBy($userFolder->getOwner()->getUID(), IShare::TYPE_ROOM, $src, false, -1));
64
+        $shares = $shareManager->getSharesBy($userFolder->getOwner()->getUID(), IShare::TYPE_USER, $src, false, -1);
65
+        $shares = array_merge($shares, $shareManager->getSharesBy($userFolder->getOwner()->getUID(), IShare::TYPE_GROUP, $src, false, -1));
66
+        $shares = array_merge($shares, $shareManager->getSharesBy($userFolder->getOwner()->getUID(), IShare::TYPE_ROOM, $src, false, -1));
67 67
 
68
-		// If the path we move is not a share we don't care
69
-		if (empty($shares)) {
70
-			return;
71
-		}
68
+        // If the path we move is not a share we don't care
69
+        if (empty($shares)) {
70
+            return;
71
+        }
72 72
 
73
-		// Check if the destination is inside a share
74
-		$mountManager = \OC::$server->getMountManager();
75
-		$dstMount = $mountManager->find($src->getPath());
76
-		if (!($dstMount instanceof \OCA\Files_Sharing\SharedMount)) {
77
-			return;
78
-		}
73
+        // Check if the destination is inside a share
74
+        $mountManager = \OC::$server->getMountManager();
75
+        $dstMount = $mountManager->find($src->getPath());
76
+        if (!($dstMount instanceof \OCA\Files_Sharing\SharedMount)) {
77
+            return;
78
+        }
79 79
 
80
-		$newOwner = $dstMount->getShare()->getShareOwner();
80
+        $newOwner = $dstMount->getShare()->getShareOwner();
81 81
 
82
-		//Ownership is moved over
83
-		foreach ($shares as $share) {
84
-			/** @var IShare $share */
85
-			$share->setShareOwner($newOwner);
86
-			$shareManager->updateShare($share);
87
-		}
88
-	}
82
+        //Ownership is moved over
83
+        foreach ($shares as $share) {
84
+            /** @var IShare $share */
85
+            $share->setShareOwner($newOwner);
86
+            $shareManager->updateShare($share);
87
+        }
88
+    }
89 89
 
90
-	/**
91
-	 * rename mount point from the children if the parent was renamed
92
-	 *
93
-	 * @param string $oldPath old path relative to data/user/files
94
-	 * @param string $newPath new path relative to data/user/files
95
-	 */
96
-	private static function renameChildren($oldPath, $newPath) {
97
-		$absNewPath = \OC\Files\Filesystem::normalizePath('/' . \OC_User::getUser() . '/files/' . $newPath);
98
-		$absOldPath = \OC\Files\Filesystem::normalizePath('/' . \OC_User::getUser() . '/files/' . $oldPath);
90
+    /**
91
+     * rename mount point from the children if the parent was renamed
92
+     *
93
+     * @param string $oldPath old path relative to data/user/files
94
+     * @param string $newPath new path relative to data/user/files
95
+     */
96
+    private static function renameChildren($oldPath, $newPath) {
97
+        $absNewPath = \OC\Files\Filesystem::normalizePath('/' . \OC_User::getUser() . '/files/' . $newPath);
98
+        $absOldPath = \OC\Files\Filesystem::normalizePath('/' . \OC_User::getUser() . '/files/' . $oldPath);
99 99
 
100
-		$mountManager = \OC\Files\Filesystem::getMountManager();
101
-		$mountedShares = $mountManager->findIn('/' . \OC_User::getUser() . '/files/' . $oldPath);
102
-		foreach ($mountedShares as $mount) {
103
-			if ($mount->getStorage()->instanceOfStorage(ISharedStorage::class)) {
104
-				$mountPoint = $mount->getMountPoint();
105
-				$target = str_replace($absOldPath, $absNewPath, $mountPoint);
106
-				$mount->moveMount($target);
107
-			}
108
-		}
109
-	}
100
+        $mountManager = \OC\Files\Filesystem::getMountManager();
101
+        $mountedShares = $mountManager->findIn('/' . \OC_User::getUser() . '/files/' . $oldPath);
102
+        foreach ($mountedShares as $mount) {
103
+            if ($mount->getStorage()->instanceOfStorage(ISharedStorage::class)) {
104
+                $mountPoint = $mount->getMountPoint();
105
+                $target = str_replace($absOldPath, $absNewPath, $mountPoint);
106
+                $mount->moveMount($target);
107
+            }
108
+        }
109
+    }
110 110
 }
Please login to merge, or discard this patch.
Spacing   +3 added lines, -3 removed lines patch added patch discarded remove patch
@@ -94,11 +94,11 @@
 block discarded – undo
94 94
 	 * @param string $newPath new path relative to data/user/files
95 95
 	 */
96 96
 	private static function renameChildren($oldPath, $newPath) {
97
-		$absNewPath = \OC\Files\Filesystem::normalizePath('/' . \OC_User::getUser() . '/files/' . $newPath);
98
-		$absOldPath = \OC\Files\Filesystem::normalizePath('/' . \OC_User::getUser() . '/files/' . $oldPath);
97
+		$absNewPath = \OC\Files\Filesystem::normalizePath('/'.\OC_User::getUser().'/files/'.$newPath);
98
+		$absOldPath = \OC\Files\Filesystem::normalizePath('/'.\OC_User::getUser().'/files/'.$oldPath);
99 99
 
100 100
 		$mountManager = \OC\Files\Filesystem::getMountManager();
101
-		$mountedShares = $mountManager->findIn('/' . \OC_User::getUser() . '/files/' . $oldPath);
101
+		$mountedShares = $mountManager->findIn('/'.\OC_User::getUser().'/files/'.$oldPath);
102 102
 		foreach ($mountedShares as $mount) {
103 103
 			if ($mount->getStorage()->instanceOfStorage(ISharedStorage::class)) {
104 104
 				$mountPoint = $mount->getMountPoint();
Please login to merge, or discard this patch.
apps/files/ajax/download.php 1 patch
Spacing   +3 added lines, -3 removed lines patch added patch discarded remove patch
@@ -32,8 +32,8 @@  discard block
 block discarded – undo
32 32
 OC_Util::checkLoggedIn();
33 33
 \OC::$server->getSession()->close();
34 34
 
35
-$files = isset($_GET['files']) ? (string)$_GET['files'] : '';
36
-$dir = isset($_GET['dir']) ? (string)$_GET['dir'] : '';
35
+$files = isset($_GET['files']) ? (string) $_GET['files'] : '';
36
+$dir = isset($_GET['dir']) ? (string) $_GET['dir'] : '';
37 37
 
38 38
 $files_list = json_decode($files);
39 39
 // in case we get only a single file
@@ -66,7 +66,7 @@  discard block
 block discarded – undo
66 66
 	}
67 67
 }
68 68
 
69
-$server_params = [ 'head' => \OC::$server->getRequest()->getMethod() === 'HEAD' ];
69
+$server_params = ['head' => \OC::$server->getRequest()->getMethod() === 'HEAD'];
70 70
 
71 71
 /**
72 72
  * Http range requests support
Please login to merge, or discard this patch.
lib/private/Files/View.php 2 patches
Indentation   +2120 added lines, -2120 removed lines patch added patch discarded remove patch
@@ -85,2124 +85,2124 @@
 block discarded – undo
85 85
  * \OC\Files\Storage\Storage object
86 86
  */
87 87
 class View {
88
-	/** @var string */
89
-	private $fakeRoot = '';
90
-
91
-	/**
92
-	 * @var \OCP\Lock\ILockingProvider
93
-	 */
94
-	protected $lockingProvider;
95
-
96
-	private $lockingEnabled;
97
-
98
-	private $updaterEnabled = true;
99
-
100
-	/** @var \OC\User\Manager */
101
-	private $userManager;
102
-
103
-	/** @var \OCP\ILogger */
104
-	private $logger;
105
-
106
-	/**
107
-	 * @param string $root
108
-	 * @throws \Exception If $root contains an invalid path
109
-	 */
110
-	public function __construct($root = '') {
111
-		if (is_null($root)) {
112
-			throw new \InvalidArgumentException('Root can\'t be null');
113
-		}
114
-		if (!Filesystem::isValidPath($root)) {
115
-			throw new \Exception();
116
-		}
117
-
118
-		$this->fakeRoot = $root;
119
-		$this->lockingProvider = \OC::$server->getLockingProvider();
120
-		$this->lockingEnabled = !($this->lockingProvider instanceof \OC\Lock\NoopLockingProvider);
121
-		$this->userManager = \OC::$server->getUserManager();
122
-		$this->logger = \OC::$server->getLogger();
123
-	}
124
-
125
-	public function getAbsolutePath($path = '/') {
126
-		if ($path === null) {
127
-			return null;
128
-		}
129
-		$this->assertPathLength($path);
130
-		if ($path === '') {
131
-			$path = '/';
132
-		}
133
-		if ($path[0] !== '/') {
134
-			$path = '/' . $path;
135
-		}
136
-		return $this->fakeRoot . $path;
137
-	}
138
-
139
-	/**
140
-	 * change the root to a fake root
141
-	 *
142
-	 * @param string $fakeRoot
143
-	 * @return boolean|null
144
-	 */
145
-	public function chroot($fakeRoot) {
146
-		if (!$fakeRoot == '') {
147
-			if ($fakeRoot[0] !== '/') {
148
-				$fakeRoot = '/' . $fakeRoot;
149
-			}
150
-		}
151
-		$this->fakeRoot = $fakeRoot;
152
-	}
153
-
154
-	/**
155
-	 * get the fake root
156
-	 *
157
-	 * @return string
158
-	 */
159
-	public function getRoot() {
160
-		return $this->fakeRoot;
161
-	}
162
-
163
-	/**
164
-	 * get path relative to the root of the view
165
-	 *
166
-	 * @param string $path
167
-	 * @return string
168
-	 */
169
-	public function getRelativePath($path) {
170
-		$this->assertPathLength($path);
171
-		if ($this->fakeRoot == '') {
172
-			return $path;
173
-		}
174
-
175
-		if (rtrim($path, '/') === rtrim($this->fakeRoot, '/')) {
176
-			return '/';
177
-		}
178
-
179
-		// missing slashes can cause wrong matches!
180
-		$root = rtrim($this->fakeRoot, '/') . '/';
181
-
182
-		if (strpos($path, $root) !== 0) {
183
-			return null;
184
-		} else {
185
-			$path = substr($path, strlen($this->fakeRoot));
186
-			if (strlen($path) === 0) {
187
-				return '/';
188
-			} else {
189
-				return $path;
190
-			}
191
-		}
192
-	}
193
-
194
-	/**
195
-	 * get the mountpoint of the storage object for a path
196
-	 * ( note: because a storage is not always mounted inside the fakeroot, the
197
-	 * returned mountpoint is relative to the absolute root of the filesystem
198
-	 * and does not take the chroot into account )
199
-	 *
200
-	 * @param string $path
201
-	 * @return string
202
-	 */
203
-	public function getMountPoint($path) {
204
-		return Filesystem::getMountPoint($this->getAbsolutePath($path));
205
-	}
206
-
207
-	/**
208
-	 * get the mountpoint of the storage object for a path
209
-	 * ( note: because a storage is not always mounted inside the fakeroot, the
210
-	 * returned mountpoint is relative to the absolute root of the filesystem
211
-	 * and does not take the chroot into account )
212
-	 *
213
-	 * @param string $path
214
-	 * @return \OCP\Files\Mount\IMountPoint
215
-	 */
216
-	public function getMount($path) {
217
-		return Filesystem::getMountManager()->find($this->getAbsolutePath($path));
218
-	}
219
-
220
-	/**
221
-	 * resolve a path to a storage and internal path
222
-	 *
223
-	 * @param string $path
224
-	 * @return array an array consisting of the storage and the internal path
225
-	 */
226
-	public function resolvePath($path) {
227
-		$a = $this->getAbsolutePath($path);
228
-		$p = Filesystem::normalizePath($a);
229
-		return Filesystem::resolvePath($p);
230
-	}
231
-
232
-	/**
233
-	 * return the path to a local version of the file
234
-	 * we need this because we can't know if a file is stored local or not from
235
-	 * outside the filestorage and for some purposes a local file is needed
236
-	 *
237
-	 * @param string $path
238
-	 * @return string
239
-	 */
240
-	public function getLocalFile($path) {
241
-		$parent = substr($path, 0, strrpos($path, '/'));
242
-		$path = $this->getAbsolutePath($path);
243
-		[$storage, $internalPath] = Filesystem::resolvePath($path);
244
-		if (Filesystem::isValidPath($parent) and $storage) {
245
-			return $storage->getLocalFile($internalPath);
246
-		} else {
247
-			return null;
248
-		}
249
-	}
250
-
251
-	/**
252
-	 * @param string $path
253
-	 * @return string
254
-	 */
255
-	public function getLocalFolder($path) {
256
-		$parent = substr($path, 0, strrpos($path, '/'));
257
-		$path = $this->getAbsolutePath($path);
258
-		[$storage, $internalPath] = Filesystem::resolvePath($path);
259
-		if (Filesystem::isValidPath($parent) and $storage) {
260
-			return $storage->getLocalFolder($internalPath);
261
-		} else {
262
-			return null;
263
-		}
264
-	}
265
-
266
-	/**
267
-	 * the following functions operate with arguments and return values identical
268
-	 * to those of their PHP built-in equivalents. Mostly they are merely wrappers
269
-	 * for \OC\Files\Storage\Storage via basicOperation().
270
-	 */
271
-	public function mkdir($path) {
272
-		return $this->basicOperation('mkdir', $path, ['create', 'write']);
273
-	}
274
-
275
-	/**
276
-	 * remove mount point
277
-	 *
278
-	 * @param \OC\Files\Mount\MoveableMount $mount
279
-	 * @param string $path relative to data/
280
-	 * @return boolean
281
-	 */
282
-	protected function removeMount($mount, $path) {
283
-		if ($mount instanceof MoveableMount) {
284
-			// cut of /user/files to get the relative path to data/user/files
285
-			$pathParts = explode('/', $path, 4);
286
-			$relPath = '/' . $pathParts[3];
287
-			$this->lockFile($relPath, ILockingProvider::LOCK_SHARED, true);
288
-			\OC_Hook::emit(
289
-				Filesystem::CLASSNAME, "umount",
290
-				[Filesystem::signal_param_path => $relPath]
291
-			);
292
-			$this->changeLock($relPath, ILockingProvider::LOCK_EXCLUSIVE, true);
293
-			$result = $mount->removeMount();
294
-			$this->changeLock($relPath, ILockingProvider::LOCK_SHARED, true);
295
-			if ($result) {
296
-				\OC_Hook::emit(
297
-					Filesystem::CLASSNAME, "post_umount",
298
-					[Filesystem::signal_param_path => $relPath]
299
-				);
300
-			}
301
-			$this->unlockFile($relPath, ILockingProvider::LOCK_SHARED, true);
302
-			return $result;
303
-		} else {
304
-			// do not allow deleting the storage's root / the mount point
305
-			// because for some storages it might delete the whole contents
306
-			// but isn't supposed to work that way
307
-			return false;
308
-		}
309
-	}
310
-
311
-	public function disableCacheUpdate() {
312
-		$this->updaterEnabled = false;
313
-	}
314
-
315
-	public function enableCacheUpdate() {
316
-		$this->updaterEnabled = true;
317
-	}
318
-
319
-	protected function writeUpdate(Storage $storage, $internalPath, $time = null) {
320
-		if ($this->updaterEnabled) {
321
-			if (is_null($time)) {
322
-				$time = time();
323
-			}
324
-			$storage->getUpdater()->update($internalPath, $time);
325
-		}
326
-	}
327
-
328
-	protected function removeUpdate(Storage $storage, $internalPath) {
329
-		if ($this->updaterEnabled) {
330
-			$storage->getUpdater()->remove($internalPath);
331
-		}
332
-	}
333
-
334
-	protected function renameUpdate(Storage $sourceStorage, Storage $targetStorage, $sourceInternalPath, $targetInternalPath) {
335
-		if ($this->updaterEnabled) {
336
-			$targetStorage->getUpdater()->renameFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
337
-		}
338
-	}
339
-
340
-	/**
341
-	 * @param string $path
342
-	 * @return bool|mixed
343
-	 */
344
-	public function rmdir($path) {
345
-		$absolutePath = $this->getAbsolutePath($path);
346
-		$mount = Filesystem::getMountManager()->find($absolutePath);
347
-		if ($mount->getInternalPath($absolutePath) === '') {
348
-			return $this->removeMount($mount, $absolutePath);
349
-		}
350
-		if ($this->is_dir($path)) {
351
-			$result = $this->basicOperation('rmdir', $path, ['delete']);
352
-		} else {
353
-			$result = false;
354
-		}
355
-
356
-		if (!$result && !$this->file_exists($path)) { //clear ghost files from the cache on delete
357
-			$storage = $mount->getStorage();
358
-			$internalPath = $mount->getInternalPath($absolutePath);
359
-			$storage->getUpdater()->remove($internalPath);
360
-		}
361
-		return $result;
362
-	}
363
-
364
-	/**
365
-	 * @param string $path
366
-	 * @return resource
367
-	 */
368
-	public function opendir($path) {
369
-		return $this->basicOperation('opendir', $path, ['read']);
370
-	}
371
-
372
-	/**
373
-	 * @param string $path
374
-	 * @return bool|mixed
375
-	 */
376
-	public function is_dir($path) {
377
-		if ($path == '/') {
378
-			return true;
379
-		}
380
-		return $this->basicOperation('is_dir', $path);
381
-	}
382
-
383
-	/**
384
-	 * @param string $path
385
-	 * @return bool|mixed
386
-	 */
387
-	public function is_file($path) {
388
-		if ($path == '/') {
389
-			return false;
390
-		}
391
-		return $this->basicOperation('is_file', $path);
392
-	}
393
-
394
-	/**
395
-	 * @param string $path
396
-	 * @return mixed
397
-	 */
398
-	public function stat($path) {
399
-		return $this->basicOperation('stat', $path);
400
-	}
401
-
402
-	/**
403
-	 * @param string $path
404
-	 * @return mixed
405
-	 */
406
-	public function filetype($path) {
407
-		return $this->basicOperation('filetype', $path);
408
-	}
409
-
410
-	/**
411
-	 * @param string $path
412
-	 * @return mixed
413
-	 */
414
-	public function filesize($path) {
415
-		return $this->basicOperation('filesize', $path);
416
-	}
417
-
418
-	/**
419
-	 * @param string $path
420
-	 * @return bool|mixed
421
-	 * @throws \OCP\Files\InvalidPathException
422
-	 */
423
-	public function readfile($path) {
424
-		$this->assertPathLength($path);
425
-		@ob_end_clean();
426
-		$handle = $this->fopen($path, 'rb');
427
-		if ($handle) {
428
-			$chunkSize = 524288; // 512 kB chunks
429
-			while (!feof($handle)) {
430
-				echo fread($handle, $chunkSize);
431
-				flush();
432
-			}
433
-			fclose($handle);
434
-			return $this->filesize($path);
435
-		}
436
-		return false;
437
-	}
438
-
439
-	/**
440
-	 * @param string $path
441
-	 * @param int $from
442
-	 * @param int $to
443
-	 * @return bool|mixed
444
-	 * @throws \OCP\Files\InvalidPathException
445
-	 * @throws \OCP\Files\UnseekableException
446
-	 */
447
-	public function readfilePart($path, $from, $to) {
448
-		$this->assertPathLength($path);
449
-		@ob_end_clean();
450
-		$handle = $this->fopen($path, 'rb');
451
-		if ($handle) {
452
-			$chunkSize = 524288; // 512 kB chunks
453
-			$startReading = true;
454
-
455
-			if ($from !== 0 && $from !== '0' && fseek($handle, $from) !== 0) {
456
-				// forward file handle via chunked fread because fseek seem to have failed
457
-
458
-				$end = $from + 1;
459
-				while (!feof($handle) && ftell($handle) < $end && ftell($handle) !== $from) {
460
-					$len = $from - ftell($handle);
461
-					if ($len > $chunkSize) {
462
-						$len = $chunkSize;
463
-					}
464
-					$result = fread($handle, $len);
465
-
466
-					if ($result === false) {
467
-						$startReading = false;
468
-						break;
469
-					}
470
-				}
471
-			}
472
-
473
-			if ($startReading) {
474
-				$end = $to + 1;
475
-				while (!feof($handle) && ftell($handle) < $end) {
476
-					$len = $end - ftell($handle);
477
-					if ($len > $chunkSize) {
478
-						$len = $chunkSize;
479
-					}
480
-					echo fread($handle, $len);
481
-					flush();
482
-				}
483
-				return ftell($handle) - $from;
484
-			}
485
-
486
-			throw new \OCP\Files\UnseekableException('fseek error');
487
-		}
488
-		return false;
489
-	}
490
-
491
-	/**
492
-	 * @param string $path
493
-	 * @return mixed
494
-	 */
495
-	public function isCreatable($path) {
496
-		return $this->basicOperation('isCreatable', $path);
497
-	}
498
-
499
-	/**
500
-	 * @param string $path
501
-	 * @return mixed
502
-	 */
503
-	public function isReadable($path) {
504
-		return $this->basicOperation('isReadable', $path);
505
-	}
506
-
507
-	/**
508
-	 * @param string $path
509
-	 * @return mixed
510
-	 */
511
-	public function isUpdatable($path) {
512
-		return $this->basicOperation('isUpdatable', $path);
513
-	}
514
-
515
-	/**
516
-	 * @param string $path
517
-	 * @return bool|mixed
518
-	 */
519
-	public function isDeletable($path) {
520
-		$absolutePath = $this->getAbsolutePath($path);
521
-		$mount = Filesystem::getMountManager()->find($absolutePath);
522
-		if ($mount->getInternalPath($absolutePath) === '') {
523
-			return $mount instanceof MoveableMount;
524
-		}
525
-		return $this->basicOperation('isDeletable', $path);
526
-	}
527
-
528
-	/**
529
-	 * @param string $path
530
-	 * @return mixed
531
-	 */
532
-	public function isSharable($path) {
533
-		return $this->basicOperation('isSharable', $path);
534
-	}
535
-
536
-	/**
537
-	 * @param string $path
538
-	 * @return bool|mixed
539
-	 */
540
-	public function file_exists($path) {
541
-		if ($path == '/') {
542
-			return true;
543
-		}
544
-		return $this->basicOperation('file_exists', $path);
545
-	}
546
-
547
-	/**
548
-	 * @param string $path
549
-	 * @return mixed
550
-	 */
551
-	public function filemtime($path) {
552
-		return $this->basicOperation('filemtime', $path);
553
-	}
554
-
555
-	/**
556
-	 * @param string $path
557
-	 * @param int|string $mtime
558
-	 * @return bool
559
-	 */
560
-	public function touch($path, $mtime = null) {
561
-		if (!is_null($mtime) and !is_numeric($mtime)) {
562
-			$mtime = strtotime($mtime);
563
-		}
564
-
565
-		$hooks = ['touch'];
566
-
567
-		if (!$this->file_exists($path)) {
568
-			$hooks[] = 'create';
569
-			$hooks[] = 'write';
570
-		}
571
-		try {
572
-			$result = $this->basicOperation('touch', $path, $hooks, $mtime);
573
-		} catch (\Exception $e) {
574
-			$this->logger->logException($e, ['level' => ILogger::INFO, 'message' => 'Error while setting modified time']);
575
-			$result = false;
576
-		}
577
-		if (!$result) {
578
-			// If create file fails because of permissions on external storage like SMB folders,
579
-			// check file exists and return false if not.
580
-			if (!$this->file_exists($path)) {
581
-				return false;
582
-			}
583
-			if (is_null($mtime)) {
584
-				$mtime = time();
585
-			}
586
-			//if native touch fails, we emulate it by changing the mtime in the cache
587
-			$this->putFileInfo($path, ['mtime' => floor($mtime)]);
588
-		}
589
-		return true;
590
-	}
591
-
592
-	/**
593
-	 * @param string $path
594
-	 * @return mixed
595
-	 * @throws LockedException
596
-	 */
597
-	public function file_get_contents($path) {
598
-		return $this->basicOperation('file_get_contents', $path, ['read']);
599
-	}
600
-
601
-	/**
602
-	 * @param bool $exists
603
-	 * @param string $path
604
-	 * @param bool $run
605
-	 */
606
-	protected function emit_file_hooks_pre($exists, $path, &$run) {
607
-		if (!$exists) {
608
-			\OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_create, [
609
-				Filesystem::signal_param_path => $this->getHookPath($path),
610
-				Filesystem::signal_param_run => &$run,
611
-			]);
612
-		} else {
613
-			\OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_update, [
614
-				Filesystem::signal_param_path => $this->getHookPath($path),
615
-				Filesystem::signal_param_run => &$run,
616
-			]);
617
-		}
618
-		\OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_write, [
619
-			Filesystem::signal_param_path => $this->getHookPath($path),
620
-			Filesystem::signal_param_run => &$run,
621
-		]);
622
-	}
623
-
624
-	/**
625
-	 * @param bool $exists
626
-	 * @param string $path
627
-	 */
628
-	protected function emit_file_hooks_post($exists, $path) {
629
-		if (!$exists) {
630
-			\OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_post_create, [
631
-				Filesystem::signal_param_path => $this->getHookPath($path),
632
-			]);
633
-		} else {
634
-			\OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_post_update, [
635
-				Filesystem::signal_param_path => $this->getHookPath($path),
636
-			]);
637
-		}
638
-		\OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_post_write, [
639
-			Filesystem::signal_param_path => $this->getHookPath($path),
640
-		]);
641
-	}
642
-
643
-	/**
644
-	 * @param string $path
645
-	 * @param string|resource $data
646
-	 * @return bool|mixed
647
-	 * @throws LockedException
648
-	 */
649
-	public function file_put_contents($path, $data) {
650
-		if (is_resource($data)) { //not having to deal with streams in file_put_contents makes life easier
651
-			$absolutePath = Filesystem::normalizePath($this->getAbsolutePath($path));
652
-			if (Filesystem::isValidPath($path)
653
-				and !Filesystem::isFileBlacklisted($path)
654
-			) {
655
-				$path = $this->getRelativePath($absolutePath);
656
-
657
-				$this->lockFile($path, ILockingProvider::LOCK_SHARED);
658
-
659
-				$exists = $this->file_exists($path);
660
-				$run = true;
661
-				if ($this->shouldEmitHooks($path)) {
662
-					$this->emit_file_hooks_pre($exists, $path, $run);
663
-				}
664
-				if (!$run) {
665
-					$this->unlockFile($path, ILockingProvider::LOCK_SHARED);
666
-					return false;
667
-				}
668
-
669
-				try {
670
-					$this->changeLock($path, ILockingProvider::LOCK_EXCLUSIVE);
671
-				} catch (\Exception $e) {
672
-					// Release the shared lock before throwing.
673
-					$this->unlockFile($path, ILockingProvider::LOCK_SHARED);
674
-					throw $e;
675
-				}
676
-
677
-				/** @var \OC\Files\Storage\Storage $storage */
678
-				[$storage, $internalPath] = $this->resolvePath($path);
679
-				$target = $storage->fopen($internalPath, 'w');
680
-				if ($target) {
681
-					[, $result] = \OC_Helper::streamCopy($data, $target);
682
-					fclose($target);
683
-					fclose($data);
684
-
685
-					$this->writeUpdate($storage, $internalPath);
686
-
687
-					$this->changeLock($path, ILockingProvider::LOCK_SHARED);
688
-
689
-					if ($this->shouldEmitHooks($path) && $result !== false) {
690
-						$this->emit_file_hooks_post($exists, $path);
691
-					}
692
-					$this->unlockFile($path, ILockingProvider::LOCK_SHARED);
693
-					return $result;
694
-				} else {
695
-					$this->unlockFile($path, ILockingProvider::LOCK_EXCLUSIVE);
696
-					return false;
697
-				}
698
-			} else {
699
-				return false;
700
-			}
701
-		} else {
702
-			$hooks = $this->file_exists($path) ? ['update', 'write'] : ['create', 'write'];
703
-			return $this->basicOperation('file_put_contents', $path, $hooks, $data);
704
-		}
705
-	}
706
-
707
-	/**
708
-	 * @param string $path
709
-	 * @return bool|mixed
710
-	 */
711
-	public function unlink($path) {
712
-		if ($path === '' || $path === '/') {
713
-			// do not allow deleting the root
714
-			return false;
715
-		}
716
-		$postFix = (substr($path, -1) === '/') ? '/' : '';
717
-		$absolutePath = Filesystem::normalizePath($this->getAbsolutePath($path));
718
-		$mount = Filesystem::getMountManager()->find($absolutePath . $postFix);
719
-		if ($mount and $mount->getInternalPath($absolutePath) === '') {
720
-			return $this->removeMount($mount, $absolutePath);
721
-		}
722
-		if ($this->is_dir($path)) {
723
-			$result = $this->basicOperation('rmdir', $path, ['delete']);
724
-		} else {
725
-			$result = $this->basicOperation('unlink', $path, ['delete']);
726
-		}
727
-		if (!$result && !$this->file_exists($path)) { //clear ghost files from the cache on delete
728
-			$storage = $mount->getStorage();
729
-			$internalPath = $mount->getInternalPath($absolutePath);
730
-			$storage->getUpdater()->remove($internalPath);
731
-			return true;
732
-		} else {
733
-			return $result;
734
-		}
735
-	}
736
-
737
-	/**
738
-	 * @param string $directory
739
-	 * @return bool|mixed
740
-	 */
741
-	public function deleteAll($directory) {
742
-		return $this->rmdir($directory);
743
-	}
744
-
745
-	/**
746
-	 * Rename/move a file or folder from the source path to target path.
747
-	 *
748
-	 * @param string $path1 source path
749
-	 * @param string $path2 target path
750
-	 *
751
-	 * @return bool|mixed
752
-	 * @throws LockedException
753
-	 */
754
-	public function rename($path1, $path2) {
755
-		$absolutePath1 = Filesystem::normalizePath($this->getAbsolutePath($path1));
756
-		$absolutePath2 = Filesystem::normalizePath($this->getAbsolutePath($path2));
757
-		$result = false;
758
-		if (
759
-			Filesystem::isValidPath($path2)
760
-			and Filesystem::isValidPath($path1)
761
-			and !Filesystem::isFileBlacklisted($path2)
762
-		) {
763
-			$path1 = $this->getRelativePath($absolutePath1);
764
-			$path2 = $this->getRelativePath($absolutePath2);
765
-			$exists = $this->file_exists($path2);
766
-
767
-			if ($path1 == null or $path2 == null) {
768
-				return false;
769
-			}
770
-
771
-			$this->lockFile($path1, ILockingProvider::LOCK_SHARED, true);
772
-			try {
773
-				$this->lockFile($path2, ILockingProvider::LOCK_SHARED, true);
774
-
775
-				$run = true;
776
-				if ($this->shouldEmitHooks($path1) && (Cache\Scanner::isPartialFile($path1) && !Cache\Scanner::isPartialFile($path2))) {
777
-					// if it was a rename from a part file to a regular file it was a write and not a rename operation
778
-					$this->emit_file_hooks_pre($exists, $path2, $run);
779
-				} elseif ($this->shouldEmitHooks($path1)) {
780
-					\OC_Hook::emit(
781
-						Filesystem::CLASSNAME, Filesystem::signal_rename,
782
-						[
783
-							Filesystem::signal_param_oldpath => $this->getHookPath($path1),
784
-							Filesystem::signal_param_newpath => $this->getHookPath($path2),
785
-							Filesystem::signal_param_run => &$run
786
-						]
787
-					);
788
-				}
789
-				if ($run) {
790
-					$this->verifyPath(dirname($path2), basename($path2));
791
-
792
-					$manager = Filesystem::getMountManager();
793
-					$mount1 = $this->getMount($path1);
794
-					$mount2 = $this->getMount($path2);
795
-					$storage1 = $mount1->getStorage();
796
-					$storage2 = $mount2->getStorage();
797
-					$internalPath1 = $mount1->getInternalPath($absolutePath1);
798
-					$internalPath2 = $mount2->getInternalPath($absolutePath2);
799
-
800
-					$this->changeLock($path1, ILockingProvider::LOCK_EXCLUSIVE, true);
801
-					try {
802
-						$this->changeLock($path2, ILockingProvider::LOCK_EXCLUSIVE, true);
803
-
804
-						if ($internalPath1 === '') {
805
-							if ($mount1 instanceof MoveableMount) {
806
-								$sourceParentMount = $this->getMount(dirname($path1));
807
-								if ($sourceParentMount === $mount2 && $this->targetIsNotShared($storage2, $internalPath2)) {
808
-									/**
809
-									 * @var \OC\Files\Mount\MountPoint | \OC\Files\Mount\MoveableMount $mount1
810
-									 */
811
-									$sourceMountPoint = $mount1->getMountPoint();
812
-									$result = $mount1->moveMount($absolutePath2);
813
-									$manager->moveMount($sourceMountPoint, $mount1->getMountPoint());
814
-								} else {
815
-									$result = false;
816
-								}
817
-							} else {
818
-								$result = false;
819
-							}
820
-							// moving a file/folder within the same mount point
821
-						} elseif ($storage1 === $storage2) {
822
-							if ($storage1) {
823
-								$result = $storage1->rename($internalPath1, $internalPath2);
824
-							} else {
825
-								$result = false;
826
-							}
827
-							// moving a file/folder between storages (from $storage1 to $storage2)
828
-						} else {
829
-							$result = $storage2->moveFromStorage($storage1, $internalPath1, $internalPath2);
830
-						}
831
-
832
-						if ((Cache\Scanner::isPartialFile($path1) && !Cache\Scanner::isPartialFile($path2)) && $result !== false) {
833
-							// if it was a rename from a part file to a regular file it was a write and not a rename operation
834
-							$this->writeUpdate($storage2, $internalPath2);
835
-						} elseif ($result) {
836
-							if ($internalPath1 !== '') { // don't do a cache update for moved mounts
837
-								$this->renameUpdate($storage1, $storage2, $internalPath1, $internalPath2);
838
-							}
839
-						}
840
-					} catch (\Exception $e) {
841
-						throw $e;
842
-					} finally {
843
-						$this->changeLock($path1, ILockingProvider::LOCK_SHARED, true);
844
-						$this->changeLock($path2, ILockingProvider::LOCK_SHARED, true);
845
-					}
846
-
847
-					if ((Cache\Scanner::isPartialFile($path1) && !Cache\Scanner::isPartialFile($path2)) && $result !== false) {
848
-						if ($this->shouldEmitHooks()) {
849
-							$this->emit_file_hooks_post($exists, $path2);
850
-						}
851
-					} elseif ($result) {
852
-						if ($this->shouldEmitHooks($path1) and $this->shouldEmitHooks($path2)) {
853
-							\OC_Hook::emit(
854
-								Filesystem::CLASSNAME,
855
-								Filesystem::signal_post_rename,
856
-								[
857
-									Filesystem::signal_param_oldpath => $this->getHookPath($path1),
858
-									Filesystem::signal_param_newpath => $this->getHookPath($path2)
859
-								]
860
-							);
861
-						}
862
-					}
863
-				}
864
-			} catch (\Exception $e) {
865
-				throw $e;
866
-			} finally {
867
-				$this->unlockFile($path1, ILockingProvider::LOCK_SHARED, true);
868
-				$this->unlockFile($path2, ILockingProvider::LOCK_SHARED, true);
869
-			}
870
-		}
871
-		return $result;
872
-	}
873
-
874
-	/**
875
-	 * Copy a file/folder from the source path to target path
876
-	 *
877
-	 * @param string $path1 source path
878
-	 * @param string $path2 target path
879
-	 * @param bool $preserveMtime whether to preserve mtime on the copy
880
-	 *
881
-	 * @return bool|mixed
882
-	 */
883
-	public function copy($path1, $path2, $preserveMtime = false) {
884
-		$absolutePath1 = Filesystem::normalizePath($this->getAbsolutePath($path1));
885
-		$absolutePath2 = Filesystem::normalizePath($this->getAbsolutePath($path2));
886
-		$result = false;
887
-		if (
888
-			Filesystem::isValidPath($path2)
889
-			and Filesystem::isValidPath($path1)
890
-			and !Filesystem::isFileBlacklisted($path2)
891
-		) {
892
-			$path1 = $this->getRelativePath($absolutePath1);
893
-			$path2 = $this->getRelativePath($absolutePath2);
894
-
895
-			if ($path1 == null or $path2 == null) {
896
-				return false;
897
-			}
898
-			$run = true;
899
-
900
-			$this->lockFile($path2, ILockingProvider::LOCK_SHARED);
901
-			$this->lockFile($path1, ILockingProvider::LOCK_SHARED);
902
-			$lockTypePath1 = ILockingProvider::LOCK_SHARED;
903
-			$lockTypePath2 = ILockingProvider::LOCK_SHARED;
904
-
905
-			try {
906
-				$exists = $this->file_exists($path2);
907
-				if ($this->shouldEmitHooks()) {
908
-					\OC_Hook::emit(
909
-						Filesystem::CLASSNAME,
910
-						Filesystem::signal_copy,
911
-						[
912
-							Filesystem::signal_param_oldpath => $this->getHookPath($path1),
913
-							Filesystem::signal_param_newpath => $this->getHookPath($path2),
914
-							Filesystem::signal_param_run => &$run
915
-						]
916
-					);
917
-					$this->emit_file_hooks_pre($exists, $path2, $run);
918
-				}
919
-				if ($run) {
920
-					$mount1 = $this->getMount($path1);
921
-					$mount2 = $this->getMount($path2);
922
-					$storage1 = $mount1->getStorage();
923
-					$internalPath1 = $mount1->getInternalPath($absolutePath1);
924
-					$storage2 = $mount2->getStorage();
925
-					$internalPath2 = $mount2->getInternalPath($absolutePath2);
926
-
927
-					$this->changeLock($path2, ILockingProvider::LOCK_EXCLUSIVE);
928
-					$lockTypePath2 = ILockingProvider::LOCK_EXCLUSIVE;
929
-
930
-					if ($mount1->getMountPoint() == $mount2->getMountPoint()) {
931
-						if ($storage1) {
932
-							$result = $storage1->copy($internalPath1, $internalPath2);
933
-						} else {
934
-							$result = false;
935
-						}
936
-					} else {
937
-						$result = $storage2->copyFromStorage($storage1, $internalPath1, $internalPath2);
938
-					}
939
-
940
-					$this->writeUpdate($storage2, $internalPath2);
941
-
942
-					$this->changeLock($path2, ILockingProvider::LOCK_SHARED);
943
-					$lockTypePath2 = ILockingProvider::LOCK_SHARED;
944
-
945
-					if ($this->shouldEmitHooks() && $result !== false) {
946
-						\OC_Hook::emit(
947
-							Filesystem::CLASSNAME,
948
-							Filesystem::signal_post_copy,
949
-							[
950
-								Filesystem::signal_param_oldpath => $this->getHookPath($path1),
951
-								Filesystem::signal_param_newpath => $this->getHookPath($path2)
952
-							]
953
-						);
954
-						$this->emit_file_hooks_post($exists, $path2);
955
-					}
956
-				}
957
-			} catch (\Exception $e) {
958
-				$this->unlockFile($path2, $lockTypePath2);
959
-				$this->unlockFile($path1, $lockTypePath1);
960
-				throw $e;
961
-			}
962
-
963
-			$this->unlockFile($path2, $lockTypePath2);
964
-			$this->unlockFile($path1, $lockTypePath1);
965
-		}
966
-		return $result;
967
-	}
968
-
969
-	/**
970
-	 * @param string $path
971
-	 * @param string $mode 'r' or 'w'
972
-	 * @return resource
973
-	 * @throws LockedException
974
-	 */
975
-	public function fopen($path, $mode) {
976
-		$mode = str_replace('b', '', $mode); // the binary flag is a windows only feature which we do not support
977
-		$hooks = [];
978
-		switch ($mode) {
979
-			case 'r':
980
-				$hooks[] = 'read';
981
-				break;
982
-			case 'r+':
983
-			case 'w+':
984
-			case 'x+':
985
-			case 'a+':
986
-				$hooks[] = 'read';
987
-				$hooks[] = 'write';
988
-				break;
989
-			case 'w':
990
-			case 'x':
991
-			case 'a':
992
-				$hooks[] = 'write';
993
-				break;
994
-			default:
995
-				\OCP\Util::writeLog('core', 'invalid mode (' . $mode . ') for ' . $path, ILogger::ERROR);
996
-		}
997
-
998
-		if ($mode !== 'r' && $mode !== 'w') {
999
-			\OC::$server->getLogger()->info('Trying to open a file with a mode other than "r" or "w" can cause severe performance issues with some backends');
1000
-		}
1001
-
1002
-		return $this->basicOperation('fopen', $path, $hooks, $mode);
1003
-	}
1004
-
1005
-	/**
1006
-	 * @param string $path
1007
-	 * @return bool|string
1008
-	 * @throws \OCP\Files\InvalidPathException
1009
-	 */
1010
-	public function toTmpFile($path) {
1011
-		$this->assertPathLength($path);
1012
-		if (Filesystem::isValidPath($path)) {
1013
-			$source = $this->fopen($path, 'r');
1014
-			if ($source) {
1015
-				$extension = pathinfo($path, PATHINFO_EXTENSION);
1016
-				$tmpFile = \OC::$server->getTempManager()->getTemporaryFile($extension);
1017
-				file_put_contents($tmpFile, $source);
1018
-				return $tmpFile;
1019
-			} else {
1020
-				return false;
1021
-			}
1022
-		} else {
1023
-			return false;
1024
-		}
1025
-	}
1026
-
1027
-	/**
1028
-	 * @param string $tmpFile
1029
-	 * @param string $path
1030
-	 * @return bool|mixed
1031
-	 * @throws \OCP\Files\InvalidPathException
1032
-	 */
1033
-	public function fromTmpFile($tmpFile, $path) {
1034
-		$this->assertPathLength($path);
1035
-		if (Filesystem::isValidPath($path)) {
1036
-
1037
-			// Get directory that the file is going into
1038
-			$filePath = dirname($path);
1039
-
1040
-			// Create the directories if any
1041
-			if (!$this->file_exists($filePath)) {
1042
-				$result = $this->createParentDirectories($filePath);
1043
-				if ($result === false) {
1044
-					return false;
1045
-				}
1046
-			}
1047
-
1048
-			$source = fopen($tmpFile, 'r');
1049
-			if ($source) {
1050
-				$result = $this->file_put_contents($path, $source);
1051
-				// $this->file_put_contents() might have already closed
1052
-				// the resource, so we check it, before trying to close it
1053
-				// to avoid messages in the error log.
1054
-				if (is_resource($source)) {
1055
-					fclose($source);
1056
-				}
1057
-				unlink($tmpFile);
1058
-				return $result;
1059
-			} else {
1060
-				return false;
1061
-			}
1062
-		} else {
1063
-			return false;
1064
-		}
1065
-	}
1066
-
1067
-
1068
-	/**
1069
-	 * @param string $path
1070
-	 * @return mixed
1071
-	 * @throws \OCP\Files\InvalidPathException
1072
-	 */
1073
-	public function getMimeType($path) {
1074
-		$this->assertPathLength($path);
1075
-		return $this->basicOperation('getMimeType', $path);
1076
-	}
1077
-
1078
-	/**
1079
-	 * @param string $type
1080
-	 * @param string $path
1081
-	 * @param bool $raw
1082
-	 * @return bool|null|string
1083
-	 */
1084
-	public function hash($type, $path, $raw = false) {
1085
-		$postFix = (substr($path, -1) === '/') ? '/' : '';
1086
-		$absolutePath = Filesystem::normalizePath($this->getAbsolutePath($path));
1087
-		if (Filesystem::isValidPath($path)) {
1088
-			$path = $this->getRelativePath($absolutePath);
1089
-			if ($path == null) {
1090
-				return false;
1091
-			}
1092
-			if ($this->shouldEmitHooks($path)) {
1093
-				\OC_Hook::emit(
1094
-					Filesystem::CLASSNAME,
1095
-					Filesystem::signal_read,
1096
-					[Filesystem::signal_param_path => $this->getHookPath($path)]
1097
-				);
1098
-			}
1099
-			[$storage, $internalPath] = Filesystem::resolvePath($absolutePath . $postFix);
1100
-			if ($storage) {
1101
-				return $storage->hash($type, $internalPath, $raw);
1102
-			}
1103
-		}
1104
-		return null;
1105
-	}
1106
-
1107
-	/**
1108
-	 * @param string $path
1109
-	 * @return mixed
1110
-	 * @throws \OCP\Files\InvalidPathException
1111
-	 */
1112
-	public function free_space($path = '/') {
1113
-		$this->assertPathLength($path);
1114
-		$result = $this->basicOperation('free_space', $path);
1115
-		if ($result === null) {
1116
-			throw new InvalidPathException();
1117
-		}
1118
-		return $result;
1119
-	}
1120
-
1121
-	/**
1122
-	 * abstraction layer for basic filesystem functions: wrapper for \OC\Files\Storage\Storage
1123
-	 *
1124
-	 * @param string $operation
1125
-	 * @param string $path
1126
-	 * @param array $hooks (optional)
1127
-	 * @param mixed $extraParam (optional)
1128
-	 * @return mixed
1129
-	 * @throws LockedException
1130
-	 *
1131
-	 * This method takes requests for basic filesystem functions (e.g. reading & writing
1132
-	 * files), processes hooks and proxies, sanitises paths, and finally passes them on to
1133
-	 * \OC\Files\Storage\Storage for delegation to a storage backend for execution
1134
-	 */
1135
-	private function basicOperation($operation, $path, $hooks = [], $extraParam = null) {
1136
-		$postFix = (substr($path, -1) === '/') ? '/' : '';
1137
-		$absolutePath = Filesystem::normalizePath($this->getAbsolutePath($path));
1138
-		if (Filesystem::isValidPath($path)
1139
-			and !Filesystem::isFileBlacklisted($path)
1140
-		) {
1141
-			$path = $this->getRelativePath($absolutePath);
1142
-			if ($path == null) {
1143
-				return false;
1144
-			}
1145
-
1146
-			if (in_array('write', $hooks) || in_array('delete', $hooks) || in_array('read', $hooks)) {
1147
-				// always a shared lock during pre-hooks so the hook can read the file
1148
-				$this->lockFile($path, ILockingProvider::LOCK_SHARED);
1149
-			}
1150
-
1151
-			$run = $this->runHooks($hooks, $path);
1152
-			/** @var \OC\Files\Storage\Storage $storage */
1153
-			[$storage, $internalPath] = Filesystem::resolvePath($absolutePath . $postFix);
1154
-			if ($run and $storage) {
1155
-				if (in_array('write', $hooks) || in_array('delete', $hooks)) {
1156
-					try {
1157
-						$this->changeLock($path, ILockingProvider::LOCK_EXCLUSIVE);
1158
-					} catch (LockedException $e) {
1159
-						// release the shared lock we acquired before quiting
1160
-						$this->unlockFile($path, ILockingProvider::LOCK_SHARED);
1161
-						throw $e;
1162
-					}
1163
-				}
1164
-				try {
1165
-					if (!is_null($extraParam)) {
1166
-						$result = $storage->$operation($internalPath, $extraParam);
1167
-					} else {
1168
-						$result = $storage->$operation($internalPath);
1169
-					}
1170
-				} catch (\Exception $e) {
1171
-					if (in_array('write', $hooks) || in_array('delete', $hooks)) {
1172
-						$this->unlockFile($path, ILockingProvider::LOCK_EXCLUSIVE);
1173
-					} elseif (in_array('read', $hooks)) {
1174
-						$this->unlockFile($path, ILockingProvider::LOCK_SHARED);
1175
-					}
1176
-					throw $e;
1177
-				}
1178
-
1179
-				if ($result && in_array('delete', $hooks) and $result) {
1180
-					$this->removeUpdate($storage, $internalPath);
1181
-				}
1182
-				if ($result && in_array('write', $hooks,  true) && $operation !== 'fopen' && $operation !== 'touch') {
1183
-					$this->writeUpdate($storage, $internalPath);
1184
-				}
1185
-				if ($result && in_array('touch', $hooks)) {
1186
-					$this->writeUpdate($storage, $internalPath, $extraParam);
1187
-				}
1188
-
1189
-				if ((in_array('write', $hooks) || in_array('delete', $hooks)) && ($operation !== 'fopen' || $result === false)) {
1190
-					$this->changeLock($path, ILockingProvider::LOCK_SHARED);
1191
-				}
1192
-
1193
-				$unlockLater = false;
1194
-				if ($this->lockingEnabled && $operation === 'fopen' && is_resource($result)) {
1195
-					$unlockLater = true;
1196
-					// make sure our unlocking callback will still be called if connection is aborted
1197
-					ignore_user_abort(true);
1198
-					$result = CallbackWrapper::wrap($result, null, null, function () use ($hooks, $path) {
1199
-						if (in_array('write', $hooks)) {
1200
-							$this->unlockFile($path, ILockingProvider::LOCK_EXCLUSIVE);
1201
-						} elseif (in_array('read', $hooks)) {
1202
-							$this->unlockFile($path, ILockingProvider::LOCK_SHARED);
1203
-						}
1204
-					});
1205
-				}
1206
-
1207
-				if ($this->shouldEmitHooks($path) && $result !== false) {
1208
-					if ($operation != 'fopen') { //no post hooks for fopen, the file stream is still open
1209
-						$this->runHooks($hooks, $path, true);
1210
-					}
1211
-				}
1212
-
1213
-				if (!$unlockLater
1214
-					&& (in_array('write', $hooks) || in_array('delete', $hooks) || in_array('read', $hooks))
1215
-				) {
1216
-					$this->unlockFile($path, ILockingProvider::LOCK_SHARED);
1217
-				}
1218
-				return $result;
1219
-			} else {
1220
-				$this->unlockFile($path, ILockingProvider::LOCK_SHARED);
1221
-			}
1222
-		}
1223
-		return null;
1224
-	}
1225
-
1226
-	/**
1227
-	 * get the path relative to the default root for hook usage
1228
-	 *
1229
-	 * @param string $path
1230
-	 * @return string
1231
-	 */
1232
-	private function getHookPath($path) {
1233
-		if (!Filesystem::getView()) {
1234
-			return $path;
1235
-		}
1236
-		return Filesystem::getView()->getRelativePath($this->getAbsolutePath($path));
1237
-	}
1238
-
1239
-	private function shouldEmitHooks($path = '') {
1240
-		if ($path && Cache\Scanner::isPartialFile($path)) {
1241
-			return false;
1242
-		}
1243
-		if (!Filesystem::$loaded) {
1244
-			return false;
1245
-		}
1246
-		$defaultRoot = Filesystem::getRoot();
1247
-		if ($defaultRoot === null) {
1248
-			return false;
1249
-		}
1250
-		if ($this->fakeRoot === $defaultRoot) {
1251
-			return true;
1252
-		}
1253
-		$fullPath = $this->getAbsolutePath($path);
1254
-
1255
-		if ($fullPath === $defaultRoot) {
1256
-			return true;
1257
-		}
1258
-
1259
-		return (strlen($fullPath) > strlen($defaultRoot)) && (substr($fullPath, 0, strlen($defaultRoot) + 1) === $defaultRoot . '/');
1260
-	}
1261
-
1262
-	/**
1263
-	 * @param string[] $hooks
1264
-	 * @param string $path
1265
-	 * @param bool $post
1266
-	 * @return bool
1267
-	 */
1268
-	private function runHooks($hooks, $path, $post = false) {
1269
-		$relativePath = $path;
1270
-		$path = $this->getHookPath($path);
1271
-		$prefix = $post ? 'post_' : '';
1272
-		$run = true;
1273
-		if ($this->shouldEmitHooks($relativePath)) {
1274
-			foreach ($hooks as $hook) {
1275
-				if ($hook != 'read') {
1276
-					\OC_Hook::emit(
1277
-						Filesystem::CLASSNAME,
1278
-						$prefix . $hook,
1279
-						[
1280
-							Filesystem::signal_param_run => &$run,
1281
-							Filesystem::signal_param_path => $path
1282
-						]
1283
-					);
1284
-				} elseif (!$post) {
1285
-					\OC_Hook::emit(
1286
-						Filesystem::CLASSNAME,
1287
-						$prefix . $hook,
1288
-						[
1289
-							Filesystem::signal_param_path => $path
1290
-						]
1291
-					);
1292
-				}
1293
-			}
1294
-		}
1295
-		return $run;
1296
-	}
1297
-
1298
-	/**
1299
-	 * check if a file or folder has been updated since $time
1300
-	 *
1301
-	 * @param string $path
1302
-	 * @param int $time
1303
-	 * @return bool
1304
-	 */
1305
-	public function hasUpdated($path, $time) {
1306
-		return $this->basicOperation('hasUpdated', $path, [], $time);
1307
-	}
1308
-
1309
-	/**
1310
-	 * @param string $ownerId
1311
-	 * @return \OC\User\User
1312
-	 */
1313
-	private function getUserObjectForOwner($ownerId) {
1314
-		$owner = $this->userManager->get($ownerId);
1315
-		if ($owner instanceof IUser) {
1316
-			return $owner;
1317
-		} else {
1318
-			return new User($ownerId, null, \OC::$server->getEventDispatcher());
1319
-		}
1320
-	}
1321
-
1322
-	/**
1323
-	 * Get file info from cache
1324
-	 *
1325
-	 * If the file is not in cached it will be scanned
1326
-	 * If the file has changed on storage the cache will be updated
1327
-	 *
1328
-	 * @param \OC\Files\Storage\Storage $storage
1329
-	 * @param string $internalPath
1330
-	 * @param string $relativePath
1331
-	 * @return ICacheEntry|bool
1332
-	 */
1333
-	private function getCacheEntry($storage, $internalPath, $relativePath) {
1334
-		$cache = $storage->getCache($internalPath);
1335
-		$data = $cache->get($internalPath);
1336
-		$watcher = $storage->getWatcher($internalPath);
1337
-
1338
-		try {
1339
-			// if the file is not in the cache or needs to be updated, trigger the scanner and reload the data
1340
-			if (!$data || $data['size'] === -1) {
1341
-				if (!$storage->file_exists($internalPath)) {
1342
-					return false;
1343
-				}
1344
-				// don't need to get a lock here since the scanner does it's own locking
1345
-				$scanner = $storage->getScanner($internalPath);
1346
-				$scanner->scan($internalPath, Cache\Scanner::SCAN_SHALLOW);
1347
-				$data = $cache->get($internalPath);
1348
-			} elseif (!Cache\Scanner::isPartialFile($internalPath) && $watcher->needsUpdate($internalPath, $data)) {
1349
-				$this->lockFile($relativePath, ILockingProvider::LOCK_SHARED);
1350
-				$watcher->update($internalPath, $data);
1351
-				$storage->getPropagator()->propagateChange($internalPath, time());
1352
-				$data = $cache->get($internalPath);
1353
-				$this->unlockFile($relativePath, ILockingProvider::LOCK_SHARED);
1354
-			}
1355
-		} catch (LockedException $e) {
1356
-			// if the file is locked we just use the old cache info
1357
-		}
1358
-
1359
-		return $data;
1360
-	}
1361
-
1362
-	/**
1363
-	 * get the filesystem info
1364
-	 *
1365
-	 * @param string $path
1366
-	 * @param boolean|string $includeMountPoints true to add mountpoint sizes,
1367
-	 * 'ext' to add only ext storage mount point sizes. Defaults to true.
1368
-	 * defaults to true
1369
-	 * @return \OC\Files\FileInfo|false False if file does not exist
1370
-	 */
1371
-	public function getFileInfo($path, $includeMountPoints = true) {
1372
-		$this->assertPathLength($path);
1373
-		if (!Filesystem::isValidPath($path)) {
1374
-			return false;
1375
-		}
1376
-		if (Cache\Scanner::isPartialFile($path)) {
1377
-			return $this->getPartFileInfo($path);
1378
-		}
1379
-		$relativePath = $path;
1380
-		$path = Filesystem::normalizePath($this->fakeRoot . '/' . $path);
1381
-
1382
-		$mount = Filesystem::getMountManager()->find($path);
1383
-		if (!$mount) {
1384
-			\OC::$server->getLogger()->warning('Mountpoint not found for path: ' . $path);
1385
-			return false;
1386
-		}
1387
-		$storage = $mount->getStorage();
1388
-		$internalPath = $mount->getInternalPath($path);
1389
-		if ($storage) {
1390
-			$data = $this->getCacheEntry($storage, $internalPath, $relativePath);
1391
-
1392
-			if (!$data instanceof ICacheEntry) {
1393
-				return false;
1394
-			}
1395
-
1396
-			if ($mount instanceof MoveableMount && $internalPath === '') {
1397
-				$data['permissions'] |= \OCP\Constants::PERMISSION_DELETE;
1398
-			}
1399
-			$ownerId = $storage->getOwner($internalPath);
1400
-			$owner = null;
1401
-			if ($ownerId !== null && $ownerId !== false) {
1402
-				// ownerId might be null if files are accessed with an access token without file system access
1403
-				$owner = $this->getUserObjectForOwner($ownerId);
1404
-			}
1405
-			$info = new FileInfo($path, $storage, $internalPath, $data, $mount, $owner);
1406
-
1407
-			if ($data and isset($data['fileid'])) {
1408
-				if ($includeMountPoints and $data['mimetype'] === 'httpd/unix-directory') {
1409
-					//add the sizes of other mount points to the folder
1410
-					$extOnly = ($includeMountPoints === 'ext');
1411
-					$mounts = Filesystem::getMountManager()->findIn($path);
1412
-					$info->setSubMounts(array_filter($mounts, function (IMountPoint $mount) use ($extOnly) {
1413
-						$subStorage = $mount->getStorage();
1414
-						return !($extOnly && $subStorage instanceof \OCA\Files_Sharing\SharedStorage);
1415
-					}));
1416
-				}
1417
-			}
1418
-
1419
-			return $info;
1420
-		} else {
1421
-			\OC::$server->getLogger()->warning('Storage not valid for mountpoint: ' . $mount->getMountPoint());
1422
-		}
1423
-
1424
-		return false;
1425
-	}
1426
-
1427
-	/**
1428
-	 * get the content of a directory
1429
-	 *
1430
-	 * @param string $directory path under datadirectory
1431
-	 * @param string $mimetype_filter limit returned content to this mimetype or mimepart
1432
-	 * @return FileInfo[]
1433
-	 */
1434
-	public function getDirectoryContent($directory, $mimetype_filter = '') {
1435
-		$this->assertPathLength($directory);
1436
-		if (!Filesystem::isValidPath($directory)) {
1437
-			return [];
1438
-		}
1439
-		$path = $this->getAbsolutePath($directory);
1440
-		$path = Filesystem::normalizePath($path);
1441
-		$mount = $this->getMount($directory);
1442
-		if (!$mount) {
1443
-			return [];
1444
-		}
1445
-		$storage = $mount->getStorage();
1446
-		$internalPath = $mount->getInternalPath($path);
1447
-		if ($storage) {
1448
-			$cache = $storage->getCache($internalPath);
1449
-			$user = \OC_User::getUser();
1450
-
1451
-			$data = $this->getCacheEntry($storage, $internalPath, $directory);
1452
-
1453
-			if (!$data instanceof ICacheEntry || !isset($data['fileid']) || !($data->getPermissions() && Constants::PERMISSION_READ)) {
1454
-				return [];
1455
-			}
1456
-
1457
-			$folderId = $data['fileid'];
1458
-			$contents = $cache->getFolderContentsById($folderId); //TODO: mimetype_filter
1459
-
1460
-			$sharingDisabled = \OCP\Util::isSharingDisabledForUser();
1461
-
1462
-			$fileNames = array_map(function (ICacheEntry $content) {
1463
-				return $content->getName();
1464
-			}, $contents);
1465
-			/**
1466
-			 * @var \OC\Files\FileInfo[] $fileInfos
1467
-			 */
1468
-			$fileInfos = array_map(function (ICacheEntry $content) use ($path, $storage, $mount, $sharingDisabled) {
1469
-				if ($sharingDisabled) {
1470
-					$content['permissions'] = $content['permissions'] & ~\OCP\Constants::PERMISSION_SHARE;
1471
-				}
1472
-				$owner = $this->getUserObjectForOwner($storage->getOwner($content['path']));
1473
-				return new FileInfo($path . '/' . $content['name'], $storage, $content['path'], $content, $mount, $owner);
1474
-			}, $contents);
1475
-			$files = array_combine($fileNames, $fileInfos);
1476
-
1477
-			//add a folder for any mountpoint in this directory and add the sizes of other mountpoints to the folders
1478
-			$mounts = Filesystem::getMountManager()->findIn($path);
1479
-			$dirLength = strlen($path);
1480
-			foreach ($mounts as $mount) {
1481
-				$mountPoint = $mount->getMountPoint();
1482
-				$subStorage = $mount->getStorage();
1483
-				if ($subStorage) {
1484
-					$subCache = $subStorage->getCache('');
1485
-
1486
-					$rootEntry = $subCache->get('');
1487
-					if (!$rootEntry) {
1488
-						$subScanner = $subStorage->getScanner('');
1489
-						try {
1490
-							$subScanner->scanFile('');
1491
-						} catch (\OCP\Files\StorageNotAvailableException $e) {
1492
-							continue;
1493
-						} catch (\OCP\Files\StorageInvalidException $e) {
1494
-							continue;
1495
-						} catch (\Exception $e) {
1496
-							// sometimes when the storage is not available it can be any exception
1497
-							\OC::$server->getLogger()->logException($e, [
1498
-								'message' => 'Exception while scanning storage "' . $subStorage->getId() . '"',
1499
-								'level' => ILogger::ERROR,
1500
-								'app' => 'lib',
1501
-							]);
1502
-							continue;
1503
-						}
1504
-						$rootEntry = $subCache->get('');
1505
-					}
1506
-
1507
-					if ($rootEntry && ($rootEntry->getPermissions() && Constants::PERMISSION_READ)) {
1508
-						$relativePath = trim(substr($mountPoint, $dirLength), '/');
1509
-						if ($pos = strpos($relativePath, '/')) {
1510
-							//mountpoint inside subfolder add size to the correct folder
1511
-							$entryName = substr($relativePath, 0, $pos);
1512
-							foreach ($files as &$entry) {
1513
-								if ($entry->getName() === $entryName) {
1514
-									$entry->addSubEntry($rootEntry, $mountPoint);
1515
-								}
1516
-							}
1517
-						} else { //mountpoint in this folder, add an entry for it
1518
-							$rootEntry['name'] = $relativePath;
1519
-							$rootEntry['type'] = $rootEntry['mimetype'] === 'httpd/unix-directory' ? 'dir' : 'file';
1520
-							$permissions = $rootEntry['permissions'];
1521
-							// do not allow renaming/deleting the mount point if they are not shared files/folders
1522
-							// for shared files/folders we use the permissions given by the owner
1523
-							if ($mount instanceof MoveableMount) {
1524
-								$rootEntry['permissions'] = $permissions | \OCP\Constants::PERMISSION_UPDATE | \OCP\Constants::PERMISSION_DELETE;
1525
-							} else {
1526
-								$rootEntry['permissions'] = $permissions & (\OCP\Constants::PERMISSION_ALL - (\OCP\Constants::PERMISSION_UPDATE | \OCP\Constants::PERMISSION_DELETE));
1527
-							}
1528
-
1529
-							$rootEntry['path'] = substr(Filesystem::normalizePath($path . '/' . $rootEntry['name']), strlen($user) + 2); // full path without /$user/
1530
-
1531
-							// if sharing was disabled for the user we remove the share permissions
1532
-							if (\OCP\Util::isSharingDisabledForUser()) {
1533
-								$rootEntry['permissions'] = $rootEntry['permissions'] & ~\OCP\Constants::PERMISSION_SHARE;
1534
-							}
1535
-
1536
-							$owner = $this->getUserObjectForOwner($subStorage->getOwner(''));
1537
-							$files[$rootEntry->getName()] = new FileInfo($path . '/' . $rootEntry['name'], $subStorage, '', $rootEntry, $mount, $owner);
1538
-						}
1539
-					}
1540
-				}
1541
-			}
1542
-
1543
-			if ($mimetype_filter) {
1544
-				$files = array_filter($files, function (FileInfo $file) use ($mimetype_filter) {
1545
-					if (strpos($mimetype_filter, '/')) {
1546
-						return $file->getMimetype() === $mimetype_filter;
1547
-					} else {
1548
-						return $file->getMimePart() === $mimetype_filter;
1549
-					}
1550
-				});
1551
-			}
1552
-
1553
-			return array_values($files);
1554
-		} else {
1555
-			return [];
1556
-		}
1557
-	}
1558
-
1559
-	/**
1560
-	 * change file metadata
1561
-	 *
1562
-	 * @param string $path
1563
-	 * @param array|\OCP\Files\FileInfo $data
1564
-	 * @return int
1565
-	 *
1566
-	 * returns the fileid of the updated file
1567
-	 */
1568
-	public function putFileInfo($path, $data) {
1569
-		$this->assertPathLength($path);
1570
-		if ($data instanceof FileInfo) {
1571
-			$data = $data->getData();
1572
-		}
1573
-		$path = Filesystem::normalizePath($this->fakeRoot . '/' . $path);
1574
-		/**
1575
-		 * @var \OC\Files\Storage\Storage $storage
1576
-		 * @var string $internalPath
1577
-		 */
1578
-		[$storage, $internalPath] = Filesystem::resolvePath($path);
1579
-		if ($storage) {
1580
-			$cache = $storage->getCache($path);
1581
-
1582
-			if (!$cache->inCache($internalPath)) {
1583
-				$scanner = $storage->getScanner($internalPath);
1584
-				$scanner->scan($internalPath, Cache\Scanner::SCAN_SHALLOW);
1585
-			}
1586
-
1587
-			return $cache->put($internalPath, $data);
1588
-		} else {
1589
-			return -1;
1590
-		}
1591
-	}
1592
-
1593
-	/**
1594
-	 * search for files with the name matching $query
1595
-	 *
1596
-	 * @param string $query
1597
-	 * @return FileInfo[]
1598
-	 */
1599
-	public function search($query) {
1600
-		return $this->searchCommon('search', ['%' . $query . '%']);
1601
-	}
1602
-
1603
-	/**
1604
-	 * search for files with the name matching $query
1605
-	 *
1606
-	 * @param string $query
1607
-	 * @return FileInfo[]
1608
-	 */
1609
-	public function searchRaw($query) {
1610
-		return $this->searchCommon('search', [$query]);
1611
-	}
1612
-
1613
-	/**
1614
-	 * search for files by mimetype
1615
-	 *
1616
-	 * @param string $mimetype
1617
-	 * @return FileInfo[]
1618
-	 */
1619
-	public function searchByMime($mimetype) {
1620
-		return $this->searchCommon('searchByMime', [$mimetype]);
1621
-	}
1622
-
1623
-	/**
1624
-	 * search for files by tag
1625
-	 *
1626
-	 * @param string|int $tag name or tag id
1627
-	 * @param string $userId owner of the tags
1628
-	 * @return FileInfo[]
1629
-	 */
1630
-	public function searchByTag($tag, $userId) {
1631
-		return $this->searchCommon('searchByTag', [$tag, $userId]);
1632
-	}
1633
-
1634
-	/**
1635
-	 * @param string $method cache method
1636
-	 * @param array $args
1637
-	 * @return FileInfo[]
1638
-	 */
1639
-	private function searchCommon($method, $args) {
1640
-		$files = [];
1641
-		$rootLength = strlen($this->fakeRoot);
1642
-
1643
-		$mount = $this->getMount('');
1644
-		$mountPoint = $mount->getMountPoint();
1645
-		$storage = $mount->getStorage();
1646
-		$userManager = \OC::$server->getUserManager();
1647
-		if ($storage) {
1648
-			$cache = $storage->getCache('');
1649
-
1650
-			$results = call_user_func_array([$cache, $method], $args);
1651
-			foreach ($results as $result) {
1652
-				if (substr($mountPoint . $result['path'], 0, $rootLength + 1) === $this->fakeRoot . '/') {
1653
-					$internalPath = $result['path'];
1654
-					$path = $mountPoint . $result['path'];
1655
-					$result['path'] = substr($mountPoint . $result['path'], $rootLength);
1656
-					$owner = $userManager->get($storage->getOwner($internalPath));
1657
-					$files[] = new FileInfo($path, $storage, $internalPath, $result, $mount, $owner);
1658
-				}
1659
-			}
1660
-
1661
-			$mounts = Filesystem::getMountManager()->findIn($this->fakeRoot);
1662
-			foreach ($mounts as $mount) {
1663
-				$mountPoint = $mount->getMountPoint();
1664
-				$storage = $mount->getStorage();
1665
-				if ($storage) {
1666
-					$cache = $storage->getCache('');
1667
-
1668
-					$relativeMountPoint = substr($mountPoint, $rootLength);
1669
-					$results = call_user_func_array([$cache, $method], $args);
1670
-					if ($results) {
1671
-						foreach ($results as $result) {
1672
-							$internalPath = $result['path'];
1673
-							$result['path'] = rtrim($relativeMountPoint . $result['path'], '/');
1674
-							$path = rtrim($mountPoint . $internalPath, '/');
1675
-							$owner = $userManager->get($storage->getOwner($internalPath));
1676
-							$files[] = new FileInfo($path, $storage, $internalPath, $result, $mount, $owner);
1677
-						}
1678
-					}
1679
-				}
1680
-			}
1681
-		}
1682
-		return $files;
1683
-	}
1684
-
1685
-	/**
1686
-	 * Get the owner for a file or folder
1687
-	 *
1688
-	 * @param string $path
1689
-	 * @return string the user id of the owner
1690
-	 * @throws NotFoundException
1691
-	 */
1692
-	public function getOwner($path) {
1693
-		$info = $this->getFileInfo($path);
1694
-		if (!$info) {
1695
-			throw new NotFoundException($path . ' not found while trying to get owner');
1696
-		}
1697
-
1698
-		if ($info->getOwner() === null) {
1699
-			throw new NotFoundException($path . ' has no owner');
1700
-		}
1701
-
1702
-		return $info->getOwner()->getUID();
1703
-	}
1704
-
1705
-	/**
1706
-	 * get the ETag for a file or folder
1707
-	 *
1708
-	 * @param string $path
1709
-	 * @return string
1710
-	 */
1711
-	public function getETag($path) {
1712
-		/**
1713
-		 * @var Storage\Storage $storage
1714
-		 * @var string $internalPath
1715
-		 */
1716
-		[$storage, $internalPath] = $this->resolvePath($path);
1717
-		if ($storage) {
1718
-			return $storage->getETag($internalPath);
1719
-		} else {
1720
-			return null;
1721
-		}
1722
-	}
1723
-
1724
-	/**
1725
-	 * Get the path of a file by id, relative to the view
1726
-	 *
1727
-	 * Note that the resulting path is not guarantied to be unique for the id, multiple paths can point to the same file
1728
-	 *
1729
-	 * @param int $id
1730
-	 * @param int|null $storageId
1731
-	 * @return string
1732
-	 * @throws NotFoundException
1733
-	 */
1734
-	public function getPath($id, int $storageId = null) {
1735
-		$id = (int)$id;
1736
-		$manager = Filesystem::getMountManager();
1737
-		$mounts = $manager->findIn($this->fakeRoot);
1738
-		$mounts[] = $manager->find($this->fakeRoot);
1739
-		// reverse the array so we start with the storage this view is in
1740
-		// which is the most likely to contain the file we're looking for
1741
-		$mounts = array_reverse($mounts);
1742
-
1743
-		// put non shared mounts in front of the shared mount
1744
-		// this prevent unneeded recursion into shares
1745
-		usort($mounts, function (IMountPoint $a, IMountPoint $b) {
1746
-			return $a instanceof SharedMount && (!$b instanceof SharedMount) ? 1 : -1;
1747
-		});
1748
-
1749
-		if (!is_null($storageId)) {
1750
-			$mounts = array_filter($mounts, function (IMountPoint $mount) use ($storageId) {
1751
-				return $mount->getNumericStorageId() === $storageId;
1752
-			});
1753
-		}
1754
-
1755
-		foreach ($mounts as $mount) {
1756
-			/**
1757
-			 * @var \OC\Files\Mount\MountPoint $mount
1758
-			 */
1759
-			if ($mount->getStorage()) {
1760
-				$cache = $mount->getStorage()->getCache();
1761
-				$internalPath = $cache->getPathById($id);
1762
-				if (is_string($internalPath)) {
1763
-					$fullPath = $mount->getMountPoint() . $internalPath;
1764
-					if (!is_null($path = $this->getRelativePath($fullPath))) {
1765
-						return $path;
1766
-					}
1767
-				}
1768
-			}
1769
-		}
1770
-		throw new NotFoundException(sprintf('File with id "%s" has not been found.', $id));
1771
-	}
1772
-
1773
-	/**
1774
-	 * @param string $path
1775
-	 * @throws InvalidPathException
1776
-	 */
1777
-	private function assertPathLength($path) {
1778
-		$maxLen = min(PHP_MAXPATHLEN, 4000);
1779
-		// Check for the string length - performed using isset() instead of strlen()
1780
-		// because isset() is about 5x-40x faster.
1781
-		if (isset($path[$maxLen])) {
1782
-			$pathLen = strlen($path);
1783
-			throw new \OCP\Files\InvalidPathException("Path length($pathLen) exceeds max path length($maxLen): $path");
1784
-		}
1785
-	}
1786
-
1787
-	/**
1788
-	 * check if it is allowed to move a mount point to a given target.
1789
-	 * It is not allowed to move a mount point into a different mount point or
1790
-	 * into an already shared folder
1791
-	 *
1792
-	 * @param IStorage $targetStorage
1793
-	 * @param string $targetInternalPath
1794
-	 * @return boolean
1795
-	 */
1796
-	private function targetIsNotShared(IStorage $targetStorage, string $targetInternalPath) {
1797
-
1798
-		// note: cannot use the view because the target is already locked
1799
-		$fileId = (int)$targetStorage->getCache()->getId($targetInternalPath);
1800
-		if ($fileId === -1) {
1801
-			// target might not exist, need to check parent instead
1802
-			$fileId = (int)$targetStorage->getCache()->getId(dirname($targetInternalPath));
1803
-		}
1804
-
1805
-		// check if any of the parents were shared by the current owner (include collections)
1806
-		$shares = \OCP\Share::getItemShared(
1807
-			'folder',
1808
-			$fileId,
1809
-			\OCP\Share::FORMAT_NONE,
1810
-			null,
1811
-			true
1812
-		);
1813
-
1814
-		if (count($shares) > 0) {
1815
-			\OCP\Util::writeLog('files',
1816
-				'It is not allowed to move one mount point into a shared folder',
1817
-				ILogger::DEBUG);
1818
-			return false;
1819
-		}
1820
-
1821
-		return true;
1822
-	}
1823
-
1824
-	/**
1825
-	 * Get a fileinfo object for files that are ignored in the cache (part files)
1826
-	 *
1827
-	 * @param string $path
1828
-	 * @return \OCP\Files\FileInfo
1829
-	 */
1830
-	private function getPartFileInfo($path) {
1831
-		$mount = $this->getMount($path);
1832
-		$storage = $mount->getStorage();
1833
-		$internalPath = $mount->getInternalPath($this->getAbsolutePath($path));
1834
-		$owner = \OC::$server->getUserManager()->get($storage->getOwner($internalPath));
1835
-		return new FileInfo(
1836
-			$this->getAbsolutePath($path),
1837
-			$storage,
1838
-			$internalPath,
1839
-			[
1840
-				'fileid' => null,
1841
-				'mimetype' => $storage->getMimeType($internalPath),
1842
-				'name' => basename($path),
1843
-				'etag' => null,
1844
-				'size' => $storage->filesize($internalPath),
1845
-				'mtime' => $storage->filemtime($internalPath),
1846
-				'encrypted' => false,
1847
-				'permissions' => \OCP\Constants::PERMISSION_ALL
1848
-			],
1849
-			$mount,
1850
-			$owner
1851
-		);
1852
-	}
1853
-
1854
-	/**
1855
-	 * @param string $path
1856
-	 * @param string $fileName
1857
-	 * @throws InvalidPathException
1858
-	 */
1859
-	public function verifyPath($path, $fileName) {
1860
-		try {
1861
-			/** @type \OCP\Files\Storage $storage */
1862
-			[$storage, $internalPath] = $this->resolvePath($path);
1863
-			$storage->verifyPath($internalPath, $fileName);
1864
-		} catch (ReservedWordException $ex) {
1865
-			$l = \OC::$server->getL10N('lib');
1866
-			throw new InvalidPathException($l->t('File name is a reserved word'));
1867
-		} catch (InvalidCharacterInPathException $ex) {
1868
-			$l = \OC::$server->getL10N('lib');
1869
-			throw new InvalidPathException($l->t('File name contains at least one invalid character'));
1870
-		} catch (FileNameTooLongException $ex) {
1871
-			$l = \OC::$server->getL10N('lib');
1872
-			throw new InvalidPathException($l->t('File name is too long'));
1873
-		} catch (InvalidDirectoryException $ex) {
1874
-			$l = \OC::$server->getL10N('lib');
1875
-			throw new InvalidPathException($l->t('Dot files are not allowed'));
1876
-		} catch (EmptyFileNameException $ex) {
1877
-			$l = \OC::$server->getL10N('lib');
1878
-			throw new InvalidPathException($l->t('Empty filename is not allowed'));
1879
-		}
1880
-	}
1881
-
1882
-	/**
1883
-	 * get all parent folders of $path
1884
-	 *
1885
-	 * @param string $path
1886
-	 * @return string[]
1887
-	 */
1888
-	private function getParents($path) {
1889
-		$path = trim($path, '/');
1890
-		if (!$path) {
1891
-			return [];
1892
-		}
1893
-
1894
-		$parts = explode('/', $path);
1895
-
1896
-		// remove the single file
1897
-		array_pop($parts);
1898
-		$result = ['/'];
1899
-		$resultPath = '';
1900
-		foreach ($parts as $part) {
1901
-			if ($part) {
1902
-				$resultPath .= '/' . $part;
1903
-				$result[] = $resultPath;
1904
-			}
1905
-		}
1906
-		return $result;
1907
-	}
1908
-
1909
-	/**
1910
-	 * Returns the mount point for which to lock
1911
-	 *
1912
-	 * @param string $absolutePath absolute path
1913
-	 * @param bool $useParentMount true to return parent mount instead of whatever
1914
-	 * is mounted directly on the given path, false otherwise
1915
-	 * @return \OC\Files\Mount\MountPoint mount point for which to apply locks
1916
-	 */
1917
-	private function getMountForLock($absolutePath, $useParentMount = false) {
1918
-		$results = [];
1919
-		$mount = Filesystem::getMountManager()->find($absolutePath);
1920
-		if (!$mount) {
1921
-			return $results;
1922
-		}
1923
-
1924
-		if ($useParentMount) {
1925
-			// find out if something is mounted directly on the path
1926
-			$internalPath = $mount->getInternalPath($absolutePath);
1927
-			if ($internalPath === '') {
1928
-				// resolve the parent mount instead
1929
-				$mount = Filesystem::getMountManager()->find(dirname($absolutePath));
1930
-			}
1931
-		}
1932
-
1933
-		return $mount;
1934
-	}
1935
-
1936
-	/**
1937
-	 * Lock the given path
1938
-	 *
1939
-	 * @param string $path the path of the file to lock, relative to the view
1940
-	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
1941
-	 * @param bool $lockMountPoint true to lock the mount point, false to lock the attached mount/storage
1942
-	 *
1943
-	 * @return bool False if the path is excluded from locking, true otherwise
1944
-	 * @throws LockedException if the path is already locked
1945
-	 */
1946
-	private function lockPath($path, $type, $lockMountPoint = false) {
1947
-		$absolutePath = $this->getAbsolutePath($path);
1948
-		$absolutePath = Filesystem::normalizePath($absolutePath);
1949
-		if (!$this->shouldLockFile($absolutePath)) {
1950
-			return false;
1951
-		}
1952
-
1953
-		$mount = $this->getMountForLock($absolutePath, $lockMountPoint);
1954
-		if ($mount) {
1955
-			try {
1956
-				$storage = $mount->getStorage();
1957
-				if ($storage && $storage->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) {
1958
-					$storage->acquireLock(
1959
-						$mount->getInternalPath($absolutePath),
1960
-						$type,
1961
-						$this->lockingProvider
1962
-					);
1963
-				}
1964
-			} catch (LockedException $e) {
1965
-				// rethrow with the a human-readable path
1966
-				throw new LockedException(
1967
-					$this->getPathRelativeToFiles($absolutePath),
1968
-					$e,
1969
-					$e->getExistingLock()
1970
-				);
1971
-			}
1972
-		}
1973
-
1974
-		return true;
1975
-	}
1976
-
1977
-	/**
1978
-	 * Change the lock type
1979
-	 *
1980
-	 * @param string $path the path of the file to lock, relative to the view
1981
-	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
1982
-	 * @param bool $lockMountPoint true to lock the mount point, false to lock the attached mount/storage
1983
-	 *
1984
-	 * @return bool False if the path is excluded from locking, true otherwise
1985
-	 * @throws LockedException if the path is already locked
1986
-	 */
1987
-	public function changeLock($path, $type, $lockMountPoint = false) {
1988
-		$path = Filesystem::normalizePath($path);
1989
-		$absolutePath = $this->getAbsolutePath($path);
1990
-		$absolutePath = Filesystem::normalizePath($absolutePath);
1991
-		if (!$this->shouldLockFile($absolutePath)) {
1992
-			return false;
1993
-		}
1994
-
1995
-		$mount = $this->getMountForLock($absolutePath, $lockMountPoint);
1996
-		if ($mount) {
1997
-			try {
1998
-				$storage = $mount->getStorage();
1999
-				if ($storage && $storage->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) {
2000
-					$storage->changeLock(
2001
-						$mount->getInternalPath($absolutePath),
2002
-						$type,
2003
-						$this->lockingProvider
2004
-					);
2005
-				}
2006
-			} catch (LockedException $e) {
2007
-				try {
2008
-					// rethrow with the a human-readable path
2009
-					throw new LockedException(
2010
-						$this->getPathRelativeToFiles($absolutePath),
2011
-						$e,
2012
-						$e->getExistingLock()
2013
-					);
2014
-				} catch (\InvalidArgumentException $ex) {
2015
-					throw new LockedException(
2016
-						$absolutePath,
2017
-						$ex,
2018
-						$e->getExistingLock()
2019
-					);
2020
-				}
2021
-			}
2022
-		}
2023
-
2024
-		return true;
2025
-	}
2026
-
2027
-	/**
2028
-	 * Unlock the given path
2029
-	 *
2030
-	 * @param string $path the path of the file to unlock, relative to the view
2031
-	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
2032
-	 * @param bool $lockMountPoint true to lock the mount point, false to lock the attached mount/storage
2033
-	 *
2034
-	 * @return bool False if the path is excluded from locking, true otherwise
2035
-	 * @throws LockedException
2036
-	 */
2037
-	private function unlockPath($path, $type, $lockMountPoint = false) {
2038
-		$absolutePath = $this->getAbsolutePath($path);
2039
-		$absolutePath = Filesystem::normalizePath($absolutePath);
2040
-		if (!$this->shouldLockFile($absolutePath)) {
2041
-			return false;
2042
-		}
2043
-
2044
-		$mount = $this->getMountForLock($absolutePath, $lockMountPoint);
2045
-		if ($mount) {
2046
-			$storage = $mount->getStorage();
2047
-			if ($storage && $storage->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) {
2048
-				$storage->releaseLock(
2049
-					$mount->getInternalPath($absolutePath),
2050
-					$type,
2051
-					$this->lockingProvider
2052
-				);
2053
-			}
2054
-		}
2055
-
2056
-		return true;
2057
-	}
2058
-
2059
-	/**
2060
-	 * Lock a path and all its parents up to the root of the view
2061
-	 *
2062
-	 * @param string $path the path of the file to lock relative to the view
2063
-	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
2064
-	 * @param bool $lockMountPoint true to lock the mount point, false to lock the attached mount/storage
2065
-	 *
2066
-	 * @return bool False if the path is excluded from locking, true otherwise
2067
-	 * @throws LockedException
2068
-	 */
2069
-	public function lockFile($path, $type, $lockMountPoint = false) {
2070
-		$absolutePath = $this->getAbsolutePath($path);
2071
-		$absolutePath = Filesystem::normalizePath($absolutePath);
2072
-		if (!$this->shouldLockFile($absolutePath)) {
2073
-			return false;
2074
-		}
2075
-
2076
-		$this->lockPath($path, $type, $lockMountPoint);
2077
-
2078
-		$parents = $this->getParents($path);
2079
-		foreach ($parents as $parent) {
2080
-			$this->lockPath($parent, ILockingProvider::LOCK_SHARED);
2081
-		}
2082
-
2083
-		return true;
2084
-	}
2085
-
2086
-	/**
2087
-	 * Unlock a path and all its parents up to the root of the view
2088
-	 *
2089
-	 * @param string $path the path of the file to lock relative to the view
2090
-	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
2091
-	 * @param bool $lockMountPoint true to lock the mount point, false to lock the attached mount/storage
2092
-	 *
2093
-	 * @return bool False if the path is excluded from locking, true otherwise
2094
-	 * @throws LockedException
2095
-	 */
2096
-	public function unlockFile($path, $type, $lockMountPoint = false) {
2097
-		$absolutePath = $this->getAbsolutePath($path);
2098
-		$absolutePath = Filesystem::normalizePath($absolutePath);
2099
-		if (!$this->shouldLockFile($absolutePath)) {
2100
-			return false;
2101
-		}
2102
-
2103
-		$this->unlockPath($path, $type, $lockMountPoint);
2104
-
2105
-		$parents = $this->getParents($path);
2106
-		foreach ($parents as $parent) {
2107
-			$this->unlockPath($parent, ILockingProvider::LOCK_SHARED);
2108
-		}
2109
-
2110
-		return true;
2111
-	}
2112
-
2113
-	/**
2114
-	 * Only lock files in data/user/files/
2115
-	 *
2116
-	 * @param string $path Absolute path to the file/folder we try to (un)lock
2117
-	 * @return bool
2118
-	 */
2119
-	protected function shouldLockFile($path) {
2120
-		$path = Filesystem::normalizePath($path);
2121
-
2122
-		$pathSegments = explode('/', $path);
2123
-		if (isset($pathSegments[2])) {
2124
-			// E.g.: /username/files/path-to-file
2125
-			return ($pathSegments[2] === 'files') && (count($pathSegments) > 3);
2126
-		}
2127
-
2128
-		return strpos($path, '/appdata_') !== 0;
2129
-	}
2130
-
2131
-	/**
2132
-	 * Shortens the given absolute path to be relative to
2133
-	 * "$user/files".
2134
-	 *
2135
-	 * @param string $absolutePath absolute path which is under "files"
2136
-	 *
2137
-	 * @return string path relative to "files" with trimmed slashes or null
2138
-	 * if the path was NOT relative to files
2139
-	 *
2140
-	 * @throws \InvalidArgumentException if the given path was not under "files"
2141
-	 * @since 8.1.0
2142
-	 */
2143
-	public function getPathRelativeToFiles($absolutePath) {
2144
-		$path = Filesystem::normalizePath($absolutePath);
2145
-		$parts = explode('/', trim($path, '/'), 3);
2146
-		// "$user", "files", "path/to/dir"
2147
-		if (!isset($parts[1]) || $parts[1] !== 'files') {
2148
-			$this->logger->error(
2149
-				'$absolutePath must be relative to "files", value is "%s"',
2150
-				[
2151
-					$absolutePath
2152
-				]
2153
-			);
2154
-			throw new \InvalidArgumentException('$absolutePath must be relative to "files"');
2155
-		}
2156
-		if (isset($parts[2])) {
2157
-			return $parts[2];
2158
-		}
2159
-		return '';
2160
-	}
2161
-
2162
-	/**
2163
-	 * @param string $filename
2164
-	 * @return array
2165
-	 * @throws \OC\User\NoUserException
2166
-	 * @throws NotFoundException
2167
-	 */
2168
-	public function getUidAndFilename($filename) {
2169
-		$info = $this->getFileInfo($filename);
2170
-		if (!$info instanceof \OCP\Files\FileInfo) {
2171
-			throw new NotFoundException($this->getAbsolutePath($filename) . ' not found');
2172
-		}
2173
-		$uid = $info->getOwner()->getUID();
2174
-		if ($uid != \OC_User::getUser()) {
2175
-			Filesystem::initMountPoints($uid);
2176
-			$ownerView = new View('/' . $uid . '/files');
2177
-			try {
2178
-				$filename = $ownerView->getPath($info['fileid']);
2179
-			} catch (NotFoundException $e) {
2180
-				throw new NotFoundException('File with id ' . $info['fileid'] . ' not found for user ' . $uid);
2181
-			}
2182
-		}
2183
-		return [$uid, $filename];
2184
-	}
2185
-
2186
-	/**
2187
-	 * Creates parent non-existing folders
2188
-	 *
2189
-	 * @param string $filePath
2190
-	 * @return bool
2191
-	 */
2192
-	private function createParentDirectories($filePath) {
2193
-		$directoryParts = explode('/', $filePath);
2194
-		$directoryParts = array_filter($directoryParts);
2195
-		foreach ($directoryParts as $key => $part) {
2196
-			$currentPathElements = array_slice($directoryParts, 0, $key);
2197
-			$currentPath = '/' . implode('/', $currentPathElements);
2198
-			if ($this->is_file($currentPath)) {
2199
-				return false;
2200
-			}
2201
-			if (!$this->file_exists($currentPath)) {
2202
-				$this->mkdir($currentPath);
2203
-			}
2204
-		}
2205
-
2206
-		return true;
2207
-	}
88
+    /** @var string */
89
+    private $fakeRoot = '';
90
+
91
+    /**
92
+     * @var \OCP\Lock\ILockingProvider
93
+     */
94
+    protected $lockingProvider;
95
+
96
+    private $lockingEnabled;
97
+
98
+    private $updaterEnabled = true;
99
+
100
+    /** @var \OC\User\Manager */
101
+    private $userManager;
102
+
103
+    /** @var \OCP\ILogger */
104
+    private $logger;
105
+
106
+    /**
107
+     * @param string $root
108
+     * @throws \Exception If $root contains an invalid path
109
+     */
110
+    public function __construct($root = '') {
111
+        if (is_null($root)) {
112
+            throw new \InvalidArgumentException('Root can\'t be null');
113
+        }
114
+        if (!Filesystem::isValidPath($root)) {
115
+            throw new \Exception();
116
+        }
117
+
118
+        $this->fakeRoot = $root;
119
+        $this->lockingProvider = \OC::$server->getLockingProvider();
120
+        $this->lockingEnabled = !($this->lockingProvider instanceof \OC\Lock\NoopLockingProvider);
121
+        $this->userManager = \OC::$server->getUserManager();
122
+        $this->logger = \OC::$server->getLogger();
123
+    }
124
+
125
+    public function getAbsolutePath($path = '/') {
126
+        if ($path === null) {
127
+            return null;
128
+        }
129
+        $this->assertPathLength($path);
130
+        if ($path === '') {
131
+            $path = '/';
132
+        }
133
+        if ($path[0] !== '/') {
134
+            $path = '/' . $path;
135
+        }
136
+        return $this->fakeRoot . $path;
137
+    }
138
+
139
+    /**
140
+     * change the root to a fake root
141
+     *
142
+     * @param string $fakeRoot
143
+     * @return boolean|null
144
+     */
145
+    public function chroot($fakeRoot) {
146
+        if (!$fakeRoot == '') {
147
+            if ($fakeRoot[0] !== '/') {
148
+                $fakeRoot = '/' . $fakeRoot;
149
+            }
150
+        }
151
+        $this->fakeRoot = $fakeRoot;
152
+    }
153
+
154
+    /**
155
+     * get the fake root
156
+     *
157
+     * @return string
158
+     */
159
+    public function getRoot() {
160
+        return $this->fakeRoot;
161
+    }
162
+
163
+    /**
164
+     * get path relative to the root of the view
165
+     *
166
+     * @param string $path
167
+     * @return string
168
+     */
169
+    public function getRelativePath($path) {
170
+        $this->assertPathLength($path);
171
+        if ($this->fakeRoot == '') {
172
+            return $path;
173
+        }
174
+
175
+        if (rtrim($path, '/') === rtrim($this->fakeRoot, '/')) {
176
+            return '/';
177
+        }
178
+
179
+        // missing slashes can cause wrong matches!
180
+        $root = rtrim($this->fakeRoot, '/') . '/';
181
+
182
+        if (strpos($path, $root) !== 0) {
183
+            return null;
184
+        } else {
185
+            $path = substr($path, strlen($this->fakeRoot));
186
+            if (strlen($path) === 0) {
187
+                return '/';
188
+            } else {
189
+                return $path;
190
+            }
191
+        }
192
+    }
193
+
194
+    /**
195
+     * get the mountpoint of the storage object for a path
196
+     * ( note: because a storage is not always mounted inside the fakeroot, the
197
+     * returned mountpoint is relative to the absolute root of the filesystem
198
+     * and does not take the chroot into account )
199
+     *
200
+     * @param string $path
201
+     * @return string
202
+     */
203
+    public function getMountPoint($path) {
204
+        return Filesystem::getMountPoint($this->getAbsolutePath($path));
205
+    }
206
+
207
+    /**
208
+     * get the mountpoint of the storage object for a path
209
+     * ( note: because a storage is not always mounted inside the fakeroot, the
210
+     * returned mountpoint is relative to the absolute root of the filesystem
211
+     * and does not take the chroot into account )
212
+     *
213
+     * @param string $path
214
+     * @return \OCP\Files\Mount\IMountPoint
215
+     */
216
+    public function getMount($path) {
217
+        return Filesystem::getMountManager()->find($this->getAbsolutePath($path));
218
+    }
219
+
220
+    /**
221
+     * resolve a path to a storage and internal path
222
+     *
223
+     * @param string $path
224
+     * @return array an array consisting of the storage and the internal path
225
+     */
226
+    public function resolvePath($path) {
227
+        $a = $this->getAbsolutePath($path);
228
+        $p = Filesystem::normalizePath($a);
229
+        return Filesystem::resolvePath($p);
230
+    }
231
+
232
+    /**
233
+     * return the path to a local version of the file
234
+     * we need this because we can't know if a file is stored local or not from
235
+     * outside the filestorage and for some purposes a local file is needed
236
+     *
237
+     * @param string $path
238
+     * @return string
239
+     */
240
+    public function getLocalFile($path) {
241
+        $parent = substr($path, 0, strrpos($path, '/'));
242
+        $path = $this->getAbsolutePath($path);
243
+        [$storage, $internalPath] = Filesystem::resolvePath($path);
244
+        if (Filesystem::isValidPath($parent) and $storage) {
245
+            return $storage->getLocalFile($internalPath);
246
+        } else {
247
+            return null;
248
+        }
249
+    }
250
+
251
+    /**
252
+     * @param string $path
253
+     * @return string
254
+     */
255
+    public function getLocalFolder($path) {
256
+        $parent = substr($path, 0, strrpos($path, '/'));
257
+        $path = $this->getAbsolutePath($path);
258
+        [$storage, $internalPath] = Filesystem::resolvePath($path);
259
+        if (Filesystem::isValidPath($parent) and $storage) {
260
+            return $storage->getLocalFolder($internalPath);
261
+        } else {
262
+            return null;
263
+        }
264
+    }
265
+
266
+    /**
267
+     * the following functions operate with arguments and return values identical
268
+     * to those of their PHP built-in equivalents. Mostly they are merely wrappers
269
+     * for \OC\Files\Storage\Storage via basicOperation().
270
+     */
271
+    public function mkdir($path) {
272
+        return $this->basicOperation('mkdir', $path, ['create', 'write']);
273
+    }
274
+
275
+    /**
276
+     * remove mount point
277
+     *
278
+     * @param \OC\Files\Mount\MoveableMount $mount
279
+     * @param string $path relative to data/
280
+     * @return boolean
281
+     */
282
+    protected function removeMount($mount, $path) {
283
+        if ($mount instanceof MoveableMount) {
284
+            // cut of /user/files to get the relative path to data/user/files
285
+            $pathParts = explode('/', $path, 4);
286
+            $relPath = '/' . $pathParts[3];
287
+            $this->lockFile($relPath, ILockingProvider::LOCK_SHARED, true);
288
+            \OC_Hook::emit(
289
+                Filesystem::CLASSNAME, "umount",
290
+                [Filesystem::signal_param_path => $relPath]
291
+            );
292
+            $this->changeLock($relPath, ILockingProvider::LOCK_EXCLUSIVE, true);
293
+            $result = $mount->removeMount();
294
+            $this->changeLock($relPath, ILockingProvider::LOCK_SHARED, true);
295
+            if ($result) {
296
+                \OC_Hook::emit(
297
+                    Filesystem::CLASSNAME, "post_umount",
298
+                    [Filesystem::signal_param_path => $relPath]
299
+                );
300
+            }
301
+            $this->unlockFile($relPath, ILockingProvider::LOCK_SHARED, true);
302
+            return $result;
303
+        } else {
304
+            // do not allow deleting the storage's root / the mount point
305
+            // because for some storages it might delete the whole contents
306
+            // but isn't supposed to work that way
307
+            return false;
308
+        }
309
+    }
310
+
311
+    public function disableCacheUpdate() {
312
+        $this->updaterEnabled = false;
313
+    }
314
+
315
+    public function enableCacheUpdate() {
316
+        $this->updaterEnabled = true;
317
+    }
318
+
319
+    protected function writeUpdate(Storage $storage, $internalPath, $time = null) {
320
+        if ($this->updaterEnabled) {
321
+            if (is_null($time)) {
322
+                $time = time();
323
+            }
324
+            $storage->getUpdater()->update($internalPath, $time);
325
+        }
326
+    }
327
+
328
+    protected function removeUpdate(Storage $storage, $internalPath) {
329
+        if ($this->updaterEnabled) {
330
+            $storage->getUpdater()->remove($internalPath);
331
+        }
332
+    }
333
+
334
+    protected function renameUpdate(Storage $sourceStorage, Storage $targetStorage, $sourceInternalPath, $targetInternalPath) {
335
+        if ($this->updaterEnabled) {
336
+            $targetStorage->getUpdater()->renameFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
337
+        }
338
+    }
339
+
340
+    /**
341
+     * @param string $path
342
+     * @return bool|mixed
343
+     */
344
+    public function rmdir($path) {
345
+        $absolutePath = $this->getAbsolutePath($path);
346
+        $mount = Filesystem::getMountManager()->find($absolutePath);
347
+        if ($mount->getInternalPath($absolutePath) === '') {
348
+            return $this->removeMount($mount, $absolutePath);
349
+        }
350
+        if ($this->is_dir($path)) {
351
+            $result = $this->basicOperation('rmdir', $path, ['delete']);
352
+        } else {
353
+            $result = false;
354
+        }
355
+
356
+        if (!$result && !$this->file_exists($path)) { //clear ghost files from the cache on delete
357
+            $storage = $mount->getStorage();
358
+            $internalPath = $mount->getInternalPath($absolutePath);
359
+            $storage->getUpdater()->remove($internalPath);
360
+        }
361
+        return $result;
362
+    }
363
+
364
+    /**
365
+     * @param string $path
366
+     * @return resource
367
+     */
368
+    public function opendir($path) {
369
+        return $this->basicOperation('opendir', $path, ['read']);
370
+    }
371
+
372
+    /**
373
+     * @param string $path
374
+     * @return bool|mixed
375
+     */
376
+    public function is_dir($path) {
377
+        if ($path == '/') {
378
+            return true;
379
+        }
380
+        return $this->basicOperation('is_dir', $path);
381
+    }
382
+
383
+    /**
384
+     * @param string $path
385
+     * @return bool|mixed
386
+     */
387
+    public function is_file($path) {
388
+        if ($path == '/') {
389
+            return false;
390
+        }
391
+        return $this->basicOperation('is_file', $path);
392
+    }
393
+
394
+    /**
395
+     * @param string $path
396
+     * @return mixed
397
+     */
398
+    public function stat($path) {
399
+        return $this->basicOperation('stat', $path);
400
+    }
401
+
402
+    /**
403
+     * @param string $path
404
+     * @return mixed
405
+     */
406
+    public function filetype($path) {
407
+        return $this->basicOperation('filetype', $path);
408
+    }
409
+
410
+    /**
411
+     * @param string $path
412
+     * @return mixed
413
+     */
414
+    public function filesize($path) {
415
+        return $this->basicOperation('filesize', $path);
416
+    }
417
+
418
+    /**
419
+     * @param string $path
420
+     * @return bool|mixed
421
+     * @throws \OCP\Files\InvalidPathException
422
+     */
423
+    public function readfile($path) {
424
+        $this->assertPathLength($path);
425
+        @ob_end_clean();
426
+        $handle = $this->fopen($path, 'rb');
427
+        if ($handle) {
428
+            $chunkSize = 524288; // 512 kB chunks
429
+            while (!feof($handle)) {
430
+                echo fread($handle, $chunkSize);
431
+                flush();
432
+            }
433
+            fclose($handle);
434
+            return $this->filesize($path);
435
+        }
436
+        return false;
437
+    }
438
+
439
+    /**
440
+     * @param string $path
441
+     * @param int $from
442
+     * @param int $to
443
+     * @return bool|mixed
444
+     * @throws \OCP\Files\InvalidPathException
445
+     * @throws \OCP\Files\UnseekableException
446
+     */
447
+    public function readfilePart($path, $from, $to) {
448
+        $this->assertPathLength($path);
449
+        @ob_end_clean();
450
+        $handle = $this->fopen($path, 'rb');
451
+        if ($handle) {
452
+            $chunkSize = 524288; // 512 kB chunks
453
+            $startReading = true;
454
+
455
+            if ($from !== 0 && $from !== '0' && fseek($handle, $from) !== 0) {
456
+                // forward file handle via chunked fread because fseek seem to have failed
457
+
458
+                $end = $from + 1;
459
+                while (!feof($handle) && ftell($handle) < $end && ftell($handle) !== $from) {
460
+                    $len = $from - ftell($handle);
461
+                    if ($len > $chunkSize) {
462
+                        $len = $chunkSize;
463
+                    }
464
+                    $result = fread($handle, $len);
465
+
466
+                    if ($result === false) {
467
+                        $startReading = false;
468
+                        break;
469
+                    }
470
+                }
471
+            }
472
+
473
+            if ($startReading) {
474
+                $end = $to + 1;
475
+                while (!feof($handle) && ftell($handle) < $end) {
476
+                    $len = $end - ftell($handle);
477
+                    if ($len > $chunkSize) {
478
+                        $len = $chunkSize;
479
+                    }
480
+                    echo fread($handle, $len);
481
+                    flush();
482
+                }
483
+                return ftell($handle) - $from;
484
+            }
485
+
486
+            throw new \OCP\Files\UnseekableException('fseek error');
487
+        }
488
+        return false;
489
+    }
490
+
491
+    /**
492
+     * @param string $path
493
+     * @return mixed
494
+     */
495
+    public function isCreatable($path) {
496
+        return $this->basicOperation('isCreatable', $path);
497
+    }
498
+
499
+    /**
500
+     * @param string $path
501
+     * @return mixed
502
+     */
503
+    public function isReadable($path) {
504
+        return $this->basicOperation('isReadable', $path);
505
+    }
506
+
507
+    /**
508
+     * @param string $path
509
+     * @return mixed
510
+     */
511
+    public function isUpdatable($path) {
512
+        return $this->basicOperation('isUpdatable', $path);
513
+    }
514
+
515
+    /**
516
+     * @param string $path
517
+     * @return bool|mixed
518
+     */
519
+    public function isDeletable($path) {
520
+        $absolutePath = $this->getAbsolutePath($path);
521
+        $mount = Filesystem::getMountManager()->find($absolutePath);
522
+        if ($mount->getInternalPath($absolutePath) === '') {
523
+            return $mount instanceof MoveableMount;
524
+        }
525
+        return $this->basicOperation('isDeletable', $path);
526
+    }
527
+
528
+    /**
529
+     * @param string $path
530
+     * @return mixed
531
+     */
532
+    public function isSharable($path) {
533
+        return $this->basicOperation('isSharable', $path);
534
+    }
535
+
536
+    /**
537
+     * @param string $path
538
+     * @return bool|mixed
539
+     */
540
+    public function file_exists($path) {
541
+        if ($path == '/') {
542
+            return true;
543
+        }
544
+        return $this->basicOperation('file_exists', $path);
545
+    }
546
+
547
+    /**
548
+     * @param string $path
549
+     * @return mixed
550
+     */
551
+    public function filemtime($path) {
552
+        return $this->basicOperation('filemtime', $path);
553
+    }
554
+
555
+    /**
556
+     * @param string $path
557
+     * @param int|string $mtime
558
+     * @return bool
559
+     */
560
+    public function touch($path, $mtime = null) {
561
+        if (!is_null($mtime) and !is_numeric($mtime)) {
562
+            $mtime = strtotime($mtime);
563
+        }
564
+
565
+        $hooks = ['touch'];
566
+
567
+        if (!$this->file_exists($path)) {
568
+            $hooks[] = 'create';
569
+            $hooks[] = 'write';
570
+        }
571
+        try {
572
+            $result = $this->basicOperation('touch', $path, $hooks, $mtime);
573
+        } catch (\Exception $e) {
574
+            $this->logger->logException($e, ['level' => ILogger::INFO, 'message' => 'Error while setting modified time']);
575
+            $result = false;
576
+        }
577
+        if (!$result) {
578
+            // If create file fails because of permissions on external storage like SMB folders,
579
+            // check file exists and return false if not.
580
+            if (!$this->file_exists($path)) {
581
+                return false;
582
+            }
583
+            if (is_null($mtime)) {
584
+                $mtime = time();
585
+            }
586
+            //if native touch fails, we emulate it by changing the mtime in the cache
587
+            $this->putFileInfo($path, ['mtime' => floor($mtime)]);
588
+        }
589
+        return true;
590
+    }
591
+
592
+    /**
593
+     * @param string $path
594
+     * @return mixed
595
+     * @throws LockedException
596
+     */
597
+    public function file_get_contents($path) {
598
+        return $this->basicOperation('file_get_contents', $path, ['read']);
599
+    }
600
+
601
+    /**
602
+     * @param bool $exists
603
+     * @param string $path
604
+     * @param bool $run
605
+     */
606
+    protected function emit_file_hooks_pre($exists, $path, &$run) {
607
+        if (!$exists) {
608
+            \OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_create, [
609
+                Filesystem::signal_param_path => $this->getHookPath($path),
610
+                Filesystem::signal_param_run => &$run,
611
+            ]);
612
+        } else {
613
+            \OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_update, [
614
+                Filesystem::signal_param_path => $this->getHookPath($path),
615
+                Filesystem::signal_param_run => &$run,
616
+            ]);
617
+        }
618
+        \OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_write, [
619
+            Filesystem::signal_param_path => $this->getHookPath($path),
620
+            Filesystem::signal_param_run => &$run,
621
+        ]);
622
+    }
623
+
624
+    /**
625
+     * @param bool $exists
626
+     * @param string $path
627
+     */
628
+    protected function emit_file_hooks_post($exists, $path) {
629
+        if (!$exists) {
630
+            \OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_post_create, [
631
+                Filesystem::signal_param_path => $this->getHookPath($path),
632
+            ]);
633
+        } else {
634
+            \OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_post_update, [
635
+                Filesystem::signal_param_path => $this->getHookPath($path),
636
+            ]);
637
+        }
638
+        \OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_post_write, [
639
+            Filesystem::signal_param_path => $this->getHookPath($path),
640
+        ]);
641
+    }
642
+
643
+    /**
644
+     * @param string $path
645
+     * @param string|resource $data
646
+     * @return bool|mixed
647
+     * @throws LockedException
648
+     */
649
+    public function file_put_contents($path, $data) {
650
+        if (is_resource($data)) { //not having to deal with streams in file_put_contents makes life easier
651
+            $absolutePath = Filesystem::normalizePath($this->getAbsolutePath($path));
652
+            if (Filesystem::isValidPath($path)
653
+                and !Filesystem::isFileBlacklisted($path)
654
+            ) {
655
+                $path = $this->getRelativePath($absolutePath);
656
+
657
+                $this->lockFile($path, ILockingProvider::LOCK_SHARED);
658
+
659
+                $exists = $this->file_exists($path);
660
+                $run = true;
661
+                if ($this->shouldEmitHooks($path)) {
662
+                    $this->emit_file_hooks_pre($exists, $path, $run);
663
+                }
664
+                if (!$run) {
665
+                    $this->unlockFile($path, ILockingProvider::LOCK_SHARED);
666
+                    return false;
667
+                }
668
+
669
+                try {
670
+                    $this->changeLock($path, ILockingProvider::LOCK_EXCLUSIVE);
671
+                } catch (\Exception $e) {
672
+                    // Release the shared lock before throwing.
673
+                    $this->unlockFile($path, ILockingProvider::LOCK_SHARED);
674
+                    throw $e;
675
+                }
676
+
677
+                /** @var \OC\Files\Storage\Storage $storage */
678
+                [$storage, $internalPath] = $this->resolvePath($path);
679
+                $target = $storage->fopen($internalPath, 'w');
680
+                if ($target) {
681
+                    [, $result] = \OC_Helper::streamCopy($data, $target);
682
+                    fclose($target);
683
+                    fclose($data);
684
+
685
+                    $this->writeUpdate($storage, $internalPath);
686
+
687
+                    $this->changeLock($path, ILockingProvider::LOCK_SHARED);
688
+
689
+                    if ($this->shouldEmitHooks($path) && $result !== false) {
690
+                        $this->emit_file_hooks_post($exists, $path);
691
+                    }
692
+                    $this->unlockFile($path, ILockingProvider::LOCK_SHARED);
693
+                    return $result;
694
+                } else {
695
+                    $this->unlockFile($path, ILockingProvider::LOCK_EXCLUSIVE);
696
+                    return false;
697
+                }
698
+            } else {
699
+                return false;
700
+            }
701
+        } else {
702
+            $hooks = $this->file_exists($path) ? ['update', 'write'] : ['create', 'write'];
703
+            return $this->basicOperation('file_put_contents', $path, $hooks, $data);
704
+        }
705
+    }
706
+
707
+    /**
708
+     * @param string $path
709
+     * @return bool|mixed
710
+     */
711
+    public function unlink($path) {
712
+        if ($path === '' || $path === '/') {
713
+            // do not allow deleting the root
714
+            return false;
715
+        }
716
+        $postFix = (substr($path, -1) === '/') ? '/' : '';
717
+        $absolutePath = Filesystem::normalizePath($this->getAbsolutePath($path));
718
+        $mount = Filesystem::getMountManager()->find($absolutePath . $postFix);
719
+        if ($mount and $mount->getInternalPath($absolutePath) === '') {
720
+            return $this->removeMount($mount, $absolutePath);
721
+        }
722
+        if ($this->is_dir($path)) {
723
+            $result = $this->basicOperation('rmdir', $path, ['delete']);
724
+        } else {
725
+            $result = $this->basicOperation('unlink', $path, ['delete']);
726
+        }
727
+        if (!$result && !$this->file_exists($path)) { //clear ghost files from the cache on delete
728
+            $storage = $mount->getStorage();
729
+            $internalPath = $mount->getInternalPath($absolutePath);
730
+            $storage->getUpdater()->remove($internalPath);
731
+            return true;
732
+        } else {
733
+            return $result;
734
+        }
735
+    }
736
+
737
+    /**
738
+     * @param string $directory
739
+     * @return bool|mixed
740
+     */
741
+    public function deleteAll($directory) {
742
+        return $this->rmdir($directory);
743
+    }
744
+
745
+    /**
746
+     * Rename/move a file or folder from the source path to target path.
747
+     *
748
+     * @param string $path1 source path
749
+     * @param string $path2 target path
750
+     *
751
+     * @return bool|mixed
752
+     * @throws LockedException
753
+     */
754
+    public function rename($path1, $path2) {
755
+        $absolutePath1 = Filesystem::normalizePath($this->getAbsolutePath($path1));
756
+        $absolutePath2 = Filesystem::normalizePath($this->getAbsolutePath($path2));
757
+        $result = false;
758
+        if (
759
+            Filesystem::isValidPath($path2)
760
+            and Filesystem::isValidPath($path1)
761
+            and !Filesystem::isFileBlacklisted($path2)
762
+        ) {
763
+            $path1 = $this->getRelativePath($absolutePath1);
764
+            $path2 = $this->getRelativePath($absolutePath2);
765
+            $exists = $this->file_exists($path2);
766
+
767
+            if ($path1 == null or $path2 == null) {
768
+                return false;
769
+            }
770
+
771
+            $this->lockFile($path1, ILockingProvider::LOCK_SHARED, true);
772
+            try {
773
+                $this->lockFile($path2, ILockingProvider::LOCK_SHARED, true);
774
+
775
+                $run = true;
776
+                if ($this->shouldEmitHooks($path1) && (Cache\Scanner::isPartialFile($path1) && !Cache\Scanner::isPartialFile($path2))) {
777
+                    // if it was a rename from a part file to a regular file it was a write and not a rename operation
778
+                    $this->emit_file_hooks_pre($exists, $path2, $run);
779
+                } elseif ($this->shouldEmitHooks($path1)) {
780
+                    \OC_Hook::emit(
781
+                        Filesystem::CLASSNAME, Filesystem::signal_rename,
782
+                        [
783
+                            Filesystem::signal_param_oldpath => $this->getHookPath($path1),
784
+                            Filesystem::signal_param_newpath => $this->getHookPath($path2),
785
+                            Filesystem::signal_param_run => &$run
786
+                        ]
787
+                    );
788
+                }
789
+                if ($run) {
790
+                    $this->verifyPath(dirname($path2), basename($path2));
791
+
792
+                    $manager = Filesystem::getMountManager();
793
+                    $mount1 = $this->getMount($path1);
794
+                    $mount2 = $this->getMount($path2);
795
+                    $storage1 = $mount1->getStorage();
796
+                    $storage2 = $mount2->getStorage();
797
+                    $internalPath1 = $mount1->getInternalPath($absolutePath1);
798
+                    $internalPath2 = $mount2->getInternalPath($absolutePath2);
799
+
800
+                    $this->changeLock($path1, ILockingProvider::LOCK_EXCLUSIVE, true);
801
+                    try {
802
+                        $this->changeLock($path2, ILockingProvider::LOCK_EXCLUSIVE, true);
803
+
804
+                        if ($internalPath1 === '') {
805
+                            if ($mount1 instanceof MoveableMount) {
806
+                                $sourceParentMount = $this->getMount(dirname($path1));
807
+                                if ($sourceParentMount === $mount2 && $this->targetIsNotShared($storage2, $internalPath2)) {
808
+                                    /**
809
+                                     * @var \OC\Files\Mount\MountPoint | \OC\Files\Mount\MoveableMount $mount1
810
+                                     */
811
+                                    $sourceMountPoint = $mount1->getMountPoint();
812
+                                    $result = $mount1->moveMount($absolutePath2);
813
+                                    $manager->moveMount($sourceMountPoint, $mount1->getMountPoint());
814
+                                } else {
815
+                                    $result = false;
816
+                                }
817
+                            } else {
818
+                                $result = false;
819
+                            }
820
+                            // moving a file/folder within the same mount point
821
+                        } elseif ($storage1 === $storage2) {
822
+                            if ($storage1) {
823
+                                $result = $storage1->rename($internalPath1, $internalPath2);
824
+                            } else {
825
+                                $result = false;
826
+                            }
827
+                            // moving a file/folder between storages (from $storage1 to $storage2)
828
+                        } else {
829
+                            $result = $storage2->moveFromStorage($storage1, $internalPath1, $internalPath2);
830
+                        }
831
+
832
+                        if ((Cache\Scanner::isPartialFile($path1) && !Cache\Scanner::isPartialFile($path2)) && $result !== false) {
833
+                            // if it was a rename from a part file to a regular file it was a write and not a rename operation
834
+                            $this->writeUpdate($storage2, $internalPath2);
835
+                        } elseif ($result) {
836
+                            if ($internalPath1 !== '') { // don't do a cache update for moved mounts
837
+                                $this->renameUpdate($storage1, $storage2, $internalPath1, $internalPath2);
838
+                            }
839
+                        }
840
+                    } catch (\Exception $e) {
841
+                        throw $e;
842
+                    } finally {
843
+                        $this->changeLock($path1, ILockingProvider::LOCK_SHARED, true);
844
+                        $this->changeLock($path2, ILockingProvider::LOCK_SHARED, true);
845
+                    }
846
+
847
+                    if ((Cache\Scanner::isPartialFile($path1) && !Cache\Scanner::isPartialFile($path2)) && $result !== false) {
848
+                        if ($this->shouldEmitHooks()) {
849
+                            $this->emit_file_hooks_post($exists, $path2);
850
+                        }
851
+                    } elseif ($result) {
852
+                        if ($this->shouldEmitHooks($path1) and $this->shouldEmitHooks($path2)) {
853
+                            \OC_Hook::emit(
854
+                                Filesystem::CLASSNAME,
855
+                                Filesystem::signal_post_rename,
856
+                                [
857
+                                    Filesystem::signal_param_oldpath => $this->getHookPath($path1),
858
+                                    Filesystem::signal_param_newpath => $this->getHookPath($path2)
859
+                                ]
860
+                            );
861
+                        }
862
+                    }
863
+                }
864
+            } catch (\Exception $e) {
865
+                throw $e;
866
+            } finally {
867
+                $this->unlockFile($path1, ILockingProvider::LOCK_SHARED, true);
868
+                $this->unlockFile($path2, ILockingProvider::LOCK_SHARED, true);
869
+            }
870
+        }
871
+        return $result;
872
+    }
873
+
874
+    /**
875
+     * Copy a file/folder from the source path to target path
876
+     *
877
+     * @param string $path1 source path
878
+     * @param string $path2 target path
879
+     * @param bool $preserveMtime whether to preserve mtime on the copy
880
+     *
881
+     * @return bool|mixed
882
+     */
883
+    public function copy($path1, $path2, $preserveMtime = false) {
884
+        $absolutePath1 = Filesystem::normalizePath($this->getAbsolutePath($path1));
885
+        $absolutePath2 = Filesystem::normalizePath($this->getAbsolutePath($path2));
886
+        $result = false;
887
+        if (
888
+            Filesystem::isValidPath($path2)
889
+            and Filesystem::isValidPath($path1)
890
+            and !Filesystem::isFileBlacklisted($path2)
891
+        ) {
892
+            $path1 = $this->getRelativePath($absolutePath1);
893
+            $path2 = $this->getRelativePath($absolutePath2);
894
+
895
+            if ($path1 == null or $path2 == null) {
896
+                return false;
897
+            }
898
+            $run = true;
899
+
900
+            $this->lockFile($path2, ILockingProvider::LOCK_SHARED);
901
+            $this->lockFile($path1, ILockingProvider::LOCK_SHARED);
902
+            $lockTypePath1 = ILockingProvider::LOCK_SHARED;
903
+            $lockTypePath2 = ILockingProvider::LOCK_SHARED;
904
+
905
+            try {
906
+                $exists = $this->file_exists($path2);
907
+                if ($this->shouldEmitHooks()) {
908
+                    \OC_Hook::emit(
909
+                        Filesystem::CLASSNAME,
910
+                        Filesystem::signal_copy,
911
+                        [
912
+                            Filesystem::signal_param_oldpath => $this->getHookPath($path1),
913
+                            Filesystem::signal_param_newpath => $this->getHookPath($path2),
914
+                            Filesystem::signal_param_run => &$run
915
+                        ]
916
+                    );
917
+                    $this->emit_file_hooks_pre($exists, $path2, $run);
918
+                }
919
+                if ($run) {
920
+                    $mount1 = $this->getMount($path1);
921
+                    $mount2 = $this->getMount($path2);
922
+                    $storage1 = $mount1->getStorage();
923
+                    $internalPath1 = $mount1->getInternalPath($absolutePath1);
924
+                    $storage2 = $mount2->getStorage();
925
+                    $internalPath2 = $mount2->getInternalPath($absolutePath2);
926
+
927
+                    $this->changeLock($path2, ILockingProvider::LOCK_EXCLUSIVE);
928
+                    $lockTypePath2 = ILockingProvider::LOCK_EXCLUSIVE;
929
+
930
+                    if ($mount1->getMountPoint() == $mount2->getMountPoint()) {
931
+                        if ($storage1) {
932
+                            $result = $storage1->copy($internalPath1, $internalPath2);
933
+                        } else {
934
+                            $result = false;
935
+                        }
936
+                    } else {
937
+                        $result = $storage2->copyFromStorage($storage1, $internalPath1, $internalPath2);
938
+                    }
939
+
940
+                    $this->writeUpdate($storage2, $internalPath2);
941
+
942
+                    $this->changeLock($path2, ILockingProvider::LOCK_SHARED);
943
+                    $lockTypePath2 = ILockingProvider::LOCK_SHARED;
944
+
945
+                    if ($this->shouldEmitHooks() && $result !== false) {
946
+                        \OC_Hook::emit(
947
+                            Filesystem::CLASSNAME,
948
+                            Filesystem::signal_post_copy,
949
+                            [
950
+                                Filesystem::signal_param_oldpath => $this->getHookPath($path1),
951
+                                Filesystem::signal_param_newpath => $this->getHookPath($path2)
952
+                            ]
953
+                        );
954
+                        $this->emit_file_hooks_post($exists, $path2);
955
+                    }
956
+                }
957
+            } catch (\Exception $e) {
958
+                $this->unlockFile($path2, $lockTypePath2);
959
+                $this->unlockFile($path1, $lockTypePath1);
960
+                throw $e;
961
+            }
962
+
963
+            $this->unlockFile($path2, $lockTypePath2);
964
+            $this->unlockFile($path1, $lockTypePath1);
965
+        }
966
+        return $result;
967
+    }
968
+
969
+    /**
970
+     * @param string $path
971
+     * @param string $mode 'r' or 'w'
972
+     * @return resource
973
+     * @throws LockedException
974
+     */
975
+    public function fopen($path, $mode) {
976
+        $mode = str_replace('b', '', $mode); // the binary flag is a windows only feature which we do not support
977
+        $hooks = [];
978
+        switch ($mode) {
979
+            case 'r':
980
+                $hooks[] = 'read';
981
+                break;
982
+            case 'r+':
983
+            case 'w+':
984
+            case 'x+':
985
+            case 'a+':
986
+                $hooks[] = 'read';
987
+                $hooks[] = 'write';
988
+                break;
989
+            case 'w':
990
+            case 'x':
991
+            case 'a':
992
+                $hooks[] = 'write';
993
+                break;
994
+            default:
995
+                \OCP\Util::writeLog('core', 'invalid mode (' . $mode . ') for ' . $path, ILogger::ERROR);
996
+        }
997
+
998
+        if ($mode !== 'r' && $mode !== 'w') {
999
+            \OC::$server->getLogger()->info('Trying to open a file with a mode other than "r" or "w" can cause severe performance issues with some backends');
1000
+        }
1001
+
1002
+        return $this->basicOperation('fopen', $path, $hooks, $mode);
1003
+    }
1004
+
1005
+    /**
1006
+     * @param string $path
1007
+     * @return bool|string
1008
+     * @throws \OCP\Files\InvalidPathException
1009
+     */
1010
+    public function toTmpFile($path) {
1011
+        $this->assertPathLength($path);
1012
+        if (Filesystem::isValidPath($path)) {
1013
+            $source = $this->fopen($path, 'r');
1014
+            if ($source) {
1015
+                $extension = pathinfo($path, PATHINFO_EXTENSION);
1016
+                $tmpFile = \OC::$server->getTempManager()->getTemporaryFile($extension);
1017
+                file_put_contents($tmpFile, $source);
1018
+                return $tmpFile;
1019
+            } else {
1020
+                return false;
1021
+            }
1022
+        } else {
1023
+            return false;
1024
+        }
1025
+    }
1026
+
1027
+    /**
1028
+     * @param string $tmpFile
1029
+     * @param string $path
1030
+     * @return bool|mixed
1031
+     * @throws \OCP\Files\InvalidPathException
1032
+     */
1033
+    public function fromTmpFile($tmpFile, $path) {
1034
+        $this->assertPathLength($path);
1035
+        if (Filesystem::isValidPath($path)) {
1036
+
1037
+            // Get directory that the file is going into
1038
+            $filePath = dirname($path);
1039
+
1040
+            // Create the directories if any
1041
+            if (!$this->file_exists($filePath)) {
1042
+                $result = $this->createParentDirectories($filePath);
1043
+                if ($result === false) {
1044
+                    return false;
1045
+                }
1046
+            }
1047
+
1048
+            $source = fopen($tmpFile, 'r');
1049
+            if ($source) {
1050
+                $result = $this->file_put_contents($path, $source);
1051
+                // $this->file_put_contents() might have already closed
1052
+                // the resource, so we check it, before trying to close it
1053
+                // to avoid messages in the error log.
1054
+                if (is_resource($source)) {
1055
+                    fclose($source);
1056
+                }
1057
+                unlink($tmpFile);
1058
+                return $result;
1059
+            } else {
1060
+                return false;
1061
+            }
1062
+        } else {
1063
+            return false;
1064
+        }
1065
+    }
1066
+
1067
+
1068
+    /**
1069
+     * @param string $path
1070
+     * @return mixed
1071
+     * @throws \OCP\Files\InvalidPathException
1072
+     */
1073
+    public function getMimeType($path) {
1074
+        $this->assertPathLength($path);
1075
+        return $this->basicOperation('getMimeType', $path);
1076
+    }
1077
+
1078
+    /**
1079
+     * @param string $type
1080
+     * @param string $path
1081
+     * @param bool $raw
1082
+     * @return bool|null|string
1083
+     */
1084
+    public function hash($type, $path, $raw = false) {
1085
+        $postFix = (substr($path, -1) === '/') ? '/' : '';
1086
+        $absolutePath = Filesystem::normalizePath($this->getAbsolutePath($path));
1087
+        if (Filesystem::isValidPath($path)) {
1088
+            $path = $this->getRelativePath($absolutePath);
1089
+            if ($path == null) {
1090
+                return false;
1091
+            }
1092
+            if ($this->shouldEmitHooks($path)) {
1093
+                \OC_Hook::emit(
1094
+                    Filesystem::CLASSNAME,
1095
+                    Filesystem::signal_read,
1096
+                    [Filesystem::signal_param_path => $this->getHookPath($path)]
1097
+                );
1098
+            }
1099
+            [$storage, $internalPath] = Filesystem::resolvePath($absolutePath . $postFix);
1100
+            if ($storage) {
1101
+                return $storage->hash($type, $internalPath, $raw);
1102
+            }
1103
+        }
1104
+        return null;
1105
+    }
1106
+
1107
+    /**
1108
+     * @param string $path
1109
+     * @return mixed
1110
+     * @throws \OCP\Files\InvalidPathException
1111
+     */
1112
+    public function free_space($path = '/') {
1113
+        $this->assertPathLength($path);
1114
+        $result = $this->basicOperation('free_space', $path);
1115
+        if ($result === null) {
1116
+            throw new InvalidPathException();
1117
+        }
1118
+        return $result;
1119
+    }
1120
+
1121
+    /**
1122
+     * abstraction layer for basic filesystem functions: wrapper for \OC\Files\Storage\Storage
1123
+     *
1124
+     * @param string $operation
1125
+     * @param string $path
1126
+     * @param array $hooks (optional)
1127
+     * @param mixed $extraParam (optional)
1128
+     * @return mixed
1129
+     * @throws LockedException
1130
+     *
1131
+     * This method takes requests for basic filesystem functions (e.g. reading & writing
1132
+     * files), processes hooks and proxies, sanitises paths, and finally passes them on to
1133
+     * \OC\Files\Storage\Storage for delegation to a storage backend for execution
1134
+     */
1135
+    private function basicOperation($operation, $path, $hooks = [], $extraParam = null) {
1136
+        $postFix = (substr($path, -1) === '/') ? '/' : '';
1137
+        $absolutePath = Filesystem::normalizePath($this->getAbsolutePath($path));
1138
+        if (Filesystem::isValidPath($path)
1139
+            and !Filesystem::isFileBlacklisted($path)
1140
+        ) {
1141
+            $path = $this->getRelativePath($absolutePath);
1142
+            if ($path == null) {
1143
+                return false;
1144
+            }
1145
+
1146
+            if (in_array('write', $hooks) || in_array('delete', $hooks) || in_array('read', $hooks)) {
1147
+                // always a shared lock during pre-hooks so the hook can read the file
1148
+                $this->lockFile($path, ILockingProvider::LOCK_SHARED);
1149
+            }
1150
+
1151
+            $run = $this->runHooks($hooks, $path);
1152
+            /** @var \OC\Files\Storage\Storage $storage */
1153
+            [$storage, $internalPath] = Filesystem::resolvePath($absolutePath . $postFix);
1154
+            if ($run and $storage) {
1155
+                if (in_array('write', $hooks) || in_array('delete', $hooks)) {
1156
+                    try {
1157
+                        $this->changeLock($path, ILockingProvider::LOCK_EXCLUSIVE);
1158
+                    } catch (LockedException $e) {
1159
+                        // release the shared lock we acquired before quiting
1160
+                        $this->unlockFile($path, ILockingProvider::LOCK_SHARED);
1161
+                        throw $e;
1162
+                    }
1163
+                }
1164
+                try {
1165
+                    if (!is_null($extraParam)) {
1166
+                        $result = $storage->$operation($internalPath, $extraParam);
1167
+                    } else {
1168
+                        $result = $storage->$operation($internalPath);
1169
+                    }
1170
+                } catch (\Exception $e) {
1171
+                    if (in_array('write', $hooks) || in_array('delete', $hooks)) {
1172
+                        $this->unlockFile($path, ILockingProvider::LOCK_EXCLUSIVE);
1173
+                    } elseif (in_array('read', $hooks)) {
1174
+                        $this->unlockFile($path, ILockingProvider::LOCK_SHARED);
1175
+                    }
1176
+                    throw $e;
1177
+                }
1178
+
1179
+                if ($result && in_array('delete', $hooks) and $result) {
1180
+                    $this->removeUpdate($storage, $internalPath);
1181
+                }
1182
+                if ($result && in_array('write', $hooks,  true) && $operation !== 'fopen' && $operation !== 'touch') {
1183
+                    $this->writeUpdate($storage, $internalPath);
1184
+                }
1185
+                if ($result && in_array('touch', $hooks)) {
1186
+                    $this->writeUpdate($storage, $internalPath, $extraParam);
1187
+                }
1188
+
1189
+                if ((in_array('write', $hooks) || in_array('delete', $hooks)) && ($operation !== 'fopen' || $result === false)) {
1190
+                    $this->changeLock($path, ILockingProvider::LOCK_SHARED);
1191
+                }
1192
+
1193
+                $unlockLater = false;
1194
+                if ($this->lockingEnabled && $operation === 'fopen' && is_resource($result)) {
1195
+                    $unlockLater = true;
1196
+                    // make sure our unlocking callback will still be called if connection is aborted
1197
+                    ignore_user_abort(true);
1198
+                    $result = CallbackWrapper::wrap($result, null, null, function () use ($hooks, $path) {
1199
+                        if (in_array('write', $hooks)) {
1200
+                            $this->unlockFile($path, ILockingProvider::LOCK_EXCLUSIVE);
1201
+                        } elseif (in_array('read', $hooks)) {
1202
+                            $this->unlockFile($path, ILockingProvider::LOCK_SHARED);
1203
+                        }
1204
+                    });
1205
+                }
1206
+
1207
+                if ($this->shouldEmitHooks($path) && $result !== false) {
1208
+                    if ($operation != 'fopen') { //no post hooks for fopen, the file stream is still open
1209
+                        $this->runHooks($hooks, $path, true);
1210
+                    }
1211
+                }
1212
+
1213
+                if (!$unlockLater
1214
+                    && (in_array('write', $hooks) || in_array('delete', $hooks) || in_array('read', $hooks))
1215
+                ) {
1216
+                    $this->unlockFile($path, ILockingProvider::LOCK_SHARED);
1217
+                }
1218
+                return $result;
1219
+            } else {
1220
+                $this->unlockFile($path, ILockingProvider::LOCK_SHARED);
1221
+            }
1222
+        }
1223
+        return null;
1224
+    }
1225
+
1226
+    /**
1227
+     * get the path relative to the default root for hook usage
1228
+     *
1229
+     * @param string $path
1230
+     * @return string
1231
+     */
1232
+    private function getHookPath($path) {
1233
+        if (!Filesystem::getView()) {
1234
+            return $path;
1235
+        }
1236
+        return Filesystem::getView()->getRelativePath($this->getAbsolutePath($path));
1237
+    }
1238
+
1239
+    private function shouldEmitHooks($path = '') {
1240
+        if ($path && Cache\Scanner::isPartialFile($path)) {
1241
+            return false;
1242
+        }
1243
+        if (!Filesystem::$loaded) {
1244
+            return false;
1245
+        }
1246
+        $defaultRoot = Filesystem::getRoot();
1247
+        if ($defaultRoot === null) {
1248
+            return false;
1249
+        }
1250
+        if ($this->fakeRoot === $defaultRoot) {
1251
+            return true;
1252
+        }
1253
+        $fullPath = $this->getAbsolutePath($path);
1254
+
1255
+        if ($fullPath === $defaultRoot) {
1256
+            return true;
1257
+        }
1258
+
1259
+        return (strlen($fullPath) > strlen($defaultRoot)) && (substr($fullPath, 0, strlen($defaultRoot) + 1) === $defaultRoot . '/');
1260
+    }
1261
+
1262
+    /**
1263
+     * @param string[] $hooks
1264
+     * @param string $path
1265
+     * @param bool $post
1266
+     * @return bool
1267
+     */
1268
+    private function runHooks($hooks, $path, $post = false) {
1269
+        $relativePath = $path;
1270
+        $path = $this->getHookPath($path);
1271
+        $prefix = $post ? 'post_' : '';
1272
+        $run = true;
1273
+        if ($this->shouldEmitHooks($relativePath)) {
1274
+            foreach ($hooks as $hook) {
1275
+                if ($hook != 'read') {
1276
+                    \OC_Hook::emit(
1277
+                        Filesystem::CLASSNAME,
1278
+                        $prefix . $hook,
1279
+                        [
1280
+                            Filesystem::signal_param_run => &$run,
1281
+                            Filesystem::signal_param_path => $path
1282
+                        ]
1283
+                    );
1284
+                } elseif (!$post) {
1285
+                    \OC_Hook::emit(
1286
+                        Filesystem::CLASSNAME,
1287
+                        $prefix . $hook,
1288
+                        [
1289
+                            Filesystem::signal_param_path => $path
1290
+                        ]
1291
+                    );
1292
+                }
1293
+            }
1294
+        }
1295
+        return $run;
1296
+    }
1297
+
1298
+    /**
1299
+     * check if a file or folder has been updated since $time
1300
+     *
1301
+     * @param string $path
1302
+     * @param int $time
1303
+     * @return bool
1304
+     */
1305
+    public function hasUpdated($path, $time) {
1306
+        return $this->basicOperation('hasUpdated', $path, [], $time);
1307
+    }
1308
+
1309
+    /**
1310
+     * @param string $ownerId
1311
+     * @return \OC\User\User
1312
+     */
1313
+    private function getUserObjectForOwner($ownerId) {
1314
+        $owner = $this->userManager->get($ownerId);
1315
+        if ($owner instanceof IUser) {
1316
+            return $owner;
1317
+        } else {
1318
+            return new User($ownerId, null, \OC::$server->getEventDispatcher());
1319
+        }
1320
+    }
1321
+
1322
+    /**
1323
+     * Get file info from cache
1324
+     *
1325
+     * If the file is not in cached it will be scanned
1326
+     * If the file has changed on storage the cache will be updated
1327
+     *
1328
+     * @param \OC\Files\Storage\Storage $storage
1329
+     * @param string $internalPath
1330
+     * @param string $relativePath
1331
+     * @return ICacheEntry|bool
1332
+     */
1333
+    private function getCacheEntry($storage, $internalPath, $relativePath) {
1334
+        $cache = $storage->getCache($internalPath);
1335
+        $data = $cache->get($internalPath);
1336
+        $watcher = $storage->getWatcher($internalPath);
1337
+
1338
+        try {
1339
+            // if the file is not in the cache or needs to be updated, trigger the scanner and reload the data
1340
+            if (!$data || $data['size'] === -1) {
1341
+                if (!$storage->file_exists($internalPath)) {
1342
+                    return false;
1343
+                }
1344
+                // don't need to get a lock here since the scanner does it's own locking
1345
+                $scanner = $storage->getScanner($internalPath);
1346
+                $scanner->scan($internalPath, Cache\Scanner::SCAN_SHALLOW);
1347
+                $data = $cache->get($internalPath);
1348
+            } elseif (!Cache\Scanner::isPartialFile($internalPath) && $watcher->needsUpdate($internalPath, $data)) {
1349
+                $this->lockFile($relativePath, ILockingProvider::LOCK_SHARED);
1350
+                $watcher->update($internalPath, $data);
1351
+                $storage->getPropagator()->propagateChange($internalPath, time());
1352
+                $data = $cache->get($internalPath);
1353
+                $this->unlockFile($relativePath, ILockingProvider::LOCK_SHARED);
1354
+            }
1355
+        } catch (LockedException $e) {
1356
+            // if the file is locked we just use the old cache info
1357
+        }
1358
+
1359
+        return $data;
1360
+    }
1361
+
1362
+    /**
1363
+     * get the filesystem info
1364
+     *
1365
+     * @param string $path
1366
+     * @param boolean|string $includeMountPoints true to add mountpoint sizes,
1367
+     * 'ext' to add only ext storage mount point sizes. Defaults to true.
1368
+     * defaults to true
1369
+     * @return \OC\Files\FileInfo|false False if file does not exist
1370
+     */
1371
+    public function getFileInfo($path, $includeMountPoints = true) {
1372
+        $this->assertPathLength($path);
1373
+        if (!Filesystem::isValidPath($path)) {
1374
+            return false;
1375
+        }
1376
+        if (Cache\Scanner::isPartialFile($path)) {
1377
+            return $this->getPartFileInfo($path);
1378
+        }
1379
+        $relativePath = $path;
1380
+        $path = Filesystem::normalizePath($this->fakeRoot . '/' . $path);
1381
+
1382
+        $mount = Filesystem::getMountManager()->find($path);
1383
+        if (!$mount) {
1384
+            \OC::$server->getLogger()->warning('Mountpoint not found for path: ' . $path);
1385
+            return false;
1386
+        }
1387
+        $storage = $mount->getStorage();
1388
+        $internalPath = $mount->getInternalPath($path);
1389
+        if ($storage) {
1390
+            $data = $this->getCacheEntry($storage, $internalPath, $relativePath);
1391
+
1392
+            if (!$data instanceof ICacheEntry) {
1393
+                return false;
1394
+            }
1395
+
1396
+            if ($mount instanceof MoveableMount && $internalPath === '') {
1397
+                $data['permissions'] |= \OCP\Constants::PERMISSION_DELETE;
1398
+            }
1399
+            $ownerId = $storage->getOwner($internalPath);
1400
+            $owner = null;
1401
+            if ($ownerId !== null && $ownerId !== false) {
1402
+                // ownerId might be null if files are accessed with an access token without file system access
1403
+                $owner = $this->getUserObjectForOwner($ownerId);
1404
+            }
1405
+            $info = new FileInfo($path, $storage, $internalPath, $data, $mount, $owner);
1406
+
1407
+            if ($data and isset($data['fileid'])) {
1408
+                if ($includeMountPoints and $data['mimetype'] === 'httpd/unix-directory') {
1409
+                    //add the sizes of other mount points to the folder
1410
+                    $extOnly = ($includeMountPoints === 'ext');
1411
+                    $mounts = Filesystem::getMountManager()->findIn($path);
1412
+                    $info->setSubMounts(array_filter($mounts, function (IMountPoint $mount) use ($extOnly) {
1413
+                        $subStorage = $mount->getStorage();
1414
+                        return !($extOnly && $subStorage instanceof \OCA\Files_Sharing\SharedStorage);
1415
+                    }));
1416
+                }
1417
+            }
1418
+
1419
+            return $info;
1420
+        } else {
1421
+            \OC::$server->getLogger()->warning('Storage not valid for mountpoint: ' . $mount->getMountPoint());
1422
+        }
1423
+
1424
+        return false;
1425
+    }
1426
+
1427
+    /**
1428
+     * get the content of a directory
1429
+     *
1430
+     * @param string $directory path under datadirectory
1431
+     * @param string $mimetype_filter limit returned content to this mimetype or mimepart
1432
+     * @return FileInfo[]
1433
+     */
1434
+    public function getDirectoryContent($directory, $mimetype_filter = '') {
1435
+        $this->assertPathLength($directory);
1436
+        if (!Filesystem::isValidPath($directory)) {
1437
+            return [];
1438
+        }
1439
+        $path = $this->getAbsolutePath($directory);
1440
+        $path = Filesystem::normalizePath($path);
1441
+        $mount = $this->getMount($directory);
1442
+        if (!$mount) {
1443
+            return [];
1444
+        }
1445
+        $storage = $mount->getStorage();
1446
+        $internalPath = $mount->getInternalPath($path);
1447
+        if ($storage) {
1448
+            $cache = $storage->getCache($internalPath);
1449
+            $user = \OC_User::getUser();
1450
+
1451
+            $data = $this->getCacheEntry($storage, $internalPath, $directory);
1452
+
1453
+            if (!$data instanceof ICacheEntry || !isset($data['fileid']) || !($data->getPermissions() && Constants::PERMISSION_READ)) {
1454
+                return [];
1455
+            }
1456
+
1457
+            $folderId = $data['fileid'];
1458
+            $contents = $cache->getFolderContentsById($folderId); //TODO: mimetype_filter
1459
+
1460
+            $sharingDisabled = \OCP\Util::isSharingDisabledForUser();
1461
+
1462
+            $fileNames = array_map(function (ICacheEntry $content) {
1463
+                return $content->getName();
1464
+            }, $contents);
1465
+            /**
1466
+             * @var \OC\Files\FileInfo[] $fileInfos
1467
+             */
1468
+            $fileInfos = array_map(function (ICacheEntry $content) use ($path, $storage, $mount, $sharingDisabled) {
1469
+                if ($sharingDisabled) {
1470
+                    $content['permissions'] = $content['permissions'] & ~\OCP\Constants::PERMISSION_SHARE;
1471
+                }
1472
+                $owner = $this->getUserObjectForOwner($storage->getOwner($content['path']));
1473
+                return new FileInfo($path . '/' . $content['name'], $storage, $content['path'], $content, $mount, $owner);
1474
+            }, $contents);
1475
+            $files = array_combine($fileNames, $fileInfos);
1476
+
1477
+            //add a folder for any mountpoint in this directory and add the sizes of other mountpoints to the folders
1478
+            $mounts = Filesystem::getMountManager()->findIn($path);
1479
+            $dirLength = strlen($path);
1480
+            foreach ($mounts as $mount) {
1481
+                $mountPoint = $mount->getMountPoint();
1482
+                $subStorage = $mount->getStorage();
1483
+                if ($subStorage) {
1484
+                    $subCache = $subStorage->getCache('');
1485
+
1486
+                    $rootEntry = $subCache->get('');
1487
+                    if (!$rootEntry) {
1488
+                        $subScanner = $subStorage->getScanner('');
1489
+                        try {
1490
+                            $subScanner->scanFile('');
1491
+                        } catch (\OCP\Files\StorageNotAvailableException $e) {
1492
+                            continue;
1493
+                        } catch (\OCP\Files\StorageInvalidException $e) {
1494
+                            continue;
1495
+                        } catch (\Exception $e) {
1496
+                            // sometimes when the storage is not available it can be any exception
1497
+                            \OC::$server->getLogger()->logException($e, [
1498
+                                'message' => 'Exception while scanning storage "' . $subStorage->getId() . '"',
1499
+                                'level' => ILogger::ERROR,
1500
+                                'app' => 'lib',
1501
+                            ]);
1502
+                            continue;
1503
+                        }
1504
+                        $rootEntry = $subCache->get('');
1505
+                    }
1506
+
1507
+                    if ($rootEntry && ($rootEntry->getPermissions() && Constants::PERMISSION_READ)) {
1508
+                        $relativePath = trim(substr($mountPoint, $dirLength), '/');
1509
+                        if ($pos = strpos($relativePath, '/')) {
1510
+                            //mountpoint inside subfolder add size to the correct folder
1511
+                            $entryName = substr($relativePath, 0, $pos);
1512
+                            foreach ($files as &$entry) {
1513
+                                if ($entry->getName() === $entryName) {
1514
+                                    $entry->addSubEntry($rootEntry, $mountPoint);
1515
+                                }
1516
+                            }
1517
+                        } else { //mountpoint in this folder, add an entry for it
1518
+                            $rootEntry['name'] = $relativePath;
1519
+                            $rootEntry['type'] = $rootEntry['mimetype'] === 'httpd/unix-directory' ? 'dir' : 'file';
1520
+                            $permissions = $rootEntry['permissions'];
1521
+                            // do not allow renaming/deleting the mount point if they are not shared files/folders
1522
+                            // for shared files/folders we use the permissions given by the owner
1523
+                            if ($mount instanceof MoveableMount) {
1524
+                                $rootEntry['permissions'] = $permissions | \OCP\Constants::PERMISSION_UPDATE | \OCP\Constants::PERMISSION_DELETE;
1525
+                            } else {
1526
+                                $rootEntry['permissions'] = $permissions & (\OCP\Constants::PERMISSION_ALL - (\OCP\Constants::PERMISSION_UPDATE | \OCP\Constants::PERMISSION_DELETE));
1527
+                            }
1528
+
1529
+                            $rootEntry['path'] = substr(Filesystem::normalizePath($path . '/' . $rootEntry['name']), strlen($user) + 2); // full path without /$user/
1530
+
1531
+                            // if sharing was disabled for the user we remove the share permissions
1532
+                            if (\OCP\Util::isSharingDisabledForUser()) {
1533
+                                $rootEntry['permissions'] = $rootEntry['permissions'] & ~\OCP\Constants::PERMISSION_SHARE;
1534
+                            }
1535
+
1536
+                            $owner = $this->getUserObjectForOwner($subStorage->getOwner(''));
1537
+                            $files[$rootEntry->getName()] = new FileInfo($path . '/' . $rootEntry['name'], $subStorage, '', $rootEntry, $mount, $owner);
1538
+                        }
1539
+                    }
1540
+                }
1541
+            }
1542
+
1543
+            if ($mimetype_filter) {
1544
+                $files = array_filter($files, function (FileInfo $file) use ($mimetype_filter) {
1545
+                    if (strpos($mimetype_filter, '/')) {
1546
+                        return $file->getMimetype() === $mimetype_filter;
1547
+                    } else {
1548
+                        return $file->getMimePart() === $mimetype_filter;
1549
+                    }
1550
+                });
1551
+            }
1552
+
1553
+            return array_values($files);
1554
+        } else {
1555
+            return [];
1556
+        }
1557
+    }
1558
+
1559
+    /**
1560
+     * change file metadata
1561
+     *
1562
+     * @param string $path
1563
+     * @param array|\OCP\Files\FileInfo $data
1564
+     * @return int
1565
+     *
1566
+     * returns the fileid of the updated file
1567
+     */
1568
+    public function putFileInfo($path, $data) {
1569
+        $this->assertPathLength($path);
1570
+        if ($data instanceof FileInfo) {
1571
+            $data = $data->getData();
1572
+        }
1573
+        $path = Filesystem::normalizePath($this->fakeRoot . '/' . $path);
1574
+        /**
1575
+         * @var \OC\Files\Storage\Storage $storage
1576
+         * @var string $internalPath
1577
+         */
1578
+        [$storage, $internalPath] = Filesystem::resolvePath($path);
1579
+        if ($storage) {
1580
+            $cache = $storage->getCache($path);
1581
+
1582
+            if (!$cache->inCache($internalPath)) {
1583
+                $scanner = $storage->getScanner($internalPath);
1584
+                $scanner->scan($internalPath, Cache\Scanner::SCAN_SHALLOW);
1585
+            }
1586
+
1587
+            return $cache->put($internalPath, $data);
1588
+        } else {
1589
+            return -1;
1590
+        }
1591
+    }
1592
+
1593
+    /**
1594
+     * search for files with the name matching $query
1595
+     *
1596
+     * @param string $query
1597
+     * @return FileInfo[]
1598
+     */
1599
+    public function search($query) {
1600
+        return $this->searchCommon('search', ['%' . $query . '%']);
1601
+    }
1602
+
1603
+    /**
1604
+     * search for files with the name matching $query
1605
+     *
1606
+     * @param string $query
1607
+     * @return FileInfo[]
1608
+     */
1609
+    public function searchRaw($query) {
1610
+        return $this->searchCommon('search', [$query]);
1611
+    }
1612
+
1613
+    /**
1614
+     * search for files by mimetype
1615
+     *
1616
+     * @param string $mimetype
1617
+     * @return FileInfo[]
1618
+     */
1619
+    public function searchByMime($mimetype) {
1620
+        return $this->searchCommon('searchByMime', [$mimetype]);
1621
+    }
1622
+
1623
+    /**
1624
+     * search for files by tag
1625
+     *
1626
+     * @param string|int $tag name or tag id
1627
+     * @param string $userId owner of the tags
1628
+     * @return FileInfo[]
1629
+     */
1630
+    public function searchByTag($tag, $userId) {
1631
+        return $this->searchCommon('searchByTag', [$tag, $userId]);
1632
+    }
1633
+
1634
+    /**
1635
+     * @param string $method cache method
1636
+     * @param array $args
1637
+     * @return FileInfo[]
1638
+     */
1639
+    private function searchCommon($method, $args) {
1640
+        $files = [];
1641
+        $rootLength = strlen($this->fakeRoot);
1642
+
1643
+        $mount = $this->getMount('');
1644
+        $mountPoint = $mount->getMountPoint();
1645
+        $storage = $mount->getStorage();
1646
+        $userManager = \OC::$server->getUserManager();
1647
+        if ($storage) {
1648
+            $cache = $storage->getCache('');
1649
+
1650
+            $results = call_user_func_array([$cache, $method], $args);
1651
+            foreach ($results as $result) {
1652
+                if (substr($mountPoint . $result['path'], 0, $rootLength + 1) === $this->fakeRoot . '/') {
1653
+                    $internalPath = $result['path'];
1654
+                    $path = $mountPoint . $result['path'];
1655
+                    $result['path'] = substr($mountPoint . $result['path'], $rootLength);
1656
+                    $owner = $userManager->get($storage->getOwner($internalPath));
1657
+                    $files[] = new FileInfo($path, $storage, $internalPath, $result, $mount, $owner);
1658
+                }
1659
+            }
1660
+
1661
+            $mounts = Filesystem::getMountManager()->findIn($this->fakeRoot);
1662
+            foreach ($mounts as $mount) {
1663
+                $mountPoint = $mount->getMountPoint();
1664
+                $storage = $mount->getStorage();
1665
+                if ($storage) {
1666
+                    $cache = $storage->getCache('');
1667
+
1668
+                    $relativeMountPoint = substr($mountPoint, $rootLength);
1669
+                    $results = call_user_func_array([$cache, $method], $args);
1670
+                    if ($results) {
1671
+                        foreach ($results as $result) {
1672
+                            $internalPath = $result['path'];
1673
+                            $result['path'] = rtrim($relativeMountPoint . $result['path'], '/');
1674
+                            $path = rtrim($mountPoint . $internalPath, '/');
1675
+                            $owner = $userManager->get($storage->getOwner($internalPath));
1676
+                            $files[] = new FileInfo($path, $storage, $internalPath, $result, $mount, $owner);
1677
+                        }
1678
+                    }
1679
+                }
1680
+            }
1681
+        }
1682
+        return $files;
1683
+    }
1684
+
1685
+    /**
1686
+     * Get the owner for a file or folder
1687
+     *
1688
+     * @param string $path
1689
+     * @return string the user id of the owner
1690
+     * @throws NotFoundException
1691
+     */
1692
+    public function getOwner($path) {
1693
+        $info = $this->getFileInfo($path);
1694
+        if (!$info) {
1695
+            throw new NotFoundException($path . ' not found while trying to get owner');
1696
+        }
1697
+
1698
+        if ($info->getOwner() === null) {
1699
+            throw new NotFoundException($path . ' has no owner');
1700
+        }
1701
+
1702
+        return $info->getOwner()->getUID();
1703
+    }
1704
+
1705
+    /**
1706
+     * get the ETag for a file or folder
1707
+     *
1708
+     * @param string $path
1709
+     * @return string
1710
+     */
1711
+    public function getETag($path) {
1712
+        /**
1713
+         * @var Storage\Storage $storage
1714
+         * @var string $internalPath
1715
+         */
1716
+        [$storage, $internalPath] = $this->resolvePath($path);
1717
+        if ($storage) {
1718
+            return $storage->getETag($internalPath);
1719
+        } else {
1720
+            return null;
1721
+        }
1722
+    }
1723
+
1724
+    /**
1725
+     * Get the path of a file by id, relative to the view
1726
+     *
1727
+     * Note that the resulting path is not guarantied to be unique for the id, multiple paths can point to the same file
1728
+     *
1729
+     * @param int $id
1730
+     * @param int|null $storageId
1731
+     * @return string
1732
+     * @throws NotFoundException
1733
+     */
1734
+    public function getPath($id, int $storageId = null) {
1735
+        $id = (int)$id;
1736
+        $manager = Filesystem::getMountManager();
1737
+        $mounts = $manager->findIn($this->fakeRoot);
1738
+        $mounts[] = $manager->find($this->fakeRoot);
1739
+        // reverse the array so we start with the storage this view is in
1740
+        // which is the most likely to contain the file we're looking for
1741
+        $mounts = array_reverse($mounts);
1742
+
1743
+        // put non shared mounts in front of the shared mount
1744
+        // this prevent unneeded recursion into shares
1745
+        usort($mounts, function (IMountPoint $a, IMountPoint $b) {
1746
+            return $a instanceof SharedMount && (!$b instanceof SharedMount) ? 1 : -1;
1747
+        });
1748
+
1749
+        if (!is_null($storageId)) {
1750
+            $mounts = array_filter($mounts, function (IMountPoint $mount) use ($storageId) {
1751
+                return $mount->getNumericStorageId() === $storageId;
1752
+            });
1753
+        }
1754
+
1755
+        foreach ($mounts as $mount) {
1756
+            /**
1757
+             * @var \OC\Files\Mount\MountPoint $mount
1758
+             */
1759
+            if ($mount->getStorage()) {
1760
+                $cache = $mount->getStorage()->getCache();
1761
+                $internalPath = $cache->getPathById($id);
1762
+                if (is_string($internalPath)) {
1763
+                    $fullPath = $mount->getMountPoint() . $internalPath;
1764
+                    if (!is_null($path = $this->getRelativePath($fullPath))) {
1765
+                        return $path;
1766
+                    }
1767
+                }
1768
+            }
1769
+        }
1770
+        throw new NotFoundException(sprintf('File with id "%s" has not been found.', $id));
1771
+    }
1772
+
1773
+    /**
1774
+     * @param string $path
1775
+     * @throws InvalidPathException
1776
+     */
1777
+    private function assertPathLength($path) {
1778
+        $maxLen = min(PHP_MAXPATHLEN, 4000);
1779
+        // Check for the string length - performed using isset() instead of strlen()
1780
+        // because isset() is about 5x-40x faster.
1781
+        if (isset($path[$maxLen])) {
1782
+            $pathLen = strlen($path);
1783
+            throw new \OCP\Files\InvalidPathException("Path length($pathLen) exceeds max path length($maxLen): $path");
1784
+        }
1785
+    }
1786
+
1787
+    /**
1788
+     * check if it is allowed to move a mount point to a given target.
1789
+     * It is not allowed to move a mount point into a different mount point or
1790
+     * into an already shared folder
1791
+     *
1792
+     * @param IStorage $targetStorage
1793
+     * @param string $targetInternalPath
1794
+     * @return boolean
1795
+     */
1796
+    private function targetIsNotShared(IStorage $targetStorage, string $targetInternalPath) {
1797
+
1798
+        // note: cannot use the view because the target is already locked
1799
+        $fileId = (int)$targetStorage->getCache()->getId($targetInternalPath);
1800
+        if ($fileId === -1) {
1801
+            // target might not exist, need to check parent instead
1802
+            $fileId = (int)$targetStorage->getCache()->getId(dirname($targetInternalPath));
1803
+        }
1804
+
1805
+        // check if any of the parents were shared by the current owner (include collections)
1806
+        $shares = \OCP\Share::getItemShared(
1807
+            'folder',
1808
+            $fileId,
1809
+            \OCP\Share::FORMAT_NONE,
1810
+            null,
1811
+            true
1812
+        );
1813
+
1814
+        if (count($shares) > 0) {
1815
+            \OCP\Util::writeLog('files',
1816
+                'It is not allowed to move one mount point into a shared folder',
1817
+                ILogger::DEBUG);
1818
+            return false;
1819
+        }
1820
+
1821
+        return true;
1822
+    }
1823
+
1824
+    /**
1825
+     * Get a fileinfo object for files that are ignored in the cache (part files)
1826
+     *
1827
+     * @param string $path
1828
+     * @return \OCP\Files\FileInfo
1829
+     */
1830
+    private function getPartFileInfo($path) {
1831
+        $mount = $this->getMount($path);
1832
+        $storage = $mount->getStorage();
1833
+        $internalPath = $mount->getInternalPath($this->getAbsolutePath($path));
1834
+        $owner = \OC::$server->getUserManager()->get($storage->getOwner($internalPath));
1835
+        return new FileInfo(
1836
+            $this->getAbsolutePath($path),
1837
+            $storage,
1838
+            $internalPath,
1839
+            [
1840
+                'fileid' => null,
1841
+                'mimetype' => $storage->getMimeType($internalPath),
1842
+                'name' => basename($path),
1843
+                'etag' => null,
1844
+                'size' => $storage->filesize($internalPath),
1845
+                'mtime' => $storage->filemtime($internalPath),
1846
+                'encrypted' => false,
1847
+                'permissions' => \OCP\Constants::PERMISSION_ALL
1848
+            ],
1849
+            $mount,
1850
+            $owner
1851
+        );
1852
+    }
1853
+
1854
+    /**
1855
+     * @param string $path
1856
+     * @param string $fileName
1857
+     * @throws InvalidPathException
1858
+     */
1859
+    public function verifyPath($path, $fileName) {
1860
+        try {
1861
+            /** @type \OCP\Files\Storage $storage */
1862
+            [$storage, $internalPath] = $this->resolvePath($path);
1863
+            $storage->verifyPath($internalPath, $fileName);
1864
+        } catch (ReservedWordException $ex) {
1865
+            $l = \OC::$server->getL10N('lib');
1866
+            throw new InvalidPathException($l->t('File name is a reserved word'));
1867
+        } catch (InvalidCharacterInPathException $ex) {
1868
+            $l = \OC::$server->getL10N('lib');
1869
+            throw new InvalidPathException($l->t('File name contains at least one invalid character'));
1870
+        } catch (FileNameTooLongException $ex) {
1871
+            $l = \OC::$server->getL10N('lib');
1872
+            throw new InvalidPathException($l->t('File name is too long'));
1873
+        } catch (InvalidDirectoryException $ex) {
1874
+            $l = \OC::$server->getL10N('lib');
1875
+            throw new InvalidPathException($l->t('Dot files are not allowed'));
1876
+        } catch (EmptyFileNameException $ex) {
1877
+            $l = \OC::$server->getL10N('lib');
1878
+            throw new InvalidPathException($l->t('Empty filename is not allowed'));
1879
+        }
1880
+    }
1881
+
1882
+    /**
1883
+     * get all parent folders of $path
1884
+     *
1885
+     * @param string $path
1886
+     * @return string[]
1887
+     */
1888
+    private function getParents($path) {
1889
+        $path = trim($path, '/');
1890
+        if (!$path) {
1891
+            return [];
1892
+        }
1893
+
1894
+        $parts = explode('/', $path);
1895
+
1896
+        // remove the single file
1897
+        array_pop($parts);
1898
+        $result = ['/'];
1899
+        $resultPath = '';
1900
+        foreach ($parts as $part) {
1901
+            if ($part) {
1902
+                $resultPath .= '/' . $part;
1903
+                $result[] = $resultPath;
1904
+            }
1905
+        }
1906
+        return $result;
1907
+    }
1908
+
1909
+    /**
1910
+     * Returns the mount point for which to lock
1911
+     *
1912
+     * @param string $absolutePath absolute path
1913
+     * @param bool $useParentMount true to return parent mount instead of whatever
1914
+     * is mounted directly on the given path, false otherwise
1915
+     * @return \OC\Files\Mount\MountPoint mount point for which to apply locks
1916
+     */
1917
+    private function getMountForLock($absolutePath, $useParentMount = false) {
1918
+        $results = [];
1919
+        $mount = Filesystem::getMountManager()->find($absolutePath);
1920
+        if (!$mount) {
1921
+            return $results;
1922
+        }
1923
+
1924
+        if ($useParentMount) {
1925
+            // find out if something is mounted directly on the path
1926
+            $internalPath = $mount->getInternalPath($absolutePath);
1927
+            if ($internalPath === '') {
1928
+                // resolve the parent mount instead
1929
+                $mount = Filesystem::getMountManager()->find(dirname($absolutePath));
1930
+            }
1931
+        }
1932
+
1933
+        return $mount;
1934
+    }
1935
+
1936
+    /**
1937
+     * Lock the given path
1938
+     *
1939
+     * @param string $path the path of the file to lock, relative to the view
1940
+     * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
1941
+     * @param bool $lockMountPoint true to lock the mount point, false to lock the attached mount/storage
1942
+     *
1943
+     * @return bool False if the path is excluded from locking, true otherwise
1944
+     * @throws LockedException if the path is already locked
1945
+     */
1946
+    private function lockPath($path, $type, $lockMountPoint = false) {
1947
+        $absolutePath = $this->getAbsolutePath($path);
1948
+        $absolutePath = Filesystem::normalizePath($absolutePath);
1949
+        if (!$this->shouldLockFile($absolutePath)) {
1950
+            return false;
1951
+        }
1952
+
1953
+        $mount = $this->getMountForLock($absolutePath, $lockMountPoint);
1954
+        if ($mount) {
1955
+            try {
1956
+                $storage = $mount->getStorage();
1957
+                if ($storage && $storage->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) {
1958
+                    $storage->acquireLock(
1959
+                        $mount->getInternalPath($absolutePath),
1960
+                        $type,
1961
+                        $this->lockingProvider
1962
+                    );
1963
+                }
1964
+            } catch (LockedException $e) {
1965
+                // rethrow with the a human-readable path
1966
+                throw new LockedException(
1967
+                    $this->getPathRelativeToFiles($absolutePath),
1968
+                    $e,
1969
+                    $e->getExistingLock()
1970
+                );
1971
+            }
1972
+        }
1973
+
1974
+        return true;
1975
+    }
1976
+
1977
+    /**
1978
+     * Change the lock type
1979
+     *
1980
+     * @param string $path the path of the file to lock, relative to the view
1981
+     * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
1982
+     * @param bool $lockMountPoint true to lock the mount point, false to lock the attached mount/storage
1983
+     *
1984
+     * @return bool False if the path is excluded from locking, true otherwise
1985
+     * @throws LockedException if the path is already locked
1986
+     */
1987
+    public function changeLock($path, $type, $lockMountPoint = false) {
1988
+        $path = Filesystem::normalizePath($path);
1989
+        $absolutePath = $this->getAbsolutePath($path);
1990
+        $absolutePath = Filesystem::normalizePath($absolutePath);
1991
+        if (!$this->shouldLockFile($absolutePath)) {
1992
+            return false;
1993
+        }
1994
+
1995
+        $mount = $this->getMountForLock($absolutePath, $lockMountPoint);
1996
+        if ($mount) {
1997
+            try {
1998
+                $storage = $mount->getStorage();
1999
+                if ($storage && $storage->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) {
2000
+                    $storage->changeLock(
2001
+                        $mount->getInternalPath($absolutePath),
2002
+                        $type,
2003
+                        $this->lockingProvider
2004
+                    );
2005
+                }
2006
+            } catch (LockedException $e) {
2007
+                try {
2008
+                    // rethrow with the a human-readable path
2009
+                    throw new LockedException(
2010
+                        $this->getPathRelativeToFiles($absolutePath),
2011
+                        $e,
2012
+                        $e->getExistingLock()
2013
+                    );
2014
+                } catch (\InvalidArgumentException $ex) {
2015
+                    throw new LockedException(
2016
+                        $absolutePath,
2017
+                        $ex,
2018
+                        $e->getExistingLock()
2019
+                    );
2020
+                }
2021
+            }
2022
+        }
2023
+
2024
+        return true;
2025
+    }
2026
+
2027
+    /**
2028
+     * Unlock the given path
2029
+     *
2030
+     * @param string $path the path of the file to unlock, relative to the view
2031
+     * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
2032
+     * @param bool $lockMountPoint true to lock the mount point, false to lock the attached mount/storage
2033
+     *
2034
+     * @return bool False if the path is excluded from locking, true otherwise
2035
+     * @throws LockedException
2036
+     */
2037
+    private function unlockPath($path, $type, $lockMountPoint = false) {
2038
+        $absolutePath = $this->getAbsolutePath($path);
2039
+        $absolutePath = Filesystem::normalizePath($absolutePath);
2040
+        if (!$this->shouldLockFile($absolutePath)) {
2041
+            return false;
2042
+        }
2043
+
2044
+        $mount = $this->getMountForLock($absolutePath, $lockMountPoint);
2045
+        if ($mount) {
2046
+            $storage = $mount->getStorage();
2047
+            if ($storage && $storage->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) {
2048
+                $storage->releaseLock(
2049
+                    $mount->getInternalPath($absolutePath),
2050
+                    $type,
2051
+                    $this->lockingProvider
2052
+                );
2053
+            }
2054
+        }
2055
+
2056
+        return true;
2057
+    }
2058
+
2059
+    /**
2060
+     * Lock a path and all its parents up to the root of the view
2061
+     *
2062
+     * @param string $path the path of the file to lock relative to the view
2063
+     * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
2064
+     * @param bool $lockMountPoint true to lock the mount point, false to lock the attached mount/storage
2065
+     *
2066
+     * @return bool False if the path is excluded from locking, true otherwise
2067
+     * @throws LockedException
2068
+     */
2069
+    public function lockFile($path, $type, $lockMountPoint = false) {
2070
+        $absolutePath = $this->getAbsolutePath($path);
2071
+        $absolutePath = Filesystem::normalizePath($absolutePath);
2072
+        if (!$this->shouldLockFile($absolutePath)) {
2073
+            return false;
2074
+        }
2075
+
2076
+        $this->lockPath($path, $type, $lockMountPoint);
2077
+
2078
+        $parents = $this->getParents($path);
2079
+        foreach ($parents as $parent) {
2080
+            $this->lockPath($parent, ILockingProvider::LOCK_SHARED);
2081
+        }
2082
+
2083
+        return true;
2084
+    }
2085
+
2086
+    /**
2087
+     * Unlock a path and all its parents up to the root of the view
2088
+     *
2089
+     * @param string $path the path of the file to lock relative to the view
2090
+     * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
2091
+     * @param bool $lockMountPoint true to lock the mount point, false to lock the attached mount/storage
2092
+     *
2093
+     * @return bool False if the path is excluded from locking, true otherwise
2094
+     * @throws LockedException
2095
+     */
2096
+    public function unlockFile($path, $type, $lockMountPoint = false) {
2097
+        $absolutePath = $this->getAbsolutePath($path);
2098
+        $absolutePath = Filesystem::normalizePath($absolutePath);
2099
+        if (!$this->shouldLockFile($absolutePath)) {
2100
+            return false;
2101
+        }
2102
+
2103
+        $this->unlockPath($path, $type, $lockMountPoint);
2104
+
2105
+        $parents = $this->getParents($path);
2106
+        foreach ($parents as $parent) {
2107
+            $this->unlockPath($parent, ILockingProvider::LOCK_SHARED);
2108
+        }
2109
+
2110
+        return true;
2111
+    }
2112
+
2113
+    /**
2114
+     * Only lock files in data/user/files/
2115
+     *
2116
+     * @param string $path Absolute path to the file/folder we try to (un)lock
2117
+     * @return bool
2118
+     */
2119
+    protected function shouldLockFile($path) {
2120
+        $path = Filesystem::normalizePath($path);
2121
+
2122
+        $pathSegments = explode('/', $path);
2123
+        if (isset($pathSegments[2])) {
2124
+            // E.g.: /username/files/path-to-file
2125
+            return ($pathSegments[2] === 'files') && (count($pathSegments) > 3);
2126
+        }
2127
+
2128
+        return strpos($path, '/appdata_') !== 0;
2129
+    }
2130
+
2131
+    /**
2132
+     * Shortens the given absolute path to be relative to
2133
+     * "$user/files".
2134
+     *
2135
+     * @param string $absolutePath absolute path which is under "files"
2136
+     *
2137
+     * @return string path relative to "files" with trimmed slashes or null
2138
+     * if the path was NOT relative to files
2139
+     *
2140
+     * @throws \InvalidArgumentException if the given path was not under "files"
2141
+     * @since 8.1.0
2142
+     */
2143
+    public function getPathRelativeToFiles($absolutePath) {
2144
+        $path = Filesystem::normalizePath($absolutePath);
2145
+        $parts = explode('/', trim($path, '/'), 3);
2146
+        // "$user", "files", "path/to/dir"
2147
+        if (!isset($parts[1]) || $parts[1] !== 'files') {
2148
+            $this->logger->error(
2149
+                '$absolutePath must be relative to "files", value is "%s"',
2150
+                [
2151
+                    $absolutePath
2152
+                ]
2153
+            );
2154
+            throw new \InvalidArgumentException('$absolutePath must be relative to "files"');
2155
+        }
2156
+        if (isset($parts[2])) {
2157
+            return $parts[2];
2158
+        }
2159
+        return '';
2160
+    }
2161
+
2162
+    /**
2163
+     * @param string $filename
2164
+     * @return array
2165
+     * @throws \OC\User\NoUserException
2166
+     * @throws NotFoundException
2167
+     */
2168
+    public function getUidAndFilename($filename) {
2169
+        $info = $this->getFileInfo($filename);
2170
+        if (!$info instanceof \OCP\Files\FileInfo) {
2171
+            throw new NotFoundException($this->getAbsolutePath($filename) . ' not found');
2172
+        }
2173
+        $uid = $info->getOwner()->getUID();
2174
+        if ($uid != \OC_User::getUser()) {
2175
+            Filesystem::initMountPoints($uid);
2176
+            $ownerView = new View('/' . $uid . '/files');
2177
+            try {
2178
+                $filename = $ownerView->getPath($info['fileid']);
2179
+            } catch (NotFoundException $e) {
2180
+                throw new NotFoundException('File with id ' . $info['fileid'] . ' not found for user ' . $uid);
2181
+            }
2182
+        }
2183
+        return [$uid, $filename];
2184
+    }
2185
+
2186
+    /**
2187
+     * Creates parent non-existing folders
2188
+     *
2189
+     * @param string $filePath
2190
+     * @return bool
2191
+     */
2192
+    private function createParentDirectories($filePath) {
2193
+        $directoryParts = explode('/', $filePath);
2194
+        $directoryParts = array_filter($directoryParts);
2195
+        foreach ($directoryParts as $key => $part) {
2196
+            $currentPathElements = array_slice($directoryParts, 0, $key);
2197
+            $currentPath = '/' . implode('/', $currentPathElements);
2198
+            if ($this->is_file($currentPath)) {
2199
+                return false;
2200
+            }
2201
+            if (!$this->file_exists($currentPath)) {
2202
+                $this->mkdir($currentPath);
2203
+            }
2204
+        }
2205
+
2206
+        return true;
2207
+    }
2208 2208
 }
Please login to merge, or discard this patch.
Spacing   +45 added lines, -45 removed lines patch added patch discarded remove patch
@@ -131,9 +131,9 @@  discard block
 block discarded – undo
131 131
 			$path = '/';
132 132
 		}
133 133
 		if ($path[0] !== '/') {
134
-			$path = '/' . $path;
134
+			$path = '/'.$path;
135 135
 		}
136
-		return $this->fakeRoot . $path;
136
+		return $this->fakeRoot.$path;
137 137
 	}
138 138
 
139 139
 	/**
@@ -145,7 +145,7 @@  discard block
 block discarded – undo
145 145
 	public function chroot($fakeRoot) {
146 146
 		if (!$fakeRoot == '') {
147 147
 			if ($fakeRoot[0] !== '/') {
148
-				$fakeRoot = '/' . $fakeRoot;
148
+				$fakeRoot = '/'.$fakeRoot;
149 149
 			}
150 150
 		}
151 151
 		$this->fakeRoot = $fakeRoot;
@@ -177,7 +177,7 @@  discard block
 block discarded – undo
177 177
 		}
178 178
 
179 179
 		// missing slashes can cause wrong matches!
180
-		$root = rtrim($this->fakeRoot, '/') . '/';
180
+		$root = rtrim($this->fakeRoot, '/').'/';
181 181
 
182 182
 		if (strpos($path, $root) !== 0) {
183 183
 			return null;
@@ -283,7 +283,7 @@  discard block
 block discarded – undo
283 283
 		if ($mount instanceof MoveableMount) {
284 284
 			// cut of /user/files to get the relative path to data/user/files
285 285
 			$pathParts = explode('/', $path, 4);
286
-			$relPath = '/' . $pathParts[3];
286
+			$relPath = '/'.$pathParts[3];
287 287
 			$this->lockFile($relPath, ILockingProvider::LOCK_SHARED, true);
288 288
 			\OC_Hook::emit(
289 289
 				Filesystem::CLASSNAME, "umount",
@@ -715,7 +715,7 @@  discard block
 block discarded – undo
715 715
 		}
716 716
 		$postFix = (substr($path, -1) === '/') ? '/' : '';
717 717
 		$absolutePath = Filesystem::normalizePath($this->getAbsolutePath($path));
718
-		$mount = Filesystem::getMountManager()->find($absolutePath . $postFix);
718
+		$mount = Filesystem::getMountManager()->find($absolutePath.$postFix);
719 719
 		if ($mount and $mount->getInternalPath($absolutePath) === '') {
720 720
 			return $this->removeMount($mount, $absolutePath);
721 721
 		}
@@ -992,7 +992,7 @@  discard block
 block discarded – undo
992 992
 				$hooks[] = 'write';
993 993
 				break;
994 994
 			default:
995
-				\OCP\Util::writeLog('core', 'invalid mode (' . $mode . ') for ' . $path, ILogger::ERROR);
995
+				\OCP\Util::writeLog('core', 'invalid mode ('.$mode.') for '.$path, ILogger::ERROR);
996 996
 		}
997 997
 
998 998
 		if ($mode !== 'r' && $mode !== 'w') {
@@ -1096,7 +1096,7 @@  discard block
 block discarded – undo
1096 1096
 					[Filesystem::signal_param_path => $this->getHookPath($path)]
1097 1097
 				);
1098 1098
 			}
1099
-			[$storage, $internalPath] = Filesystem::resolvePath($absolutePath . $postFix);
1099
+			[$storage, $internalPath] = Filesystem::resolvePath($absolutePath.$postFix);
1100 1100
 			if ($storage) {
1101 1101
 				return $storage->hash($type, $internalPath, $raw);
1102 1102
 			}
@@ -1150,7 +1150,7 @@  discard block
 block discarded – undo
1150 1150
 
1151 1151
 			$run = $this->runHooks($hooks, $path);
1152 1152
 			/** @var \OC\Files\Storage\Storage $storage */
1153
-			[$storage, $internalPath] = Filesystem::resolvePath($absolutePath . $postFix);
1153
+			[$storage, $internalPath] = Filesystem::resolvePath($absolutePath.$postFix);
1154 1154
 			if ($run and $storage) {
1155 1155
 				if (in_array('write', $hooks) || in_array('delete', $hooks)) {
1156 1156
 					try {
@@ -1179,7 +1179,7 @@  discard block
 block discarded – undo
1179 1179
 				if ($result && in_array('delete', $hooks) and $result) {
1180 1180
 					$this->removeUpdate($storage, $internalPath);
1181 1181
 				}
1182
-				if ($result && in_array('write', $hooks,  true) && $operation !== 'fopen' && $operation !== 'touch') {
1182
+				if ($result && in_array('write', $hooks, true) && $operation !== 'fopen' && $operation !== 'touch') {
1183 1183
 					$this->writeUpdate($storage, $internalPath);
1184 1184
 				}
1185 1185
 				if ($result && in_array('touch', $hooks)) {
@@ -1195,7 +1195,7 @@  discard block
 block discarded – undo
1195 1195
 					$unlockLater = true;
1196 1196
 					// make sure our unlocking callback will still be called if connection is aborted
1197 1197
 					ignore_user_abort(true);
1198
-					$result = CallbackWrapper::wrap($result, null, null, function () use ($hooks, $path) {
1198
+					$result = CallbackWrapper::wrap($result, null, null, function() use ($hooks, $path) {
1199 1199
 						if (in_array('write', $hooks)) {
1200 1200
 							$this->unlockFile($path, ILockingProvider::LOCK_EXCLUSIVE);
1201 1201
 						} elseif (in_array('read', $hooks)) {
@@ -1256,7 +1256,7 @@  discard block
 block discarded – undo
1256 1256
 			return true;
1257 1257
 		}
1258 1258
 
1259
-		return (strlen($fullPath) > strlen($defaultRoot)) && (substr($fullPath, 0, strlen($defaultRoot) + 1) === $defaultRoot . '/');
1259
+		return (strlen($fullPath) > strlen($defaultRoot)) && (substr($fullPath, 0, strlen($defaultRoot) + 1) === $defaultRoot.'/');
1260 1260
 	}
1261 1261
 
1262 1262
 	/**
@@ -1275,7 +1275,7 @@  discard block
 block discarded – undo
1275 1275
 				if ($hook != 'read') {
1276 1276
 					\OC_Hook::emit(
1277 1277
 						Filesystem::CLASSNAME,
1278
-						$prefix . $hook,
1278
+						$prefix.$hook,
1279 1279
 						[
1280 1280
 							Filesystem::signal_param_run => &$run,
1281 1281
 							Filesystem::signal_param_path => $path
@@ -1284,7 +1284,7 @@  discard block
 block discarded – undo
1284 1284
 				} elseif (!$post) {
1285 1285
 					\OC_Hook::emit(
1286 1286
 						Filesystem::CLASSNAME,
1287
-						$prefix . $hook,
1287
+						$prefix.$hook,
1288 1288
 						[
1289 1289
 							Filesystem::signal_param_path => $path
1290 1290
 						]
@@ -1377,11 +1377,11 @@  discard block
 block discarded – undo
1377 1377
 			return $this->getPartFileInfo($path);
1378 1378
 		}
1379 1379
 		$relativePath = $path;
1380
-		$path = Filesystem::normalizePath($this->fakeRoot . '/' . $path);
1380
+		$path = Filesystem::normalizePath($this->fakeRoot.'/'.$path);
1381 1381
 
1382 1382
 		$mount = Filesystem::getMountManager()->find($path);
1383 1383
 		if (!$mount) {
1384
-			\OC::$server->getLogger()->warning('Mountpoint not found for path: ' . $path);
1384
+			\OC::$server->getLogger()->warning('Mountpoint not found for path: '.$path);
1385 1385
 			return false;
1386 1386
 		}
1387 1387
 		$storage = $mount->getStorage();
@@ -1409,7 +1409,7 @@  discard block
 block discarded – undo
1409 1409
 					//add the sizes of other mount points to the folder
1410 1410
 					$extOnly = ($includeMountPoints === 'ext');
1411 1411
 					$mounts = Filesystem::getMountManager()->findIn($path);
1412
-					$info->setSubMounts(array_filter($mounts, function (IMountPoint $mount) use ($extOnly) {
1412
+					$info->setSubMounts(array_filter($mounts, function(IMountPoint $mount) use ($extOnly) {
1413 1413
 						$subStorage = $mount->getStorage();
1414 1414
 						return !($extOnly && $subStorage instanceof \OCA\Files_Sharing\SharedStorage);
1415 1415
 					}));
@@ -1418,7 +1418,7 @@  discard block
 block discarded – undo
1418 1418
 
1419 1419
 			return $info;
1420 1420
 		} else {
1421
-			\OC::$server->getLogger()->warning('Storage not valid for mountpoint: ' . $mount->getMountPoint());
1421
+			\OC::$server->getLogger()->warning('Storage not valid for mountpoint: '.$mount->getMountPoint());
1422 1422
 		}
1423 1423
 
1424 1424
 		return false;
@@ -1459,18 +1459,18 @@  discard block
 block discarded – undo
1459 1459
 
1460 1460
 			$sharingDisabled = \OCP\Util::isSharingDisabledForUser();
1461 1461
 
1462
-			$fileNames = array_map(function (ICacheEntry $content) {
1462
+			$fileNames = array_map(function(ICacheEntry $content) {
1463 1463
 				return $content->getName();
1464 1464
 			}, $contents);
1465 1465
 			/**
1466 1466
 			 * @var \OC\Files\FileInfo[] $fileInfos
1467 1467
 			 */
1468
-			$fileInfos = array_map(function (ICacheEntry $content) use ($path, $storage, $mount, $sharingDisabled) {
1468
+			$fileInfos = array_map(function(ICacheEntry $content) use ($path, $storage, $mount, $sharingDisabled) {
1469 1469
 				if ($sharingDisabled) {
1470 1470
 					$content['permissions'] = $content['permissions'] & ~\OCP\Constants::PERMISSION_SHARE;
1471 1471
 				}
1472 1472
 				$owner = $this->getUserObjectForOwner($storage->getOwner($content['path']));
1473
-				return new FileInfo($path . '/' . $content['name'], $storage, $content['path'], $content, $mount, $owner);
1473
+				return new FileInfo($path.'/'.$content['name'], $storage, $content['path'], $content, $mount, $owner);
1474 1474
 			}, $contents);
1475 1475
 			$files = array_combine($fileNames, $fileInfos);
1476 1476
 
@@ -1495,7 +1495,7 @@  discard block
 block discarded – undo
1495 1495
 						} catch (\Exception $e) {
1496 1496
 							// sometimes when the storage is not available it can be any exception
1497 1497
 							\OC::$server->getLogger()->logException($e, [
1498
-								'message' => 'Exception while scanning storage "' . $subStorage->getId() . '"',
1498
+								'message' => 'Exception while scanning storage "'.$subStorage->getId().'"',
1499 1499
 								'level' => ILogger::ERROR,
1500 1500
 								'app' => 'lib',
1501 1501
 							]);
@@ -1526,7 +1526,7 @@  discard block
 block discarded – undo
1526 1526
 								$rootEntry['permissions'] = $permissions & (\OCP\Constants::PERMISSION_ALL - (\OCP\Constants::PERMISSION_UPDATE | \OCP\Constants::PERMISSION_DELETE));
1527 1527
 							}
1528 1528
 
1529
-							$rootEntry['path'] = substr(Filesystem::normalizePath($path . '/' . $rootEntry['name']), strlen($user) + 2); // full path without /$user/
1529
+							$rootEntry['path'] = substr(Filesystem::normalizePath($path.'/'.$rootEntry['name']), strlen($user) + 2); // full path without /$user/
1530 1530
 
1531 1531
 							// if sharing was disabled for the user we remove the share permissions
1532 1532
 							if (\OCP\Util::isSharingDisabledForUser()) {
@@ -1534,14 +1534,14 @@  discard block
 block discarded – undo
1534 1534
 							}
1535 1535
 
1536 1536
 							$owner = $this->getUserObjectForOwner($subStorage->getOwner(''));
1537
-							$files[$rootEntry->getName()] = new FileInfo($path . '/' . $rootEntry['name'], $subStorage, '', $rootEntry, $mount, $owner);
1537
+							$files[$rootEntry->getName()] = new FileInfo($path.'/'.$rootEntry['name'], $subStorage, '', $rootEntry, $mount, $owner);
1538 1538
 						}
1539 1539
 					}
1540 1540
 				}
1541 1541
 			}
1542 1542
 
1543 1543
 			if ($mimetype_filter) {
1544
-				$files = array_filter($files, function (FileInfo $file) use ($mimetype_filter) {
1544
+				$files = array_filter($files, function(FileInfo $file) use ($mimetype_filter) {
1545 1545
 					if (strpos($mimetype_filter, '/')) {
1546 1546
 						return $file->getMimetype() === $mimetype_filter;
1547 1547
 					} else {
@@ -1570,7 +1570,7 @@  discard block
 block discarded – undo
1570 1570
 		if ($data instanceof FileInfo) {
1571 1571
 			$data = $data->getData();
1572 1572
 		}
1573
-		$path = Filesystem::normalizePath($this->fakeRoot . '/' . $path);
1573
+		$path = Filesystem::normalizePath($this->fakeRoot.'/'.$path);
1574 1574
 		/**
1575 1575
 		 * @var \OC\Files\Storage\Storage $storage
1576 1576
 		 * @var string $internalPath
@@ -1597,7 +1597,7 @@  discard block
 block discarded – undo
1597 1597
 	 * @return FileInfo[]
1598 1598
 	 */
1599 1599
 	public function search($query) {
1600
-		return $this->searchCommon('search', ['%' . $query . '%']);
1600
+		return $this->searchCommon('search', ['%'.$query.'%']);
1601 1601
 	}
1602 1602
 
1603 1603
 	/**
@@ -1649,10 +1649,10 @@  discard block
 block discarded – undo
1649 1649
 
1650 1650
 			$results = call_user_func_array([$cache, $method], $args);
1651 1651
 			foreach ($results as $result) {
1652
-				if (substr($mountPoint . $result['path'], 0, $rootLength + 1) === $this->fakeRoot . '/') {
1652
+				if (substr($mountPoint.$result['path'], 0, $rootLength + 1) === $this->fakeRoot.'/') {
1653 1653
 					$internalPath = $result['path'];
1654
-					$path = $mountPoint . $result['path'];
1655
-					$result['path'] = substr($mountPoint . $result['path'], $rootLength);
1654
+					$path = $mountPoint.$result['path'];
1655
+					$result['path'] = substr($mountPoint.$result['path'], $rootLength);
1656 1656
 					$owner = $userManager->get($storage->getOwner($internalPath));
1657 1657
 					$files[] = new FileInfo($path, $storage, $internalPath, $result, $mount, $owner);
1658 1658
 				}
@@ -1670,8 +1670,8 @@  discard block
 block discarded – undo
1670 1670
 					if ($results) {
1671 1671
 						foreach ($results as $result) {
1672 1672
 							$internalPath = $result['path'];
1673
-							$result['path'] = rtrim($relativeMountPoint . $result['path'], '/');
1674
-							$path = rtrim($mountPoint . $internalPath, '/');
1673
+							$result['path'] = rtrim($relativeMountPoint.$result['path'], '/');
1674
+							$path = rtrim($mountPoint.$internalPath, '/');
1675 1675
 							$owner = $userManager->get($storage->getOwner($internalPath));
1676 1676
 							$files[] = new FileInfo($path, $storage, $internalPath, $result, $mount, $owner);
1677 1677
 						}
@@ -1692,11 +1692,11 @@  discard block
 block discarded – undo
1692 1692
 	public function getOwner($path) {
1693 1693
 		$info = $this->getFileInfo($path);
1694 1694
 		if (!$info) {
1695
-			throw new NotFoundException($path . ' not found while trying to get owner');
1695
+			throw new NotFoundException($path.' not found while trying to get owner');
1696 1696
 		}
1697 1697
 
1698 1698
 		if ($info->getOwner() === null) {
1699
-			throw new NotFoundException($path . ' has no owner');
1699
+			throw new NotFoundException($path.' has no owner');
1700 1700
 		}
1701 1701
 
1702 1702
 		return $info->getOwner()->getUID();
@@ -1732,7 +1732,7 @@  discard block
 block discarded – undo
1732 1732
 	 * @throws NotFoundException
1733 1733
 	 */
1734 1734
 	public function getPath($id, int $storageId = null) {
1735
-		$id = (int)$id;
1735
+		$id = (int) $id;
1736 1736
 		$manager = Filesystem::getMountManager();
1737 1737
 		$mounts = $manager->findIn($this->fakeRoot);
1738 1738
 		$mounts[] = $manager->find($this->fakeRoot);
@@ -1742,12 +1742,12 @@  discard block
 block discarded – undo
1742 1742
 
1743 1743
 		// put non shared mounts in front of the shared mount
1744 1744
 		// this prevent unneeded recursion into shares
1745
-		usort($mounts, function (IMountPoint $a, IMountPoint $b) {
1745
+		usort($mounts, function(IMountPoint $a, IMountPoint $b) {
1746 1746
 			return $a instanceof SharedMount && (!$b instanceof SharedMount) ? 1 : -1;
1747 1747
 		});
1748 1748
 
1749 1749
 		if (!is_null($storageId)) {
1750
-			$mounts = array_filter($mounts, function (IMountPoint $mount) use ($storageId) {
1750
+			$mounts = array_filter($mounts, function(IMountPoint $mount) use ($storageId) {
1751 1751
 				return $mount->getNumericStorageId() === $storageId;
1752 1752
 			});
1753 1753
 		}
@@ -1760,7 +1760,7 @@  discard block
 block discarded – undo
1760 1760
 				$cache = $mount->getStorage()->getCache();
1761 1761
 				$internalPath = $cache->getPathById($id);
1762 1762
 				if (is_string($internalPath)) {
1763
-					$fullPath = $mount->getMountPoint() . $internalPath;
1763
+					$fullPath = $mount->getMountPoint().$internalPath;
1764 1764
 					if (!is_null($path = $this->getRelativePath($fullPath))) {
1765 1765
 						return $path;
1766 1766
 					}
@@ -1796,10 +1796,10 @@  discard block
 block discarded – undo
1796 1796
 	private function targetIsNotShared(IStorage $targetStorage, string $targetInternalPath) {
1797 1797
 
1798 1798
 		// note: cannot use the view because the target is already locked
1799
-		$fileId = (int)$targetStorage->getCache()->getId($targetInternalPath);
1799
+		$fileId = (int) $targetStorage->getCache()->getId($targetInternalPath);
1800 1800
 		if ($fileId === -1) {
1801 1801
 			// target might not exist, need to check parent instead
1802
-			$fileId = (int)$targetStorage->getCache()->getId(dirname($targetInternalPath));
1802
+			$fileId = (int) $targetStorage->getCache()->getId(dirname($targetInternalPath));
1803 1803
 		}
1804 1804
 
1805 1805
 		// check if any of the parents were shared by the current owner (include collections)
@@ -1899,7 +1899,7 @@  discard block
 block discarded – undo
1899 1899
 		$resultPath = '';
1900 1900
 		foreach ($parts as $part) {
1901 1901
 			if ($part) {
1902
-				$resultPath .= '/' . $part;
1902
+				$resultPath .= '/'.$part;
1903 1903
 				$result[] = $resultPath;
1904 1904
 			}
1905 1905
 		}
@@ -2168,16 +2168,16 @@  discard block
 block discarded – undo
2168 2168
 	public function getUidAndFilename($filename) {
2169 2169
 		$info = $this->getFileInfo($filename);
2170 2170
 		if (!$info instanceof \OCP\Files\FileInfo) {
2171
-			throw new NotFoundException($this->getAbsolutePath($filename) . ' not found');
2171
+			throw new NotFoundException($this->getAbsolutePath($filename).' not found');
2172 2172
 		}
2173 2173
 		$uid = $info->getOwner()->getUID();
2174 2174
 		if ($uid != \OC_User::getUser()) {
2175 2175
 			Filesystem::initMountPoints($uid);
2176
-			$ownerView = new View('/' . $uid . '/files');
2176
+			$ownerView = new View('/'.$uid.'/files');
2177 2177
 			try {
2178 2178
 				$filename = $ownerView->getPath($info['fileid']);
2179 2179
 			} catch (NotFoundException $e) {
2180
-				throw new NotFoundException('File with id ' . $info['fileid'] . ' not found for user ' . $uid);
2180
+				throw new NotFoundException('File with id '.$info['fileid'].' not found for user '.$uid);
2181 2181
 			}
2182 2182
 		}
2183 2183
 		return [$uid, $filename];
@@ -2194,7 +2194,7 @@  discard block
 block discarded – undo
2194 2194
 		$directoryParts = array_filter($directoryParts);
2195 2195
 		foreach ($directoryParts as $key => $part) {
2196 2196
 			$currentPathElements = array_slice($directoryParts, 0, $key);
2197
-			$currentPath = '/' . implode('/', $currentPathElements);
2197
+			$currentPath = '/'.implode('/', $currentPathElements);
2198 2198
 			if ($this->is_file($currentPath)) {
2199 2199
 				return false;
2200 2200
 			}
Please login to merge, or discard this patch.
lib/private/Share/Share.php 1 patch
Indentation   +867 added lines, -867 removed lines patch added patch discarded remove patch
@@ -48,871 +48,871 @@
 block discarded – undo
48 48
  */
49 49
 class Share extends Constants {
50 50
 
51
-	/** CRUDS permissions (Create, Read, Update, Delete, Share) using a bitmask
52
-	 * Construct permissions for share() and setPermissions with Or (|) e.g.
53
-	 * Give user read and update permissions: PERMISSION_READ | PERMISSION_UPDATE
54
-	 *
55
-	 * Check if permission is granted with And (&) e.g. Check if delete is
56
-	 * granted: if ($permissions & PERMISSION_DELETE)
57
-	 *
58
-	 * Remove permissions with And (&) and Not (~) e.g. Remove the update
59
-	 * permission: $permissions &= ~PERMISSION_UPDATE
60
-	 *
61
-	 * Apps are required to handle permissions on their own, this class only
62
-	 * stores and manages the permissions of shares
63
-	 * @see lib/public/constants.php
64
-	 */
65
-
66
-	/**
67
-	 * Register a sharing backend class that implements OCP\Share_Backend for an item type
68
-	 * @param string $itemType Item type
69
-	 * @param string $class Backend class
70
-	 * @param string $collectionOf (optional) Depends on item type
71
-	 * @param array $supportedFileExtensions (optional) List of supported file extensions if this item type depends on files
72
-	 * @return boolean true if backend is registered or false if error
73
-	 */
74
-	public static function registerBackend($itemType, $class, $collectionOf = null, $supportedFileExtensions = null) {
75
-		if (\OC::$server->getConfig()->getAppValue('core', 'shareapi_enabled', 'yes') == 'yes') {
76
-			if (!isset(self::$backendTypes[$itemType])) {
77
-				self::$backendTypes[$itemType] = [
78
-					'class' => $class,
79
-					'collectionOf' => $collectionOf,
80
-					'supportedFileExtensions' => $supportedFileExtensions
81
-				];
82
-				return true;
83
-			}
84
-			\OCP\Util::writeLog('OCP\Share',
85
-				'Sharing backend '.$class.' not registered, '.self::$backendTypes[$itemType]['class']
86
-				.' is already registered for '.$itemType,
87
-				ILogger::WARN);
88
-		}
89
-		return false;
90
-	}
91
-
92
-	/**
93
-	 * Get the items of item type shared with the current user
94
-	 * @param string $itemType
95
-	 * @param int $format (optional) Format type must be defined by the backend
96
-	 * @param mixed $parameters (optional)
97
-	 * @param int $limit Number of items to return (optional) Returns all by default
98
-	 * @param boolean $includeCollections (optional)
99
-	 * @return mixed Return depends on format
100
-	 * @deprecated TESTS ONLY - this methods is only used by tests
101
-	 * called like this:
102
-	 * \OC\Share\Share::getItemsSharedWith('folder'); (apps/files_sharing/tests/UpdaterTest.php)
103
-	 */
104
-	public static function getItemsSharedWith() {
105
-		return self::getItems('folder', null, self::$shareTypeUserAndGroups, \OC_User::getUser());
106
-	}
107
-
108
-	/**
109
-	 * Get the items of item type shared with a user
110
-	 * @param string $itemType
111
-	 * @param string $user id for which user we want the shares
112
-	 * @param int $format (optional) Format type must be defined by the backend
113
-	 * @param mixed $parameters (optional)
114
-	 * @param int $limit Number of items to return (optional) Returns all by default
115
-	 * @param boolean $includeCollections (optional)
116
-	 * @return mixed Return depends on format
117
-	 * @deprecated TESTS ONLY - this methods is only used by tests
118
-	 * called like this:
119
-	 * \OC\Share\Share::getItemsSharedWithUser('test', $shareWith); (tests/lib/Share/Backend.php)
120
-	 */
121
-	public static function getItemsSharedWithUser($itemType, $user) {
122
-		return self::getItems('test', null, self::$shareTypeUserAndGroups, $user);
123
-	}
124
-
125
-	/**
126
-	 * Get the item of item type shared with a given user by source
127
-	 * @param string $itemType
128
-	 * @param string $itemSource
129
-	 * @param string $user User to whom the item was shared
130
-	 * @param string $owner Owner of the share
131
-	 * @param int $shareType only look for a specific share type
132
-	 * @return array Return list of items with file_target, permissions and expiration
133
-	 */
134
-	public static function getItemSharedWithUser($itemType, $itemSource, $user, $owner = null, $shareType = null) {
135
-		$shares = [];
136
-		$fileDependent = false;
137
-
138
-		$where = 'WHERE';
139
-		$fileDependentWhere = '';
140
-		if ($itemType === 'file' || $itemType === 'folder') {
141
-			$fileDependent = true;
142
-			$column = 'file_source';
143
-			$fileDependentWhere = 'INNER JOIN `*PREFIX*filecache` ON `file_source` = `*PREFIX*filecache`.`fileid` ';
144
-			$fileDependentWhere .= 'INNER JOIN `*PREFIX*storages` ON `numeric_id` = `*PREFIX*filecache`.`storage` ';
145
-		} else {
146
-			$column = 'item_source';
147
-		}
148
-
149
-		$select = self::createSelectStatement(self::FORMAT_NONE, $fileDependent);
150
-
151
-		$where .= ' `' . $column . '` = ? AND `item_type` = ? ';
152
-		$arguments = [$itemSource, $itemType];
153
-		// for link shares $user === null
154
-		if ($user !== null) {
155
-			$where .= ' AND `share_with` = ? ';
156
-			$arguments[] = $user;
157
-		}
158
-
159
-		if ($shareType !== null) {
160
-			$where .= ' AND `share_type` = ? ';
161
-			$arguments[] = $shareType;
162
-		}
163
-
164
-		if ($owner !== null) {
165
-			$where .= ' AND `uid_owner` = ? ';
166
-			$arguments[] = $owner;
167
-		}
168
-
169
-		$query = \OC_DB::prepare('SELECT ' . $select . ' FROM `*PREFIX*share` '. $fileDependentWhere . $where);
170
-
171
-		$result = \OC_DB::executeAudited($query, $arguments);
172
-
173
-		while ($row = $result->fetchRow()) {
174
-			if ($fileDependent && !self::isFileReachable($row['path'], $row['storage_id'])) {
175
-				continue;
176
-			}
177
-			if ($fileDependent && (int)$row['file_parent'] === -1) {
178
-				// if it is a mount point we need to get the path from the mount manager
179
-				$mountManager = \OC\Files\Filesystem::getMountManager();
180
-				$mountPoint = $mountManager->findByStorageId($row['storage_id']);
181
-				if (!empty($mountPoint)) {
182
-					$path = $mountPoint[0]->getMountPoint();
183
-					$path = trim($path, '/');
184
-					$path = substr($path, strlen($owner) + 1); //normalize path to 'files/foo.txt`
185
-					$row['path'] = $path;
186
-				} else {
187
-					\OC::$server->getLogger()->warning(
188
-						'Could not resolve mount point for ' . $row['storage_id'],
189
-						['app' => 'OCP\Share']
190
-					);
191
-				}
192
-			}
193
-			$shares[] = $row;
194
-		}
195
-		$result->closeCursor();
196
-
197
-		//if didn't found a result than let's look for a group share.
198
-		if (empty($shares) && $user !== null) {
199
-			$userObject = \OC::$server->getUserManager()->get($user);
200
-			$groups = [];
201
-			if ($userObject) {
202
-				$groups = \OC::$server->getGroupManager()->getUserGroupIds($userObject);
203
-			}
204
-
205
-			if (!empty($groups)) {
206
-				$where = $fileDependentWhere . ' WHERE `' . $column . '` = ? AND `item_type` = ? AND `share_with` in (?)';
207
-				$arguments = [$itemSource, $itemType, $groups];
208
-				$types = [null, null, IQueryBuilder::PARAM_STR_ARRAY];
209
-
210
-				if ($owner !== null) {
211
-					$where .= ' AND `uid_owner` = ?';
212
-					$arguments[] = $owner;
213
-					$types[] = null;
214
-				}
215
-
216
-				// TODO: inject connection, hopefully one day in the future when this
217
-				// class isn't static anymore...
218
-				$conn = \OC::$server->getDatabaseConnection();
219
-				$result = $conn->executeQuery(
220
-					'SELECT ' . $select . ' FROM `*PREFIX*share` ' . $where,
221
-					$arguments,
222
-					$types
223
-				);
224
-
225
-				while ($row = $result->fetch()) {
226
-					$shares[] = $row;
227
-				}
228
-			}
229
-		}
230
-
231
-		return $shares;
232
-	}
233
-
234
-	/**
235
-	 * Get the shared item of item type owned by the current user
236
-	 * @param string $itemType
237
-	 * @param string $itemSource
238
-	 * @param int $format (optional) Format type must be defined by the backend
239
-	 * @param mixed $parameters
240
-	 * @param boolean $includeCollections
241
-	 * @return mixed Return depends on format
242
-	 *
243
-	 * Refactoring notes:
244
-	 *   * defacto $parameters and $format is always the default and therefore is removed in the subsequent call
245
-	 */
246
-	public static function getItemShared($itemType, $itemSource, $format = self::FORMAT_NONE,
247
-										 $parameters = null, $includeCollections = false) {
248
-		return self::getItems($itemType, $itemSource, null, null, \OC_User::getUser(), self::FORMAT_NONE,
249
-			null, -1, $includeCollections);
250
-	}
251
-
252
-	/**
253
-	 * Get the backend class for the specified item type
254
-	 * @param string $itemType
255
-	 * @throws \Exception
256
-	 * @return \OCP\Share_Backend
257
-	 */
258
-	public static function getBackend($itemType) {
259
-		$l = \OC::$server->getL10N('lib');
260
-		if (isset(self::$backends[$itemType])) {
261
-			return self::$backends[$itemType];
262
-		} elseif (isset(self::$backendTypes[$itemType]['class'])) {
263
-			$class = self::$backendTypes[$itemType]['class'];
264
-			if (class_exists($class)) {
265
-				self::$backends[$itemType] = new $class;
266
-				if (!(self::$backends[$itemType] instanceof \OCP\Share_Backend)) {
267
-					$message = 'Sharing backend %s must implement the interface OCP\Share_Backend';
268
-					$message_t = $l->t('Sharing backend %s must implement the interface OCP\Share_Backend', [$class]);
269
-					\OCP\Util::writeLog('OCP\Share', sprintf($message, $class), ILogger::ERROR);
270
-					throw new \Exception($message_t);
271
-				}
272
-				return self::$backends[$itemType];
273
-			} else {
274
-				$message = 'Sharing backend %s not found';
275
-				$message_t = $l->t('Sharing backend %s not found', [$class]);
276
-				\OCP\Util::writeLog('OCP\Share', sprintf($message, $class), ILogger::ERROR);
277
-				throw new \Exception($message_t);
278
-			}
279
-		}
280
-		$message = 'Sharing backend for %s not found';
281
-		$message_t = $l->t('Sharing backend for %s not found', [$itemType]);
282
-		\OCP\Util::writeLog('OCP\Share', sprintf($message, $itemType), ILogger::ERROR);
283
-		throw new \Exception($message_t);
284
-	}
285
-
286
-	/**
287
-	 * Check if resharing is allowed
288
-	 * @return boolean true if allowed or false
289
-	 *
290
-	 * Resharing is allowed by default if not configured
291
-	 */
292
-	public static function isResharingAllowed() {
293
-		if (!isset(self::$isResharingAllowed)) {
294
-			if (\OC::$server->getConfig()->getAppValue('core', 'shareapi_allow_resharing', 'yes') == 'yes') {
295
-				self::$isResharingAllowed = true;
296
-			} else {
297
-				self::$isResharingAllowed = false;
298
-			}
299
-		}
300
-		return self::$isResharingAllowed;
301
-	}
302
-
303
-	/**
304
-	 * Get a list of collection item types for the specified item type
305
-	 * @param string $itemType
306
-	 * @return array
307
-	 */
308
-	private static function getCollectionItemTypes($itemType) {
309
-		$collectionTypes = [$itemType];
310
-		foreach (self::$backendTypes as $type => $backend) {
311
-			if (in_array($backend['collectionOf'], $collectionTypes)) {
312
-				$collectionTypes[] = $type;
313
-			}
314
-		}
315
-		// TODO Add option for collections to be collection of themselves, only 'folder' does it now...
316
-		if (isset(self::$backendTypes[$itemType]) && (!self::getBackend($itemType) instanceof \OCP\Share_Backend_Collection || $itemType != 'folder')) {
317
-			unset($collectionTypes[0]);
318
-		}
319
-		// Return array if collections were found or the item type is a
320
-		// collection itself - collections can be inside collections
321
-		if (count($collectionTypes) > 0) {
322
-			return $collectionTypes;
323
-		}
324
-		return false;
325
-	}
326
-
327
-	/**
328
-	 * Get shared items from the database
329
-	 * @param string $itemType
330
-	 * @param string $item Item source or target (optional)
331
-	 * @param int $shareType SHARE_TYPE_USER, SHARE_TYPE_GROUP, SHARE_TYPE_LINK, $shareTypeUserAndGroups, or $shareTypeGroupUserUnique
332
-	 * @param string $shareWith User or group the item is being shared with
333
-	 * @param string $uidOwner User that is the owner of shared items (optional)
334
-	 * @param int $format Format to convert items to with formatItems() (optional)
335
-	 * @param mixed $parameters to pass to formatItems() (optional)
336
-	 * @param int $limit Number of items to return, -1 to return all matches (optional)
337
-	 * @param boolean $includeCollections Include collection item types (optional)
338
-	 * @param boolean $itemShareWithBySource (optional)
339
-	 * @param boolean $checkExpireDate
340
-	 * @return array
341
-	 *
342
-	 * See public functions getItem(s)... for parameter usage
343
-	 *
344
-	 * Refactoring notes:
345
-	 *   * defacto $limit, $itemsShareWithBySource, $checkExpireDate, $parameters and $format is always the default and therefore is removed in the subsequent call
346
-	 */
347
-	public static function getItems($itemType, $item = null, $shareType = null, $shareWith = null,
348
-									$uidOwner = null, $format = self::FORMAT_NONE, $parameters = null, $limit = -1,
349
-									$includeCollections = false, $itemShareWithBySource = false, $checkExpireDate = true) {
350
-		if (\OC::$server->getConfig()->getAppValue('core', 'shareapi_enabled', 'yes') != 'yes') {
351
-			return [];
352
-		}
353
-		$backend = self::getBackend($itemType);
354
-		$collectionTypes = false;
355
-		// Get filesystem root to add it to the file target and remove from the
356
-		// file source, match file_source with the file cache
357
-		if ($itemType == 'file' || $itemType == 'folder') {
358
-			if (!is_null($uidOwner)) {
359
-				$root = \OC\Files\Filesystem::getRoot();
360
-			} else {
361
-				$root = '';
362
-			}
363
-			$where = 'INNER JOIN `*PREFIX*filecache` ON `file_source` = `*PREFIX*filecache`.`fileid` ';
364
-			if (!isset($item)) {
365
-				$where .= ' AND `file_target` IS NOT NULL ';
366
-			}
367
-			$where .= 'INNER JOIN `*PREFIX*storages` ON `numeric_id` = `*PREFIX*filecache`.`storage` ';
368
-			$fileDependent = true;
369
-			$queryArgs = [];
370
-		} else {
371
-			$fileDependent = false;
372
-			$root = '';
373
-			$collectionTypes = self::getCollectionItemTypes($itemType);
374
-			if ($includeCollections && !isset($item) && $collectionTypes) {
375
-				// If includeCollections is true, find collections of this item type, e.g. a music album contains songs
376
-				if (!in_array($itemType, $collectionTypes)) {
377
-					$itemTypes = array_merge([$itemType], $collectionTypes);
378
-				} else {
379
-					$itemTypes = $collectionTypes;
380
-				}
381
-				$placeholders = implode(',', array_fill(0, count($itemTypes), '?'));
382
-				$where = ' WHERE `item_type` IN ('.$placeholders.'))';
383
-				$queryArgs = $itemTypes;
384
-			} else {
385
-				$where = ' WHERE `item_type` = ?';
386
-				$queryArgs = [$itemType];
387
-			}
388
-		}
389
-		if (\OC::$server->getConfig()->getAppValue('core', 'shareapi_allow_links', 'yes') !== 'yes') {
390
-			$where .= ' AND `share_type` != ?';
391
-			$queryArgs[] = IShare::TYPE_LINK;
392
-		}
393
-		if (isset($shareType)) {
394
-			// Include all user and group items
395
-			if ($shareType == self::$shareTypeUserAndGroups && isset($shareWith)) {
396
-				$where .= ' AND ((`share_type` in (?, ?) AND `share_with` = ?) ';
397
-				$queryArgs[] = IShare::TYPE_USER;
398
-				$queryArgs[] = self::$shareTypeGroupUserUnique;
399
-				$queryArgs[] = $shareWith;
400
-
401
-				$user = \OC::$server->getUserManager()->get($shareWith);
402
-				$groups = [];
403
-				if ($user) {
404
-					$groups = \OC::$server->getGroupManager()->getUserGroupIds($user);
405
-				}
406
-				if (!empty($groups)) {
407
-					$placeholders = implode(',', array_fill(0, count($groups), '?'));
408
-					$where .= ' OR (`share_type` = ? AND `share_with` IN ('.$placeholders.')) ';
409
-					$queryArgs[] = IShare::TYPE_GROUP;
410
-					$queryArgs = array_merge($queryArgs, $groups);
411
-				}
412
-				$where .= ')';
413
-				// Don't include own group shares
414
-				$where .= ' AND `uid_owner` != ?';
415
-				$queryArgs[] = $shareWith;
416
-			} else {
417
-				$where .= ' AND `share_type` = ?';
418
-				$queryArgs[] = $shareType;
419
-				if (isset($shareWith)) {
420
-					$where .= ' AND `share_with` = ?';
421
-					$queryArgs[] = $shareWith;
422
-				}
423
-			}
424
-		}
425
-		if (isset($uidOwner)) {
426
-			$where .= ' AND `uid_owner` = ?';
427
-			$queryArgs[] = $uidOwner;
428
-			if (!isset($shareType)) {
429
-				// Prevent unique user targets for group shares from being selected
430
-				$where .= ' AND `share_type` != ?';
431
-				$queryArgs[] = self::$shareTypeGroupUserUnique;
432
-			}
433
-			if ($fileDependent) {
434
-				$column = 'file_source';
435
-			} else {
436
-				$column = 'item_source';
437
-			}
438
-		} else {
439
-			if ($fileDependent) {
440
-				$column = 'file_target';
441
-			} else {
442
-				$column = 'item_target';
443
-			}
444
-		}
445
-		if (isset($item)) {
446
-			$collectionTypes = self::getCollectionItemTypes($itemType);
447
-			if ($includeCollections && $collectionTypes && !in_array('folder', $collectionTypes)) {
448
-				$where .= ' AND (';
449
-			} else {
450
-				$where .= ' AND';
451
-			}
452
-			// If looking for own shared items, check item_source else check item_target
453
-			if (isset($uidOwner)) {
454
-				// If item type is a file, file source needs to be checked in case the item was converted
455
-				if ($fileDependent) {
456
-					$where .= ' `file_source` = ?';
457
-					$column = 'file_source';
458
-				} else {
459
-					$where .= ' `item_source` = ?';
460
-					$column = 'item_source';
461
-				}
462
-			} else {
463
-				if ($fileDependent) {
464
-					$where .= ' `file_target` = ?';
465
-					$item = \OC\Files\Filesystem::normalizePath($item);
466
-				} else {
467
-					$where .= ' `item_target` = ?';
468
-				}
469
-			}
470
-			$queryArgs[] = $item;
471
-			if ($includeCollections && $collectionTypes && !in_array('folder', $collectionTypes)) {
472
-				$placeholders = implode(',', array_fill(0, count($collectionTypes), '?'));
473
-				$where .= ' OR `item_type` IN ('.$placeholders.'))';
474
-				$queryArgs = array_merge($queryArgs, $collectionTypes);
475
-			}
476
-		}
477
-
478
-		$where .= ' ORDER BY `*PREFIX*share`.`id` ASC';
479
-
480
-		$queryLimit = null;
481
-		$select = self::createSelectStatement(self::FORMAT_NONE, $fileDependent, $uidOwner);
482
-		$root = strlen($root);
483
-		$query = \OC_DB::prepare('SELECT '.$select.' FROM `*PREFIX*share` '.$where, $queryLimit);
484
-		$result = $query->execute($queryArgs);
485
-		if ($result === false) {
486
-			\OCP\Util::writeLog('OCP\Share',
487
-				\OC_DB::getErrorMessage() . ', select=' . $select . ' where=',
488
-				ILogger::ERROR);
489
-		}
490
-		$items = [];
491
-		$targets = [];
492
-		$switchedItems = [];
493
-		$mounts = [];
494
-		while ($row = $result->fetchRow()) {
495
-			self::transformDBResults($row);
496
-			// Filter out duplicate group shares for users with unique targets
497
-			if ($fileDependent && !self::isFileReachable($row['path'], $row['storage_id'])) {
498
-				continue;
499
-			}
500
-			if ($row['share_type'] == self::$shareTypeGroupUserUnique && isset($items[$row['parent']])) {
501
-				$row['share_type'] = IShare::TYPE_GROUP;
502
-				$row['unique_name'] = true; // remember that we use a unique name for this user
503
-				$row['share_with'] = $items[$row['parent']]['share_with'];
504
-				// if the group share was unshared from the user we keep the permission, otherwise
505
-				// we take the permission from the parent because this is always the up-to-date
506
-				// permission for the group share
507
-				if ($row['permissions'] > 0) {
508
-					$row['permissions'] = $items[$row['parent']]['permissions'];
509
-				}
510
-				// Remove the parent group share
511
-				unset($items[$row['parent']]);
512
-				if ($row['permissions'] == 0) {
513
-					continue;
514
-				}
515
-			} elseif (!isset($uidOwner)) {
516
-				// Check if the same target already exists
517
-				if (isset($targets[$row['id']])) {
518
-					// Check if the same owner shared with the user twice
519
-					// through a group and user share - this is allowed
520
-					$id = $targets[$row['id']];
521
-					if (isset($items[$id]) && $items[$id]['uid_owner'] == $row['uid_owner']) {
522
-						// Switch to group share type to ensure resharing conditions aren't bypassed
523
-						if ($items[$id]['share_type'] != IShare::TYPE_GROUP) {
524
-							$items[$id]['share_type'] = IShare::TYPE_GROUP;
525
-							$items[$id]['share_with'] = $row['share_with'];
526
-						}
527
-						// Switch ids if sharing permission is granted on only
528
-						// one share to ensure correct parent is used if resharing
529
-						if (~(int)$items[$id]['permissions'] & \OCP\Constants::PERMISSION_SHARE
530
-							&& (int)$row['permissions'] & \OCP\Constants::PERMISSION_SHARE) {
531
-							$items[$row['id']] = $items[$id];
532
-							$switchedItems[$id] = $row['id'];
533
-							unset($items[$id]);
534
-							$id = $row['id'];
535
-						}
536
-						$items[$id]['permissions'] |= (int)$row['permissions'];
537
-					}
538
-					continue;
539
-				} elseif (!empty($row['parent'])) {
540
-					$targets[$row['parent']] = $row['id'];
541
-				}
542
-			}
543
-			// Remove root from file source paths if retrieving own shared items
544
-			if (isset($uidOwner) && isset($row['path'])) {
545
-				if (isset($row['parent'])) {
546
-					$query = \OC::$server->getDatabaseConnection()->getQueryBuilder();
547
-					$query->select('file_target')
548
-						->from('share')
549
-						->where($query->expr()->eq('id', $query->createNamedParameter($row['parent'])));
550
-
551
-					$parentResult = $query->execute();
552
-					$parentRow = $parentResult->fetch();
553
-					$parentResult->closeCursor();
554
-
555
-					if ($parentRow === false) {
556
-						\OCP\Util::writeLog('OCP\Share', 'Can\'t select parent: ' .
557
-							\OC_DB::getErrorMessage() . ', select=' . $select . ' where=' . $where,
558
-							ILogger::ERROR);
559
-					} else {
560
-						$tmpPath = $parentRow['file_target'];
561
-						// find the right position where the row path continues from the target path
562
-						$pos = strrpos($row['path'], $parentRow['file_target']);
563
-						$subPath = substr($row['path'], $pos);
564
-						$splitPath = explode('/', $subPath);
565
-						foreach (array_slice($splitPath, 2) as $pathPart) {
566
-							$tmpPath = $tmpPath . '/' . $pathPart;
567
-						}
568
-						$row['path'] = $tmpPath;
569
-					}
570
-				} else {
571
-					if (!isset($mounts[$row['storage']])) {
572
-						$mountPoints = \OC\Files\Filesystem::getMountByNumericId($row['storage']);
573
-						if (is_array($mountPoints) && !empty($mountPoints)) {
574
-							$mounts[$row['storage']] = current($mountPoints);
575
-						}
576
-					}
577
-					if (!empty($mounts[$row['storage']])) {
578
-						$path = $mounts[$row['storage']]->getMountPoint().$row['path'];
579
-						$relPath = substr($path, $root); // path relative to data/user
580
-						$row['path'] = rtrim($relPath, '/');
581
-					}
582
-				}
583
-			}
584
-
585
-			// Check if resharing is allowed, if not remove share permission
586
-			if (isset($row['permissions']) && (!self::isResharingAllowed() | \OCP\Util::isSharingDisabledForUser())) {
587
-				$row['permissions'] &= ~\OCP\Constants::PERMISSION_SHARE;
588
-			}
589
-			// Add display names to result
590
-			$row['share_with_displayname'] = $row['share_with'];
591
-			if (isset($row['share_with']) && $row['share_with'] != '' &&
592
-				$row['share_type'] === IShare::TYPE_USER) {
593
-				$shareWithUser = \OC::$server->getUserManager()->get($row['share_with']);
594
-				$row['share_with_displayname'] = $shareWithUser === null ? $row['share_with'] : $shareWithUser->getDisplayName();
595
-			} elseif (isset($row['share_with']) && $row['share_with'] != '' &&
596
-				$row['share_type'] === IShare::TYPE_REMOTE) {
597
-				$addressBookEntries = \OC::$server->getContactsManager()->search($row['share_with'], ['CLOUD']);
598
-				foreach ($addressBookEntries as $entry) {
599
-					foreach ($entry['CLOUD'] as $cloudID) {
600
-						if ($cloudID === $row['share_with']) {
601
-							$row['share_with_displayname'] = $entry['FN'];
602
-						}
603
-					}
604
-				}
605
-			}
606
-			if (isset($row['uid_owner']) && $row['uid_owner'] != '') {
607
-				$ownerUser = \OC::$server->getUserManager()->get($row['uid_owner']);
608
-				$row['displayname_owner'] = $ownerUser === null ? $row['uid_owner'] : $ownerUser->getDisplayName();
609
-			}
610
-
611
-			if ($row['permissions'] > 0) {
612
-				$items[$row['id']] = $row;
613
-			}
614
-		}
615
-
616
-		// group items if we are looking for items shared with the current user
617
-		if (isset($shareWith) && $shareWith === \OC_User::getUser()) {
618
-			$items = self::groupItems($items, $itemType);
619
-		}
620
-
621
-		if (!empty($items)) {
622
-			$collectionItems = [];
623
-			foreach ($items as &$row) {
624
-				// Check if this is a collection of the requested item type
625
-				if ($includeCollections && $collectionTypes && $row['item_type'] !== 'folder' && in_array($row['item_type'], $collectionTypes)) {
626
-					if (($collectionBackend = self::getBackend($row['item_type']))
627
-						&& $collectionBackend instanceof \OCP\Share_Backend_Collection) {
628
-						// Collections can be inside collections, check if the item is a collection
629
-						if (isset($item) && $row['item_type'] == $itemType && $row[$column] == $item) {
630
-							$collectionItems[] = $row;
631
-						} else {
632
-							$collection = [];
633
-							$collection['item_type'] = $row['item_type'];
634
-							if ($row['item_type'] == 'file' || $row['item_type'] == 'folder') {
635
-								$collection['path'] = basename($row['path']);
636
-							}
637
-							$row['collection'] = $collection;
638
-							// Fetch all of the children sources
639
-							$children = $collectionBackend->getChildren($row[$column]);
640
-							foreach ($children as $child) {
641
-								$childItem = $row;
642
-								$childItem['item_type'] = $itemType;
643
-								if ($row['item_type'] != 'file' && $row['item_type'] != 'folder') {
644
-									$childItem['item_source'] = $child['source'];
645
-									$childItem['item_target'] = $child['target'];
646
-								}
647
-								if ($backend instanceof \OCP\Share_Backend_File_Dependent) {
648
-									if ($row['item_type'] == 'file' || $row['item_type'] == 'folder') {
649
-										$childItem['file_source'] = $child['source'];
650
-									} else { // TODO is this really needed if we already know that we use the file backend?
651
-										$meta = \OC\Files\Filesystem::getFileInfo($child['file_path']);
652
-										$childItem['file_source'] = $meta['fileid'];
653
-									}
654
-									$childItem['file_target'] =
655
-										\OC\Files\Filesystem::normalizePath($child['file_path']);
656
-								}
657
-								if (isset($item)) {
658
-									if ($childItem[$column] == $item) {
659
-										$collectionItems[] = $childItem;
660
-									}
661
-								} else {
662
-									$collectionItems[] = $childItem;
663
-								}
664
-							}
665
-						}
666
-					}
667
-					// Remove collection item
668
-					$toRemove = $row['id'];
669
-					if (array_key_exists($toRemove, $switchedItems)) {
670
-						$toRemove = $switchedItems[$toRemove];
671
-					}
672
-					unset($items[$toRemove]);
673
-				} elseif ($includeCollections && $collectionTypes && in_array($row['item_type'], $collectionTypes)) {
674
-					// FIXME: Thats a dirty hack to improve file sharing performance,
675
-					// see github issue #10588 for more details
676
-					// Need to find a solution which works for all back-ends
677
-					$collectionBackend = self::getBackend($row['item_type']);
678
-					$sharedParents = $collectionBackend->getParents($row['item_source']);
679
-					foreach ($sharedParents as $parent) {
680
-						$collectionItems[] = $parent;
681
-					}
682
-				}
683
-			}
684
-			if (!empty($collectionItems)) {
685
-				$collectionItems = array_unique($collectionItems, SORT_REGULAR);
686
-				$items = array_merge($items, $collectionItems);
687
-			}
688
-
689
-			// filter out invalid items, these can appear when subshare entries exist
690
-			// for a group in which the requested user isn't a member any more
691
-			$items = array_filter($items, function ($item) {
692
-				return $item['share_type'] !== self::$shareTypeGroupUserUnique;
693
-			});
694
-
695
-			return self::formatResult($items, $column, $backend);
696
-		} elseif ($includeCollections && $collectionTypes && in_array('folder', $collectionTypes)) {
697
-			// FIXME: Thats a dirty hack to improve file sharing performance,
698
-			// see github issue #10588 for more details
699
-			// Need to find a solution which works for all back-ends
700
-			$collectionItems = [];
701
-			$collectionBackend = self::getBackend('folder');
702
-			$sharedParents = $collectionBackend->getParents($item, $shareWith, $uidOwner);
703
-			foreach ($sharedParents as $parent) {
704
-				$collectionItems[] = $parent;
705
-			}
706
-			return self::formatResult($collectionItems, $column, $backend);
707
-		}
708
-
709
-		return [];
710
-	}
711
-
712
-	/**
713
-	 * group items with link to the same source
714
-	 *
715
-	 * @param array $items
716
-	 * @param string $itemType
717
-	 * @return array of grouped items
718
-	 */
719
-	protected static function groupItems($items, $itemType) {
720
-		$fileSharing = $itemType === 'file' || $itemType === 'folder';
721
-
722
-		$result = [];
723
-
724
-		foreach ($items as $item) {
725
-			$grouped = false;
726
-			foreach ($result as $key => $r) {
727
-				// for file/folder shares we need to compare file_source, otherwise we compare item_source
728
-				// only group shares if they already point to the same target, otherwise the file where shared
729
-				// before grouping of shares was added. In this case we don't group them toi avoid confusions
730
-				if (($fileSharing && $item['file_source'] === $r['file_source'] && $item['file_target'] === $r['file_target']) ||
731
-					(!$fileSharing && $item['item_source'] === $r['item_source'] && $item['item_target'] === $r['item_target'])) {
732
-					// add the first item to the list of grouped shares
733
-					if (!isset($result[$key]['grouped'])) {
734
-						$result[$key]['grouped'][] = $result[$key];
735
-					}
736
-					$result[$key]['permissions'] = (int) $item['permissions'] | (int) $r['permissions'];
737
-					$result[$key]['grouped'][] = $item;
738
-					$grouped = true;
739
-					break;
740
-				}
741
-			}
742
-
743
-			if (!$grouped) {
744
-				$result[] = $item;
745
-			}
746
-		}
747
-
748
-		return $result;
749
-	}
750
-
751
-	/**
752
-	 * construct select statement
753
-	 * @param int $format
754
-	 * @param boolean $fileDependent ist it a file/folder share or a generla share
755
-	 * @param string $uidOwner
756
-	 * @return string select statement
757
-	 */
758
-	private static function createSelectStatement($format, $fileDependent, $uidOwner = null) {
759
-		$select = '*';
760
-		if ($format == self::FORMAT_STATUSES) {
761
-			if ($fileDependent) {
762
-				$select = '`*PREFIX*share`.`id`, `*PREFIX*share`.`parent`, `share_type`, `path`, `storage`, '
763
-					. '`share_with`, `uid_owner` , `file_source`, `stime`, `*PREFIX*share`.`permissions`, '
764
-					. '`*PREFIX*storages`.`id` AS `storage_id`, `*PREFIX*filecache`.`parent` as `file_parent`, '
765
-					. '`uid_initiator`';
766
-			} else {
767
-				$select = '`id`, `parent`, `share_type`, `share_with`, `uid_owner`, `item_source`, `stime`, `*PREFIX*share`.`permissions`';
768
-			}
769
-		} else {
770
-			if (isset($uidOwner)) {
771
-				if ($fileDependent) {
772
-					$select = '`*PREFIX*share`.`id`, `item_type`, `item_source`, `*PREFIX*share`.`parent`,'
773
-						. ' `share_type`, `share_with`, `file_source`, `file_target`, `path`, `*PREFIX*share`.`permissions`, `stime`,'
774
-						. ' `expiration`, `token`, `storage`, `mail_send`, `uid_owner`, '
775
-						. '`*PREFIX*storages`.`id` AS `storage_id`, `*PREFIX*filecache`.`parent` as `file_parent`';
776
-				} else {
777
-					$select = '`id`, `item_type`, `item_source`, `parent`, `share_type`, `share_with`, `*PREFIX*share`.`permissions`,'
778
-						. ' `stime`, `file_source`, `expiration`, `token`, `mail_send`, `uid_owner`';
779
-				}
780
-			} else {
781
-				if ($fileDependent) {
782
-					if ($format == \OCA\Files_Sharing\ShareBackend\File::FORMAT_GET_FOLDER_CONTENTS || $format == \OCA\Files_Sharing\ShareBackend\File::FORMAT_FILE_APP_ROOT) {
783
-						$select = '`*PREFIX*share`.`id`, `item_type`, `item_source`, `*PREFIX*share`.`parent`, `uid_owner`, '
784
-							. '`share_type`, `share_with`, `file_source`, `path`, `file_target`, `stime`, '
785
-							. '`*PREFIX*share`.`permissions`, `expiration`, `storage`, `*PREFIX*filecache`.`parent` as `file_parent`, '
786
-							. '`name`, `mtime`, `mimetype`, `mimepart`, `size`, `encrypted`, `etag`, `mail_send`';
787
-					} else {
788
-						$select = '`*PREFIX*share`.`id`, `item_type`, `item_source`, `item_target`,'
789
-							. '`*PREFIX*share`.`parent`, `share_type`, `share_with`, `uid_owner`,'
790
-							. '`file_source`, `path`, `file_target`, `*PREFIX*share`.`permissions`,'
791
-							. '`stime`, `expiration`, `token`, `storage`, `mail_send`,'
792
-							. '`*PREFIX*storages`.`id` AS `storage_id`, `*PREFIX*filecache`.`parent` as `file_parent`';
793
-					}
794
-				}
795
-			}
796
-		}
797
-		return $select;
798
-	}
799
-
800
-
801
-	/**
802
-	 * transform db results
803
-	 * @param array $row result
804
-	 */
805
-	private static function transformDBResults(&$row) {
806
-		if (isset($row['id'])) {
807
-			$row['id'] = (int) $row['id'];
808
-		}
809
-		if (isset($row['share_type'])) {
810
-			$row['share_type'] = (int) $row['share_type'];
811
-		}
812
-		if (isset($row['parent'])) {
813
-			$row['parent'] = (int) $row['parent'];
814
-		}
815
-		if (isset($row['file_parent'])) {
816
-			$row['file_parent'] = (int) $row['file_parent'];
817
-		}
818
-		if (isset($row['file_source'])) {
819
-			$row['file_source'] = (int) $row['file_source'];
820
-		}
821
-		if (isset($row['permissions'])) {
822
-			$row['permissions'] = (int) $row['permissions'];
823
-		}
824
-		if (isset($row['storage'])) {
825
-			$row['storage'] = (int) $row['storage'];
826
-		}
827
-		if (isset($row['stime'])) {
828
-			$row['stime'] = (int) $row['stime'];
829
-		}
830
-		if (isset($row['expiration']) && $row['share_type'] !== IShare::TYPE_LINK) {
831
-			// discard expiration date for non-link shares, which might have been
832
-			// set by ancient bugs
833
-			$row['expiration'] = null;
834
-		}
835
-	}
836
-
837
-	/**
838
-	 * format result
839
-	 * @param array $items result
840
-	 * @param string $column is it a file share or a general share ('file_target' or 'item_target')
841
-	 * @param \OCP\Share_Backend $backend sharing backend
842
-	 * @param int $format
843
-	 * @param array $parameters additional format parameters
844
-	 * @return array format result
845
-	 */
846
-	private static function formatResult($items, $column, $backend, $format = self::FORMAT_NONE , $parameters = null) {
847
-		if ($format === self::FORMAT_NONE) {
848
-			return $items;
849
-		} elseif ($format === self::FORMAT_STATUSES) {
850
-			$statuses = [];
851
-			foreach ($items as $item) {
852
-				if ($item['share_type'] === IShare::TYPE_LINK) {
853
-					if ($item['uid_initiator'] !== \OC::$server->getUserSession()->getUser()->getUID()) {
854
-						continue;
855
-					}
856
-					$statuses[$item[$column]]['link'] = true;
857
-				} elseif (!isset($statuses[$item[$column]])) {
858
-					$statuses[$item[$column]]['link'] = false;
859
-				}
860
-				if (!empty($item['file_target'])) {
861
-					$statuses[$item[$column]]['path'] = $item['path'];
862
-				}
863
-			}
864
-			return $statuses;
865
-		} else {
866
-			return $backend->formatItems($items, $format, $parameters);
867
-		}
868
-	}
869
-
870
-	/**
871
-	 * remove protocol from URL
872
-	 *
873
-	 * @param string $url
874
-	 * @return string
875
-	 */
876
-	public static function removeProtocolFromUrl($url) {
877
-		if (strpos($url, 'https://') === 0) {
878
-			return substr($url, strlen('https://'));
879
-		} elseif (strpos($url, 'http://') === 0) {
880
-			return substr($url, strlen('http://'));
881
-		}
882
-
883
-		return $url;
884
-	}
885
-
886
-
887
-	/**
888
-	 * @return int
889
-	 */
890
-	public static function getExpireInterval() {
891
-		return (int)\OC::$server->getConfig()->getAppValue('core', 'shareapi_expire_after_n_days', '7');
892
-	}
893
-
894
-	/**
895
-	 * Checks whether the given path is reachable for the given owner
896
-	 *
897
-	 * @param string $path path relative to files
898
-	 * @param string $ownerStorageId storage id of the owner
899
-	 *
900
-	 * @return boolean true if file is reachable, false otherwise
901
-	 */
902
-	private static function isFileReachable($path, $ownerStorageId) {
903
-		// if outside the home storage, file is always considered reachable
904
-		if (!(substr($ownerStorageId, 0, 6) === 'home::' ||
905
-			substr($ownerStorageId, 0, 13) === 'object::user:'
906
-		)) {
907
-			return true;
908
-		}
909
-
910
-		// if inside the home storage, the file has to be under "/files/"
911
-		$path = ltrim($path, '/');
912
-		if (substr($path, 0, 6) === 'files/') {
913
-			return true;
914
-		}
915
-
916
-		return false;
917
-	}
51
+    /** CRUDS permissions (Create, Read, Update, Delete, Share) using a bitmask
52
+     * Construct permissions for share() and setPermissions with Or (|) e.g.
53
+     * Give user read and update permissions: PERMISSION_READ | PERMISSION_UPDATE
54
+     *
55
+     * Check if permission is granted with And (&) e.g. Check if delete is
56
+     * granted: if ($permissions & PERMISSION_DELETE)
57
+     *
58
+     * Remove permissions with And (&) and Not (~) e.g. Remove the update
59
+     * permission: $permissions &= ~PERMISSION_UPDATE
60
+     *
61
+     * Apps are required to handle permissions on their own, this class only
62
+     * stores and manages the permissions of shares
63
+     * @see lib/public/constants.php
64
+     */
65
+
66
+    /**
67
+     * Register a sharing backend class that implements OCP\Share_Backend for an item type
68
+     * @param string $itemType Item type
69
+     * @param string $class Backend class
70
+     * @param string $collectionOf (optional) Depends on item type
71
+     * @param array $supportedFileExtensions (optional) List of supported file extensions if this item type depends on files
72
+     * @return boolean true if backend is registered or false if error
73
+     */
74
+    public static function registerBackend($itemType, $class, $collectionOf = null, $supportedFileExtensions = null) {
75
+        if (\OC::$server->getConfig()->getAppValue('core', 'shareapi_enabled', 'yes') == 'yes') {
76
+            if (!isset(self::$backendTypes[$itemType])) {
77
+                self::$backendTypes[$itemType] = [
78
+                    'class' => $class,
79
+                    'collectionOf' => $collectionOf,
80
+                    'supportedFileExtensions' => $supportedFileExtensions
81
+                ];
82
+                return true;
83
+            }
84
+            \OCP\Util::writeLog('OCP\Share',
85
+                'Sharing backend '.$class.' not registered, '.self::$backendTypes[$itemType]['class']
86
+                .' is already registered for '.$itemType,
87
+                ILogger::WARN);
88
+        }
89
+        return false;
90
+    }
91
+
92
+    /**
93
+     * Get the items of item type shared with the current user
94
+     * @param string $itemType
95
+     * @param int $format (optional) Format type must be defined by the backend
96
+     * @param mixed $parameters (optional)
97
+     * @param int $limit Number of items to return (optional) Returns all by default
98
+     * @param boolean $includeCollections (optional)
99
+     * @return mixed Return depends on format
100
+     * @deprecated TESTS ONLY - this methods is only used by tests
101
+     * called like this:
102
+     * \OC\Share\Share::getItemsSharedWith('folder'); (apps/files_sharing/tests/UpdaterTest.php)
103
+     */
104
+    public static function getItemsSharedWith() {
105
+        return self::getItems('folder', null, self::$shareTypeUserAndGroups, \OC_User::getUser());
106
+    }
107
+
108
+    /**
109
+     * Get the items of item type shared with a user
110
+     * @param string $itemType
111
+     * @param string $user id for which user we want the shares
112
+     * @param int $format (optional) Format type must be defined by the backend
113
+     * @param mixed $parameters (optional)
114
+     * @param int $limit Number of items to return (optional) Returns all by default
115
+     * @param boolean $includeCollections (optional)
116
+     * @return mixed Return depends on format
117
+     * @deprecated TESTS ONLY - this methods is only used by tests
118
+     * called like this:
119
+     * \OC\Share\Share::getItemsSharedWithUser('test', $shareWith); (tests/lib/Share/Backend.php)
120
+     */
121
+    public static function getItemsSharedWithUser($itemType, $user) {
122
+        return self::getItems('test', null, self::$shareTypeUserAndGroups, $user);
123
+    }
124
+
125
+    /**
126
+     * Get the item of item type shared with a given user by source
127
+     * @param string $itemType
128
+     * @param string $itemSource
129
+     * @param string $user User to whom the item was shared
130
+     * @param string $owner Owner of the share
131
+     * @param int $shareType only look for a specific share type
132
+     * @return array Return list of items with file_target, permissions and expiration
133
+     */
134
+    public static function getItemSharedWithUser($itemType, $itemSource, $user, $owner = null, $shareType = null) {
135
+        $shares = [];
136
+        $fileDependent = false;
137
+
138
+        $where = 'WHERE';
139
+        $fileDependentWhere = '';
140
+        if ($itemType === 'file' || $itemType === 'folder') {
141
+            $fileDependent = true;
142
+            $column = 'file_source';
143
+            $fileDependentWhere = 'INNER JOIN `*PREFIX*filecache` ON `file_source` = `*PREFIX*filecache`.`fileid` ';
144
+            $fileDependentWhere .= 'INNER JOIN `*PREFIX*storages` ON `numeric_id` = `*PREFIX*filecache`.`storage` ';
145
+        } else {
146
+            $column = 'item_source';
147
+        }
148
+
149
+        $select = self::createSelectStatement(self::FORMAT_NONE, $fileDependent);
150
+
151
+        $where .= ' `' . $column . '` = ? AND `item_type` = ? ';
152
+        $arguments = [$itemSource, $itemType];
153
+        // for link shares $user === null
154
+        if ($user !== null) {
155
+            $where .= ' AND `share_with` = ? ';
156
+            $arguments[] = $user;
157
+        }
158
+
159
+        if ($shareType !== null) {
160
+            $where .= ' AND `share_type` = ? ';
161
+            $arguments[] = $shareType;
162
+        }
163
+
164
+        if ($owner !== null) {
165
+            $where .= ' AND `uid_owner` = ? ';
166
+            $arguments[] = $owner;
167
+        }
168
+
169
+        $query = \OC_DB::prepare('SELECT ' . $select . ' FROM `*PREFIX*share` '. $fileDependentWhere . $where);
170
+
171
+        $result = \OC_DB::executeAudited($query, $arguments);
172
+
173
+        while ($row = $result->fetchRow()) {
174
+            if ($fileDependent && !self::isFileReachable($row['path'], $row['storage_id'])) {
175
+                continue;
176
+            }
177
+            if ($fileDependent && (int)$row['file_parent'] === -1) {
178
+                // if it is a mount point we need to get the path from the mount manager
179
+                $mountManager = \OC\Files\Filesystem::getMountManager();
180
+                $mountPoint = $mountManager->findByStorageId($row['storage_id']);
181
+                if (!empty($mountPoint)) {
182
+                    $path = $mountPoint[0]->getMountPoint();
183
+                    $path = trim($path, '/');
184
+                    $path = substr($path, strlen($owner) + 1); //normalize path to 'files/foo.txt`
185
+                    $row['path'] = $path;
186
+                } else {
187
+                    \OC::$server->getLogger()->warning(
188
+                        'Could not resolve mount point for ' . $row['storage_id'],
189
+                        ['app' => 'OCP\Share']
190
+                    );
191
+                }
192
+            }
193
+            $shares[] = $row;
194
+        }
195
+        $result->closeCursor();
196
+
197
+        //if didn't found a result than let's look for a group share.
198
+        if (empty($shares) && $user !== null) {
199
+            $userObject = \OC::$server->getUserManager()->get($user);
200
+            $groups = [];
201
+            if ($userObject) {
202
+                $groups = \OC::$server->getGroupManager()->getUserGroupIds($userObject);
203
+            }
204
+
205
+            if (!empty($groups)) {
206
+                $where = $fileDependentWhere . ' WHERE `' . $column . '` = ? AND `item_type` = ? AND `share_with` in (?)';
207
+                $arguments = [$itemSource, $itemType, $groups];
208
+                $types = [null, null, IQueryBuilder::PARAM_STR_ARRAY];
209
+
210
+                if ($owner !== null) {
211
+                    $where .= ' AND `uid_owner` = ?';
212
+                    $arguments[] = $owner;
213
+                    $types[] = null;
214
+                }
215
+
216
+                // TODO: inject connection, hopefully one day in the future when this
217
+                // class isn't static anymore...
218
+                $conn = \OC::$server->getDatabaseConnection();
219
+                $result = $conn->executeQuery(
220
+                    'SELECT ' . $select . ' FROM `*PREFIX*share` ' . $where,
221
+                    $arguments,
222
+                    $types
223
+                );
224
+
225
+                while ($row = $result->fetch()) {
226
+                    $shares[] = $row;
227
+                }
228
+            }
229
+        }
230
+
231
+        return $shares;
232
+    }
233
+
234
+    /**
235
+     * Get the shared item of item type owned by the current user
236
+     * @param string $itemType
237
+     * @param string $itemSource
238
+     * @param int $format (optional) Format type must be defined by the backend
239
+     * @param mixed $parameters
240
+     * @param boolean $includeCollections
241
+     * @return mixed Return depends on format
242
+     *
243
+     * Refactoring notes:
244
+     *   * defacto $parameters and $format is always the default and therefore is removed in the subsequent call
245
+     */
246
+    public static function getItemShared($itemType, $itemSource, $format = self::FORMAT_NONE,
247
+                                            $parameters = null, $includeCollections = false) {
248
+        return self::getItems($itemType, $itemSource, null, null, \OC_User::getUser(), self::FORMAT_NONE,
249
+            null, -1, $includeCollections);
250
+    }
251
+
252
+    /**
253
+     * Get the backend class for the specified item type
254
+     * @param string $itemType
255
+     * @throws \Exception
256
+     * @return \OCP\Share_Backend
257
+     */
258
+    public static function getBackend($itemType) {
259
+        $l = \OC::$server->getL10N('lib');
260
+        if (isset(self::$backends[$itemType])) {
261
+            return self::$backends[$itemType];
262
+        } elseif (isset(self::$backendTypes[$itemType]['class'])) {
263
+            $class = self::$backendTypes[$itemType]['class'];
264
+            if (class_exists($class)) {
265
+                self::$backends[$itemType] = new $class;
266
+                if (!(self::$backends[$itemType] instanceof \OCP\Share_Backend)) {
267
+                    $message = 'Sharing backend %s must implement the interface OCP\Share_Backend';
268
+                    $message_t = $l->t('Sharing backend %s must implement the interface OCP\Share_Backend', [$class]);
269
+                    \OCP\Util::writeLog('OCP\Share', sprintf($message, $class), ILogger::ERROR);
270
+                    throw new \Exception($message_t);
271
+                }
272
+                return self::$backends[$itemType];
273
+            } else {
274
+                $message = 'Sharing backend %s not found';
275
+                $message_t = $l->t('Sharing backend %s not found', [$class]);
276
+                \OCP\Util::writeLog('OCP\Share', sprintf($message, $class), ILogger::ERROR);
277
+                throw new \Exception($message_t);
278
+            }
279
+        }
280
+        $message = 'Sharing backend for %s not found';
281
+        $message_t = $l->t('Sharing backend for %s not found', [$itemType]);
282
+        \OCP\Util::writeLog('OCP\Share', sprintf($message, $itemType), ILogger::ERROR);
283
+        throw new \Exception($message_t);
284
+    }
285
+
286
+    /**
287
+     * Check if resharing is allowed
288
+     * @return boolean true if allowed or false
289
+     *
290
+     * Resharing is allowed by default if not configured
291
+     */
292
+    public static function isResharingAllowed() {
293
+        if (!isset(self::$isResharingAllowed)) {
294
+            if (\OC::$server->getConfig()->getAppValue('core', 'shareapi_allow_resharing', 'yes') == 'yes') {
295
+                self::$isResharingAllowed = true;
296
+            } else {
297
+                self::$isResharingAllowed = false;
298
+            }
299
+        }
300
+        return self::$isResharingAllowed;
301
+    }
302
+
303
+    /**
304
+     * Get a list of collection item types for the specified item type
305
+     * @param string $itemType
306
+     * @return array
307
+     */
308
+    private static function getCollectionItemTypes($itemType) {
309
+        $collectionTypes = [$itemType];
310
+        foreach (self::$backendTypes as $type => $backend) {
311
+            if (in_array($backend['collectionOf'], $collectionTypes)) {
312
+                $collectionTypes[] = $type;
313
+            }
314
+        }
315
+        // TODO Add option for collections to be collection of themselves, only 'folder' does it now...
316
+        if (isset(self::$backendTypes[$itemType]) && (!self::getBackend($itemType) instanceof \OCP\Share_Backend_Collection || $itemType != 'folder')) {
317
+            unset($collectionTypes[0]);
318
+        }
319
+        // Return array if collections were found or the item type is a
320
+        // collection itself - collections can be inside collections
321
+        if (count($collectionTypes) > 0) {
322
+            return $collectionTypes;
323
+        }
324
+        return false;
325
+    }
326
+
327
+    /**
328
+     * Get shared items from the database
329
+     * @param string $itemType
330
+     * @param string $item Item source or target (optional)
331
+     * @param int $shareType SHARE_TYPE_USER, SHARE_TYPE_GROUP, SHARE_TYPE_LINK, $shareTypeUserAndGroups, or $shareTypeGroupUserUnique
332
+     * @param string $shareWith User or group the item is being shared with
333
+     * @param string $uidOwner User that is the owner of shared items (optional)
334
+     * @param int $format Format to convert items to with formatItems() (optional)
335
+     * @param mixed $parameters to pass to formatItems() (optional)
336
+     * @param int $limit Number of items to return, -1 to return all matches (optional)
337
+     * @param boolean $includeCollections Include collection item types (optional)
338
+     * @param boolean $itemShareWithBySource (optional)
339
+     * @param boolean $checkExpireDate
340
+     * @return array
341
+     *
342
+     * See public functions getItem(s)... for parameter usage
343
+     *
344
+     * Refactoring notes:
345
+     *   * defacto $limit, $itemsShareWithBySource, $checkExpireDate, $parameters and $format is always the default and therefore is removed in the subsequent call
346
+     */
347
+    public static function getItems($itemType, $item = null, $shareType = null, $shareWith = null,
348
+                                    $uidOwner = null, $format = self::FORMAT_NONE, $parameters = null, $limit = -1,
349
+                                    $includeCollections = false, $itemShareWithBySource = false, $checkExpireDate = true) {
350
+        if (\OC::$server->getConfig()->getAppValue('core', 'shareapi_enabled', 'yes') != 'yes') {
351
+            return [];
352
+        }
353
+        $backend = self::getBackend($itemType);
354
+        $collectionTypes = false;
355
+        // Get filesystem root to add it to the file target and remove from the
356
+        // file source, match file_source with the file cache
357
+        if ($itemType == 'file' || $itemType == 'folder') {
358
+            if (!is_null($uidOwner)) {
359
+                $root = \OC\Files\Filesystem::getRoot();
360
+            } else {
361
+                $root = '';
362
+            }
363
+            $where = 'INNER JOIN `*PREFIX*filecache` ON `file_source` = `*PREFIX*filecache`.`fileid` ';
364
+            if (!isset($item)) {
365
+                $where .= ' AND `file_target` IS NOT NULL ';
366
+            }
367
+            $where .= 'INNER JOIN `*PREFIX*storages` ON `numeric_id` = `*PREFIX*filecache`.`storage` ';
368
+            $fileDependent = true;
369
+            $queryArgs = [];
370
+        } else {
371
+            $fileDependent = false;
372
+            $root = '';
373
+            $collectionTypes = self::getCollectionItemTypes($itemType);
374
+            if ($includeCollections && !isset($item) && $collectionTypes) {
375
+                // If includeCollections is true, find collections of this item type, e.g. a music album contains songs
376
+                if (!in_array($itemType, $collectionTypes)) {
377
+                    $itemTypes = array_merge([$itemType], $collectionTypes);
378
+                } else {
379
+                    $itemTypes = $collectionTypes;
380
+                }
381
+                $placeholders = implode(',', array_fill(0, count($itemTypes), '?'));
382
+                $where = ' WHERE `item_type` IN ('.$placeholders.'))';
383
+                $queryArgs = $itemTypes;
384
+            } else {
385
+                $where = ' WHERE `item_type` = ?';
386
+                $queryArgs = [$itemType];
387
+            }
388
+        }
389
+        if (\OC::$server->getConfig()->getAppValue('core', 'shareapi_allow_links', 'yes') !== 'yes') {
390
+            $where .= ' AND `share_type` != ?';
391
+            $queryArgs[] = IShare::TYPE_LINK;
392
+        }
393
+        if (isset($shareType)) {
394
+            // Include all user and group items
395
+            if ($shareType == self::$shareTypeUserAndGroups && isset($shareWith)) {
396
+                $where .= ' AND ((`share_type` in (?, ?) AND `share_with` = ?) ';
397
+                $queryArgs[] = IShare::TYPE_USER;
398
+                $queryArgs[] = self::$shareTypeGroupUserUnique;
399
+                $queryArgs[] = $shareWith;
400
+
401
+                $user = \OC::$server->getUserManager()->get($shareWith);
402
+                $groups = [];
403
+                if ($user) {
404
+                    $groups = \OC::$server->getGroupManager()->getUserGroupIds($user);
405
+                }
406
+                if (!empty($groups)) {
407
+                    $placeholders = implode(',', array_fill(0, count($groups), '?'));
408
+                    $where .= ' OR (`share_type` = ? AND `share_with` IN ('.$placeholders.')) ';
409
+                    $queryArgs[] = IShare::TYPE_GROUP;
410
+                    $queryArgs = array_merge($queryArgs, $groups);
411
+                }
412
+                $where .= ')';
413
+                // Don't include own group shares
414
+                $where .= ' AND `uid_owner` != ?';
415
+                $queryArgs[] = $shareWith;
416
+            } else {
417
+                $where .= ' AND `share_type` = ?';
418
+                $queryArgs[] = $shareType;
419
+                if (isset($shareWith)) {
420
+                    $where .= ' AND `share_with` = ?';
421
+                    $queryArgs[] = $shareWith;
422
+                }
423
+            }
424
+        }
425
+        if (isset($uidOwner)) {
426
+            $where .= ' AND `uid_owner` = ?';
427
+            $queryArgs[] = $uidOwner;
428
+            if (!isset($shareType)) {
429
+                // Prevent unique user targets for group shares from being selected
430
+                $where .= ' AND `share_type` != ?';
431
+                $queryArgs[] = self::$shareTypeGroupUserUnique;
432
+            }
433
+            if ($fileDependent) {
434
+                $column = 'file_source';
435
+            } else {
436
+                $column = 'item_source';
437
+            }
438
+        } else {
439
+            if ($fileDependent) {
440
+                $column = 'file_target';
441
+            } else {
442
+                $column = 'item_target';
443
+            }
444
+        }
445
+        if (isset($item)) {
446
+            $collectionTypes = self::getCollectionItemTypes($itemType);
447
+            if ($includeCollections && $collectionTypes && !in_array('folder', $collectionTypes)) {
448
+                $where .= ' AND (';
449
+            } else {
450
+                $where .= ' AND';
451
+            }
452
+            // If looking for own shared items, check item_source else check item_target
453
+            if (isset($uidOwner)) {
454
+                // If item type is a file, file source needs to be checked in case the item was converted
455
+                if ($fileDependent) {
456
+                    $where .= ' `file_source` = ?';
457
+                    $column = 'file_source';
458
+                } else {
459
+                    $where .= ' `item_source` = ?';
460
+                    $column = 'item_source';
461
+                }
462
+            } else {
463
+                if ($fileDependent) {
464
+                    $where .= ' `file_target` = ?';
465
+                    $item = \OC\Files\Filesystem::normalizePath($item);
466
+                } else {
467
+                    $where .= ' `item_target` = ?';
468
+                }
469
+            }
470
+            $queryArgs[] = $item;
471
+            if ($includeCollections && $collectionTypes && !in_array('folder', $collectionTypes)) {
472
+                $placeholders = implode(',', array_fill(0, count($collectionTypes), '?'));
473
+                $where .= ' OR `item_type` IN ('.$placeholders.'))';
474
+                $queryArgs = array_merge($queryArgs, $collectionTypes);
475
+            }
476
+        }
477
+
478
+        $where .= ' ORDER BY `*PREFIX*share`.`id` ASC';
479
+
480
+        $queryLimit = null;
481
+        $select = self::createSelectStatement(self::FORMAT_NONE, $fileDependent, $uidOwner);
482
+        $root = strlen($root);
483
+        $query = \OC_DB::prepare('SELECT '.$select.' FROM `*PREFIX*share` '.$where, $queryLimit);
484
+        $result = $query->execute($queryArgs);
485
+        if ($result === false) {
486
+            \OCP\Util::writeLog('OCP\Share',
487
+                \OC_DB::getErrorMessage() . ', select=' . $select . ' where=',
488
+                ILogger::ERROR);
489
+        }
490
+        $items = [];
491
+        $targets = [];
492
+        $switchedItems = [];
493
+        $mounts = [];
494
+        while ($row = $result->fetchRow()) {
495
+            self::transformDBResults($row);
496
+            // Filter out duplicate group shares for users with unique targets
497
+            if ($fileDependent && !self::isFileReachable($row['path'], $row['storage_id'])) {
498
+                continue;
499
+            }
500
+            if ($row['share_type'] == self::$shareTypeGroupUserUnique && isset($items[$row['parent']])) {
501
+                $row['share_type'] = IShare::TYPE_GROUP;
502
+                $row['unique_name'] = true; // remember that we use a unique name for this user
503
+                $row['share_with'] = $items[$row['parent']]['share_with'];
504
+                // if the group share was unshared from the user we keep the permission, otherwise
505
+                // we take the permission from the parent because this is always the up-to-date
506
+                // permission for the group share
507
+                if ($row['permissions'] > 0) {
508
+                    $row['permissions'] = $items[$row['parent']]['permissions'];
509
+                }
510
+                // Remove the parent group share
511
+                unset($items[$row['parent']]);
512
+                if ($row['permissions'] == 0) {
513
+                    continue;
514
+                }
515
+            } elseif (!isset($uidOwner)) {
516
+                // Check if the same target already exists
517
+                if (isset($targets[$row['id']])) {
518
+                    // Check if the same owner shared with the user twice
519
+                    // through a group and user share - this is allowed
520
+                    $id = $targets[$row['id']];
521
+                    if (isset($items[$id]) && $items[$id]['uid_owner'] == $row['uid_owner']) {
522
+                        // Switch to group share type to ensure resharing conditions aren't bypassed
523
+                        if ($items[$id]['share_type'] != IShare::TYPE_GROUP) {
524
+                            $items[$id]['share_type'] = IShare::TYPE_GROUP;
525
+                            $items[$id]['share_with'] = $row['share_with'];
526
+                        }
527
+                        // Switch ids if sharing permission is granted on only
528
+                        // one share to ensure correct parent is used if resharing
529
+                        if (~(int)$items[$id]['permissions'] & \OCP\Constants::PERMISSION_SHARE
530
+                            && (int)$row['permissions'] & \OCP\Constants::PERMISSION_SHARE) {
531
+                            $items[$row['id']] = $items[$id];
532
+                            $switchedItems[$id] = $row['id'];
533
+                            unset($items[$id]);
534
+                            $id = $row['id'];
535
+                        }
536
+                        $items[$id]['permissions'] |= (int)$row['permissions'];
537
+                    }
538
+                    continue;
539
+                } elseif (!empty($row['parent'])) {
540
+                    $targets[$row['parent']] = $row['id'];
541
+                }
542
+            }
543
+            // Remove root from file source paths if retrieving own shared items
544
+            if (isset($uidOwner) && isset($row['path'])) {
545
+                if (isset($row['parent'])) {
546
+                    $query = \OC::$server->getDatabaseConnection()->getQueryBuilder();
547
+                    $query->select('file_target')
548
+                        ->from('share')
549
+                        ->where($query->expr()->eq('id', $query->createNamedParameter($row['parent'])));
550
+
551
+                    $parentResult = $query->execute();
552
+                    $parentRow = $parentResult->fetch();
553
+                    $parentResult->closeCursor();
554
+
555
+                    if ($parentRow === false) {
556
+                        \OCP\Util::writeLog('OCP\Share', 'Can\'t select parent: ' .
557
+                            \OC_DB::getErrorMessage() . ', select=' . $select . ' where=' . $where,
558
+                            ILogger::ERROR);
559
+                    } else {
560
+                        $tmpPath = $parentRow['file_target'];
561
+                        // find the right position where the row path continues from the target path
562
+                        $pos = strrpos($row['path'], $parentRow['file_target']);
563
+                        $subPath = substr($row['path'], $pos);
564
+                        $splitPath = explode('/', $subPath);
565
+                        foreach (array_slice($splitPath, 2) as $pathPart) {
566
+                            $tmpPath = $tmpPath . '/' . $pathPart;
567
+                        }
568
+                        $row['path'] = $tmpPath;
569
+                    }
570
+                } else {
571
+                    if (!isset($mounts[$row['storage']])) {
572
+                        $mountPoints = \OC\Files\Filesystem::getMountByNumericId($row['storage']);
573
+                        if (is_array($mountPoints) && !empty($mountPoints)) {
574
+                            $mounts[$row['storage']] = current($mountPoints);
575
+                        }
576
+                    }
577
+                    if (!empty($mounts[$row['storage']])) {
578
+                        $path = $mounts[$row['storage']]->getMountPoint().$row['path'];
579
+                        $relPath = substr($path, $root); // path relative to data/user
580
+                        $row['path'] = rtrim($relPath, '/');
581
+                    }
582
+                }
583
+            }
584
+
585
+            // Check if resharing is allowed, if not remove share permission
586
+            if (isset($row['permissions']) && (!self::isResharingAllowed() | \OCP\Util::isSharingDisabledForUser())) {
587
+                $row['permissions'] &= ~\OCP\Constants::PERMISSION_SHARE;
588
+            }
589
+            // Add display names to result
590
+            $row['share_with_displayname'] = $row['share_with'];
591
+            if (isset($row['share_with']) && $row['share_with'] != '' &&
592
+                $row['share_type'] === IShare::TYPE_USER) {
593
+                $shareWithUser = \OC::$server->getUserManager()->get($row['share_with']);
594
+                $row['share_with_displayname'] = $shareWithUser === null ? $row['share_with'] : $shareWithUser->getDisplayName();
595
+            } elseif (isset($row['share_with']) && $row['share_with'] != '' &&
596
+                $row['share_type'] === IShare::TYPE_REMOTE) {
597
+                $addressBookEntries = \OC::$server->getContactsManager()->search($row['share_with'], ['CLOUD']);
598
+                foreach ($addressBookEntries as $entry) {
599
+                    foreach ($entry['CLOUD'] as $cloudID) {
600
+                        if ($cloudID === $row['share_with']) {
601
+                            $row['share_with_displayname'] = $entry['FN'];
602
+                        }
603
+                    }
604
+                }
605
+            }
606
+            if (isset($row['uid_owner']) && $row['uid_owner'] != '') {
607
+                $ownerUser = \OC::$server->getUserManager()->get($row['uid_owner']);
608
+                $row['displayname_owner'] = $ownerUser === null ? $row['uid_owner'] : $ownerUser->getDisplayName();
609
+            }
610
+
611
+            if ($row['permissions'] > 0) {
612
+                $items[$row['id']] = $row;
613
+            }
614
+        }
615
+
616
+        // group items if we are looking for items shared with the current user
617
+        if (isset($shareWith) && $shareWith === \OC_User::getUser()) {
618
+            $items = self::groupItems($items, $itemType);
619
+        }
620
+
621
+        if (!empty($items)) {
622
+            $collectionItems = [];
623
+            foreach ($items as &$row) {
624
+                // Check if this is a collection of the requested item type
625
+                if ($includeCollections && $collectionTypes && $row['item_type'] !== 'folder' && in_array($row['item_type'], $collectionTypes)) {
626
+                    if (($collectionBackend = self::getBackend($row['item_type']))
627
+                        && $collectionBackend instanceof \OCP\Share_Backend_Collection) {
628
+                        // Collections can be inside collections, check if the item is a collection
629
+                        if (isset($item) && $row['item_type'] == $itemType && $row[$column] == $item) {
630
+                            $collectionItems[] = $row;
631
+                        } else {
632
+                            $collection = [];
633
+                            $collection['item_type'] = $row['item_type'];
634
+                            if ($row['item_type'] == 'file' || $row['item_type'] == 'folder') {
635
+                                $collection['path'] = basename($row['path']);
636
+                            }
637
+                            $row['collection'] = $collection;
638
+                            // Fetch all of the children sources
639
+                            $children = $collectionBackend->getChildren($row[$column]);
640
+                            foreach ($children as $child) {
641
+                                $childItem = $row;
642
+                                $childItem['item_type'] = $itemType;
643
+                                if ($row['item_type'] != 'file' && $row['item_type'] != 'folder') {
644
+                                    $childItem['item_source'] = $child['source'];
645
+                                    $childItem['item_target'] = $child['target'];
646
+                                }
647
+                                if ($backend instanceof \OCP\Share_Backend_File_Dependent) {
648
+                                    if ($row['item_type'] == 'file' || $row['item_type'] == 'folder') {
649
+                                        $childItem['file_source'] = $child['source'];
650
+                                    } else { // TODO is this really needed if we already know that we use the file backend?
651
+                                        $meta = \OC\Files\Filesystem::getFileInfo($child['file_path']);
652
+                                        $childItem['file_source'] = $meta['fileid'];
653
+                                    }
654
+                                    $childItem['file_target'] =
655
+                                        \OC\Files\Filesystem::normalizePath($child['file_path']);
656
+                                }
657
+                                if (isset($item)) {
658
+                                    if ($childItem[$column] == $item) {
659
+                                        $collectionItems[] = $childItem;
660
+                                    }
661
+                                } else {
662
+                                    $collectionItems[] = $childItem;
663
+                                }
664
+                            }
665
+                        }
666
+                    }
667
+                    // Remove collection item
668
+                    $toRemove = $row['id'];
669
+                    if (array_key_exists($toRemove, $switchedItems)) {
670
+                        $toRemove = $switchedItems[$toRemove];
671
+                    }
672
+                    unset($items[$toRemove]);
673
+                } elseif ($includeCollections && $collectionTypes && in_array($row['item_type'], $collectionTypes)) {
674
+                    // FIXME: Thats a dirty hack to improve file sharing performance,
675
+                    // see github issue #10588 for more details
676
+                    // Need to find a solution which works for all back-ends
677
+                    $collectionBackend = self::getBackend($row['item_type']);
678
+                    $sharedParents = $collectionBackend->getParents($row['item_source']);
679
+                    foreach ($sharedParents as $parent) {
680
+                        $collectionItems[] = $parent;
681
+                    }
682
+                }
683
+            }
684
+            if (!empty($collectionItems)) {
685
+                $collectionItems = array_unique($collectionItems, SORT_REGULAR);
686
+                $items = array_merge($items, $collectionItems);
687
+            }
688
+
689
+            // filter out invalid items, these can appear when subshare entries exist
690
+            // for a group in which the requested user isn't a member any more
691
+            $items = array_filter($items, function ($item) {
692
+                return $item['share_type'] !== self::$shareTypeGroupUserUnique;
693
+            });
694
+
695
+            return self::formatResult($items, $column, $backend);
696
+        } elseif ($includeCollections && $collectionTypes && in_array('folder', $collectionTypes)) {
697
+            // FIXME: Thats a dirty hack to improve file sharing performance,
698
+            // see github issue #10588 for more details
699
+            // Need to find a solution which works for all back-ends
700
+            $collectionItems = [];
701
+            $collectionBackend = self::getBackend('folder');
702
+            $sharedParents = $collectionBackend->getParents($item, $shareWith, $uidOwner);
703
+            foreach ($sharedParents as $parent) {
704
+                $collectionItems[] = $parent;
705
+            }
706
+            return self::formatResult($collectionItems, $column, $backend);
707
+        }
708
+
709
+        return [];
710
+    }
711
+
712
+    /**
713
+     * group items with link to the same source
714
+     *
715
+     * @param array $items
716
+     * @param string $itemType
717
+     * @return array of grouped items
718
+     */
719
+    protected static function groupItems($items, $itemType) {
720
+        $fileSharing = $itemType === 'file' || $itemType === 'folder';
721
+
722
+        $result = [];
723
+
724
+        foreach ($items as $item) {
725
+            $grouped = false;
726
+            foreach ($result as $key => $r) {
727
+                // for file/folder shares we need to compare file_source, otherwise we compare item_source
728
+                // only group shares if they already point to the same target, otherwise the file where shared
729
+                // before grouping of shares was added. In this case we don't group them toi avoid confusions
730
+                if (($fileSharing && $item['file_source'] === $r['file_source'] && $item['file_target'] === $r['file_target']) ||
731
+                    (!$fileSharing && $item['item_source'] === $r['item_source'] && $item['item_target'] === $r['item_target'])) {
732
+                    // add the first item to the list of grouped shares
733
+                    if (!isset($result[$key]['grouped'])) {
734
+                        $result[$key]['grouped'][] = $result[$key];
735
+                    }
736
+                    $result[$key]['permissions'] = (int) $item['permissions'] | (int) $r['permissions'];
737
+                    $result[$key]['grouped'][] = $item;
738
+                    $grouped = true;
739
+                    break;
740
+                }
741
+            }
742
+
743
+            if (!$grouped) {
744
+                $result[] = $item;
745
+            }
746
+        }
747
+
748
+        return $result;
749
+    }
750
+
751
+    /**
752
+     * construct select statement
753
+     * @param int $format
754
+     * @param boolean $fileDependent ist it a file/folder share or a generla share
755
+     * @param string $uidOwner
756
+     * @return string select statement
757
+     */
758
+    private static function createSelectStatement($format, $fileDependent, $uidOwner = null) {
759
+        $select = '*';
760
+        if ($format == self::FORMAT_STATUSES) {
761
+            if ($fileDependent) {
762
+                $select = '`*PREFIX*share`.`id`, `*PREFIX*share`.`parent`, `share_type`, `path`, `storage`, '
763
+                    . '`share_with`, `uid_owner` , `file_source`, `stime`, `*PREFIX*share`.`permissions`, '
764
+                    . '`*PREFIX*storages`.`id` AS `storage_id`, `*PREFIX*filecache`.`parent` as `file_parent`, '
765
+                    . '`uid_initiator`';
766
+            } else {
767
+                $select = '`id`, `parent`, `share_type`, `share_with`, `uid_owner`, `item_source`, `stime`, `*PREFIX*share`.`permissions`';
768
+            }
769
+        } else {
770
+            if (isset($uidOwner)) {
771
+                if ($fileDependent) {
772
+                    $select = '`*PREFIX*share`.`id`, `item_type`, `item_source`, `*PREFIX*share`.`parent`,'
773
+                        . ' `share_type`, `share_with`, `file_source`, `file_target`, `path`, `*PREFIX*share`.`permissions`, `stime`,'
774
+                        . ' `expiration`, `token`, `storage`, `mail_send`, `uid_owner`, '
775
+                        . '`*PREFIX*storages`.`id` AS `storage_id`, `*PREFIX*filecache`.`parent` as `file_parent`';
776
+                } else {
777
+                    $select = '`id`, `item_type`, `item_source`, `parent`, `share_type`, `share_with`, `*PREFIX*share`.`permissions`,'
778
+                        . ' `stime`, `file_source`, `expiration`, `token`, `mail_send`, `uid_owner`';
779
+                }
780
+            } else {
781
+                if ($fileDependent) {
782
+                    if ($format == \OCA\Files_Sharing\ShareBackend\File::FORMAT_GET_FOLDER_CONTENTS || $format == \OCA\Files_Sharing\ShareBackend\File::FORMAT_FILE_APP_ROOT) {
783
+                        $select = '`*PREFIX*share`.`id`, `item_type`, `item_source`, `*PREFIX*share`.`parent`, `uid_owner`, '
784
+                            . '`share_type`, `share_with`, `file_source`, `path`, `file_target`, `stime`, '
785
+                            . '`*PREFIX*share`.`permissions`, `expiration`, `storage`, `*PREFIX*filecache`.`parent` as `file_parent`, '
786
+                            . '`name`, `mtime`, `mimetype`, `mimepart`, `size`, `encrypted`, `etag`, `mail_send`';
787
+                    } else {
788
+                        $select = '`*PREFIX*share`.`id`, `item_type`, `item_source`, `item_target`,'
789
+                            . '`*PREFIX*share`.`parent`, `share_type`, `share_with`, `uid_owner`,'
790
+                            . '`file_source`, `path`, `file_target`, `*PREFIX*share`.`permissions`,'
791
+                            . '`stime`, `expiration`, `token`, `storage`, `mail_send`,'
792
+                            . '`*PREFIX*storages`.`id` AS `storage_id`, `*PREFIX*filecache`.`parent` as `file_parent`';
793
+                    }
794
+                }
795
+            }
796
+        }
797
+        return $select;
798
+    }
799
+
800
+
801
+    /**
802
+     * transform db results
803
+     * @param array $row result
804
+     */
805
+    private static function transformDBResults(&$row) {
806
+        if (isset($row['id'])) {
807
+            $row['id'] = (int) $row['id'];
808
+        }
809
+        if (isset($row['share_type'])) {
810
+            $row['share_type'] = (int) $row['share_type'];
811
+        }
812
+        if (isset($row['parent'])) {
813
+            $row['parent'] = (int) $row['parent'];
814
+        }
815
+        if (isset($row['file_parent'])) {
816
+            $row['file_parent'] = (int) $row['file_parent'];
817
+        }
818
+        if (isset($row['file_source'])) {
819
+            $row['file_source'] = (int) $row['file_source'];
820
+        }
821
+        if (isset($row['permissions'])) {
822
+            $row['permissions'] = (int) $row['permissions'];
823
+        }
824
+        if (isset($row['storage'])) {
825
+            $row['storage'] = (int) $row['storage'];
826
+        }
827
+        if (isset($row['stime'])) {
828
+            $row['stime'] = (int) $row['stime'];
829
+        }
830
+        if (isset($row['expiration']) && $row['share_type'] !== IShare::TYPE_LINK) {
831
+            // discard expiration date for non-link shares, which might have been
832
+            // set by ancient bugs
833
+            $row['expiration'] = null;
834
+        }
835
+    }
836
+
837
+    /**
838
+     * format result
839
+     * @param array $items result
840
+     * @param string $column is it a file share or a general share ('file_target' or 'item_target')
841
+     * @param \OCP\Share_Backend $backend sharing backend
842
+     * @param int $format
843
+     * @param array $parameters additional format parameters
844
+     * @return array format result
845
+     */
846
+    private static function formatResult($items, $column, $backend, $format = self::FORMAT_NONE , $parameters = null) {
847
+        if ($format === self::FORMAT_NONE) {
848
+            return $items;
849
+        } elseif ($format === self::FORMAT_STATUSES) {
850
+            $statuses = [];
851
+            foreach ($items as $item) {
852
+                if ($item['share_type'] === IShare::TYPE_LINK) {
853
+                    if ($item['uid_initiator'] !== \OC::$server->getUserSession()->getUser()->getUID()) {
854
+                        continue;
855
+                    }
856
+                    $statuses[$item[$column]]['link'] = true;
857
+                } elseif (!isset($statuses[$item[$column]])) {
858
+                    $statuses[$item[$column]]['link'] = false;
859
+                }
860
+                if (!empty($item['file_target'])) {
861
+                    $statuses[$item[$column]]['path'] = $item['path'];
862
+                }
863
+            }
864
+            return $statuses;
865
+        } else {
866
+            return $backend->formatItems($items, $format, $parameters);
867
+        }
868
+    }
869
+
870
+    /**
871
+     * remove protocol from URL
872
+     *
873
+     * @param string $url
874
+     * @return string
875
+     */
876
+    public static function removeProtocolFromUrl($url) {
877
+        if (strpos($url, 'https://') === 0) {
878
+            return substr($url, strlen('https://'));
879
+        } elseif (strpos($url, 'http://') === 0) {
880
+            return substr($url, strlen('http://'));
881
+        }
882
+
883
+        return $url;
884
+    }
885
+
886
+
887
+    /**
888
+     * @return int
889
+     */
890
+    public static function getExpireInterval() {
891
+        return (int)\OC::$server->getConfig()->getAppValue('core', 'shareapi_expire_after_n_days', '7');
892
+    }
893
+
894
+    /**
895
+     * Checks whether the given path is reachable for the given owner
896
+     *
897
+     * @param string $path path relative to files
898
+     * @param string $ownerStorageId storage id of the owner
899
+     *
900
+     * @return boolean true if file is reachable, false otherwise
901
+     */
902
+    private static function isFileReachable($path, $ownerStorageId) {
903
+        // if outside the home storage, file is always considered reachable
904
+        if (!(substr($ownerStorageId, 0, 6) === 'home::' ||
905
+            substr($ownerStorageId, 0, 13) === 'object::user:'
906
+        )) {
907
+            return true;
908
+        }
909
+
910
+        // if inside the home storage, the file has to be under "/files/"
911
+        $path = ltrim($path, '/');
912
+        if (substr($path, 0, 6) === 'files/') {
913
+            return true;
914
+        }
915
+
916
+        return false;
917
+    }
918 918
 }
Please login to merge, or discard this patch.