Passed
Push — master ( 927582...173f8a )
by Morris
11:22 queued 17s
created
apps/files_trashbin/lib/Trashbin.php 2 patches
Indentation   +994 added lines, -994 removed lines patch added patch discarded remove patch
@@ -58,998 +58,998 @@
 block discarded – undo
58 58
 
59 59
 class Trashbin {
60 60
 
61
-	// unit: percentage; 50% of available disk space/quota
62
-	public const DEFAULTMAXSIZE = 50;
63
-
64
-	/**
65
-	 * Whether versions have already be rescanned during this PHP request
66
-	 *
67
-	 * @var bool
68
-	 */
69
-	private static $scannedVersions = false;
70
-
71
-	/**
72
-	 * Ensure we don't need to scan the file during the move to trash
73
-	 * by triggering the scan in the pre-hook
74
-	 *
75
-	 * @param array $params
76
-	 */
77
-	public static function ensureFileScannedHook($params) {
78
-		try {
79
-			self::getUidAndFilename($params['path']);
80
-		} catch (NotFoundException $e) {
81
-			// nothing to scan for non existing files
82
-		}
83
-	}
84
-
85
-	/**
86
-	 * get the UID of the owner of the file and the path to the file relative to
87
-	 * owners files folder
88
-	 *
89
-	 * @param string $filename
90
-	 * @return array
91
-	 * @throws \OC\User\NoUserException
92
-	 */
93
-	public static function getUidAndFilename($filename) {
94
-		$uid = Filesystem::getOwner($filename);
95
-		$userManager = \OC::$server->getUserManager();
96
-		// if the user with the UID doesn't exists, e.g. because the UID points
97
-		// to a remote user with a federated cloud ID we use the current logged-in
98
-		// user. We need a valid local user to move the file to the right trash bin
99
-		if (!$userManager->userExists($uid)) {
100
-			$uid = User::getUser();
101
-		}
102
-		if (!$uid) {
103
-			// no owner, usually because of share link from ext storage
104
-			return [null, null];
105
-		}
106
-		Filesystem::initMountPoints($uid);
107
-		if ($uid !== User::getUser()) {
108
-			$info = Filesystem::getFileInfo($filename);
109
-			$ownerView = new View('/' . $uid . '/files');
110
-			try {
111
-				$filename = $ownerView->getPath($info['fileid']);
112
-			} catch (NotFoundException $e) {
113
-				$filename = null;
114
-			}
115
-		}
116
-		return [$uid, $filename];
117
-	}
118
-
119
-	/**
120
-	 * get original location of files for user
121
-	 *
122
-	 * @param string $user
123
-	 * @return array (filename => array (timestamp => original location))
124
-	 */
125
-	public static function getLocations($user) {
126
-		$query = \OC_DB::prepare('SELECT `id`, `timestamp`, `location`'
127
-			. ' FROM `*PREFIX*files_trash` WHERE `user`=?');
128
-		$result = $query->execute([$user]);
129
-		$array = [];
130
-		while ($row = $result->fetchRow()) {
131
-			if (isset($array[$row['id']])) {
132
-				$array[$row['id']][$row['timestamp']] = $row['location'];
133
-			} else {
134
-				$array[$row['id']] = [$row['timestamp'] => $row['location']];
135
-			}
136
-		}
137
-		return $array;
138
-	}
139
-
140
-	/**
141
-	 * get original location of file
142
-	 *
143
-	 * @param string $user
144
-	 * @param string $filename
145
-	 * @param string $timestamp
146
-	 * @return string original location
147
-	 */
148
-	public static function getLocation($user, $filename, $timestamp) {
149
-		$query = \OC_DB::prepare('SELECT `location` FROM `*PREFIX*files_trash`'
150
-			. ' WHERE `user`=? AND `id`=? AND `timestamp`=?');
151
-		$result = $query->execute([$user, $filename, $timestamp])->fetchAll();
152
-		if (isset($result[0]['location'])) {
153
-			return $result[0]['location'];
154
-		} else {
155
-			return false;
156
-		}
157
-	}
158
-
159
-	private static function setUpTrash($user) {
160
-		$view = new View('/' . $user);
161
-		if (!$view->is_dir('files_trashbin')) {
162
-			$view->mkdir('files_trashbin');
163
-		}
164
-		if (!$view->is_dir('files_trashbin/files')) {
165
-			$view->mkdir('files_trashbin/files');
166
-		}
167
-		if (!$view->is_dir('files_trashbin/versions')) {
168
-			$view->mkdir('files_trashbin/versions');
169
-		}
170
-		if (!$view->is_dir('files_trashbin/keys')) {
171
-			$view->mkdir('files_trashbin/keys');
172
-		}
173
-	}
174
-
175
-
176
-	/**
177
-	 * copy file to owners trash
178
-	 *
179
-	 * @param string $sourcePath
180
-	 * @param string $owner
181
-	 * @param string $targetPath
182
-	 * @param $user
183
-	 * @param integer $timestamp
184
-	 */
185
-	private static function copyFilesToUser($sourcePath, $owner, $targetPath, $user, $timestamp) {
186
-		self::setUpTrash($owner);
187
-
188
-		$targetFilename = basename($targetPath);
189
-		$targetLocation = dirname($targetPath);
190
-
191
-		$sourceFilename = basename($sourcePath);
192
-
193
-		$view = new View('/');
194
-
195
-		$target = $user . '/files_trashbin/files/' . $targetFilename . '.d' . $timestamp;
196
-		$source = $owner . '/files_trashbin/files/' . $sourceFilename . '.d' . $timestamp;
197
-		$free = $view->free_space($target);
198
-		$isUnknownOrUnlimitedFreeSpace = $free < 0;
199
-		$isEnoughFreeSpaceLeft = $view->filesize($source) < $free;
200
-		if ($isUnknownOrUnlimitedFreeSpace || $isEnoughFreeSpaceLeft) {
201
-			self::copy_recursive($source, $target, $view);
202
-		}
203
-
204
-
205
-		if ($view->file_exists($target)) {
206
-			$query = \OC_DB::prepare("INSERT INTO `*PREFIX*files_trash` (`id`,`timestamp`,`location`,`user`) VALUES (?,?,?,?)");
207
-			$result = $query->execute([$targetFilename, $timestamp, $targetLocation, $user]);
208
-			if (!$result) {
209
-				\OC::$server->getLogger()->error('trash bin database couldn\'t be updated for the files owner', ['app' => 'files_trashbin']);
210
-			}
211
-		}
212
-	}
213
-
214
-
215
-	/**
216
-	 * move file to the trash bin
217
-	 *
218
-	 * @param string $file_path path to the deleted file/directory relative to the files root directory
219
-	 * @param bool $ownerOnly delete for owner only (if file gets moved out of a shared folder)
220
-	 *
221
-	 * @return bool
222
-	 */
223
-	public static function move2trash($file_path, $ownerOnly = false) {
224
-		// get the user for which the filesystem is setup
225
-		$root = Filesystem::getRoot();
226
-		[, $user] = explode('/', $root);
227
-		[$owner, $ownerPath] = self::getUidAndFilename($file_path);
228
-
229
-		// if no owner found (ex: ext storage + share link), will use the current user's trashbin then
230
-		if (is_null($owner)) {
231
-			$owner = $user;
232
-			$ownerPath = $file_path;
233
-		}
234
-
235
-		$ownerView = new View('/' . $owner);
236
-		// file has been deleted in between
237
-		if (is_null($ownerPath) || $ownerPath === '' || !$ownerView->file_exists('/files/' . $ownerPath)) {
238
-			return true;
239
-		}
240
-
241
-		self::setUpTrash($user);
242
-		if ($owner !== $user) {
243
-			// also setup for owner
244
-			self::setUpTrash($owner);
245
-		}
246
-
247
-		$path_parts = pathinfo($ownerPath);
248
-
249
-		$filename = $path_parts['basename'];
250
-		$location = $path_parts['dirname'];
251
-		/** @var ITimeFactory $timeFactory */
252
-		$timeFactory = \OC::$server->query(ITimeFactory::class);
253
-		$timestamp = $timeFactory->getTime();
254
-
255
-		$lockingProvider = \OC::$server->getLockingProvider();
256
-
257
-		// disable proxy to prevent recursive calls
258
-		$trashPath = '/files_trashbin/files/' . $filename . '.d' . $timestamp;
259
-		$gotLock = false;
260
-
261
-		while (!$gotLock) {
262
-			try {
263
-				/** @var \OC\Files\Storage\Storage $trashStorage */
264
-				[$trashStorage, $trashInternalPath] = $ownerView->resolvePath($trashPath);
265
-
266
-				$trashStorage->acquireLock($trashInternalPath, ILockingProvider::LOCK_EXCLUSIVE, $lockingProvider);
267
-				$gotLock = true;
268
-			} catch (LockedException $e) {
269
-				// a file with the same name is being deleted concurrently
270
-				// nudge the timestamp a bit to resolve the conflict
271
-
272
-				$timestamp = $timestamp + 1;
273
-
274
-				$trashPath = '/files_trashbin/files/' . $filename . '.d' . $timestamp;
275
-			}
276
-		}
277
-
278
-		/** @var \OC\Files\Storage\Storage $sourceStorage */
279
-		[$sourceStorage, $sourceInternalPath] = $ownerView->resolvePath('/files/' . $ownerPath);
280
-
281
-		$connection = \OC::$server->getDatabaseConnection();
282
-		$connection->beginTransaction();
283
-		$trashStorage->getUpdater()->renameFromStorage($sourceStorage, $sourceInternalPath, $trashInternalPath);
284
-
285
-		try {
286
-			$moveSuccessful = true;
287
-			if ($trashStorage->file_exists($trashInternalPath)) {
288
-				$trashStorage->unlink($trashInternalPath);
289
-			}
290
-			$trashStorage->moveFromStorage($sourceStorage, $sourceInternalPath, $trashInternalPath);
291
-		} catch (\OCA\Files_Trashbin\Exceptions\CopyRecursiveException $e) {
292
-			$moveSuccessful = false;
293
-			if ($trashStorage->file_exists($trashInternalPath)) {
294
-				$trashStorage->unlink($trashInternalPath);
295
-			}
296
-			\OC::$server->getLogger()->error('Couldn\'t move ' . $file_path . ' to the trash bin', ['app' => 'files_trashbin']);
297
-		}
298
-
299
-		if ($sourceStorage->file_exists($sourceInternalPath)) { // failed to delete the original file, abort
300
-			if ($sourceStorage->is_dir($sourceInternalPath)) {
301
-				$sourceStorage->rmdir($sourceInternalPath);
302
-			} else {
303
-				$sourceStorage->unlink($sourceInternalPath);
304
-			}
305
-			$connection->rollBack();
306
-			return false;
307
-		}
308
-
309
-		$connection->commit();
310
-
311
-		if ($moveSuccessful) {
312
-			$query = \OC_DB::prepare("INSERT INTO `*PREFIX*files_trash` (`id`,`timestamp`,`location`,`user`) VALUES (?,?,?,?)");
313
-			$result = $query->execute([$filename, $timestamp, $location, $owner]);
314
-			if (!$result) {
315
-				\OC::$server->getLogger()->error('trash bin database couldn\'t be updated', ['app' => 'files_trashbin']);
316
-			}
317
-			\OCP\Util::emitHook('\OCA\Files_Trashbin\Trashbin', 'post_moveToTrash', ['filePath' => Filesystem::normalizePath($file_path),
318
-				'trashPath' => Filesystem::normalizePath($filename . '.d' . $timestamp)]);
319
-
320
-			self::retainVersions($filename, $owner, $ownerPath, $timestamp);
321
-
322
-			// if owner !== user we need to also add a copy to the users trash
323
-			if ($user !== $owner && $ownerOnly === false) {
324
-				self::copyFilesToUser($ownerPath, $owner, $file_path, $user, $timestamp);
325
-			}
326
-		}
327
-
328
-		$trashStorage->releaseLock($trashInternalPath, ILockingProvider::LOCK_EXCLUSIVE, $lockingProvider);
329
-
330
-		self::scheduleExpire($user);
331
-
332
-		// if owner !== user we also need to update the owners trash size
333
-		if ($owner !== $user) {
334
-			self::scheduleExpire($owner);
335
-		}
336
-
337
-		return $moveSuccessful;
338
-	}
339
-
340
-	/**
341
-	 * Move file versions to trash so that they can be restored later
342
-	 *
343
-	 * @param string $filename of deleted file
344
-	 * @param string $owner owner user id
345
-	 * @param string $ownerPath path relative to the owner's home storage
346
-	 * @param integer $timestamp when the file was deleted
347
-	 */
348
-	private static function retainVersions($filename, $owner, $ownerPath, $timestamp) {
349
-		if (\OCP\App::isEnabled('files_versions') && !empty($ownerPath)) {
350
-			$user = User::getUser();
351
-			$rootView = new View('/');
352
-
353
-			if ($rootView->is_dir($owner . '/files_versions/' . $ownerPath)) {
354
-				if ($owner !== $user) {
355
-					self::copy_recursive($owner . '/files_versions/' . $ownerPath, $owner . '/files_trashbin/versions/' . basename($ownerPath) . '.d' . $timestamp, $rootView);
356
-				}
357
-				self::move($rootView, $owner . '/files_versions/' . $ownerPath, $user . '/files_trashbin/versions/' . $filename . '.d' . $timestamp);
358
-			} elseif ($versions = \OCA\Files_Versions\Storage::getVersions($owner, $ownerPath)) {
359
-				foreach ($versions as $v) {
360
-					if ($owner !== $user) {
361
-						self::copy($rootView, $owner . '/files_versions' . $v['path'] . '.v' . $v['version'], $owner . '/files_trashbin/versions/' . $v['name'] . '.v' . $v['version'] . '.d' . $timestamp);
362
-					}
363
-					self::move($rootView, $owner . '/files_versions' . $v['path'] . '.v' . $v['version'], $user . '/files_trashbin/versions/' . $filename . '.v' . $v['version'] . '.d' . $timestamp);
364
-				}
365
-			}
366
-		}
367
-	}
368
-
369
-	/**
370
-	 * Move a file or folder on storage level
371
-	 *
372
-	 * @param View $view
373
-	 * @param string $source
374
-	 * @param string $target
375
-	 * @return bool
376
-	 */
377
-	private static function move(View $view, $source, $target) {
378
-		/** @var \OC\Files\Storage\Storage $sourceStorage */
379
-		[$sourceStorage, $sourceInternalPath] = $view->resolvePath($source);
380
-		/** @var \OC\Files\Storage\Storage $targetStorage */
381
-		[$targetStorage, $targetInternalPath] = $view->resolvePath($target);
382
-		/** @var \OC\Files\Storage\Storage $ownerTrashStorage */
383
-
384
-		$result = $targetStorage->moveFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
385
-		if ($result) {
386
-			$targetStorage->getUpdater()->renameFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
387
-		}
388
-		return $result;
389
-	}
390
-
391
-	/**
392
-	 * Copy a file or folder on storage level
393
-	 *
394
-	 * @param View $view
395
-	 * @param string $source
396
-	 * @param string $target
397
-	 * @return bool
398
-	 */
399
-	private static function copy(View $view, $source, $target) {
400
-		/** @var \OC\Files\Storage\Storage $sourceStorage */
401
-		[$sourceStorage, $sourceInternalPath] = $view->resolvePath($source);
402
-		/** @var \OC\Files\Storage\Storage $targetStorage */
403
-		[$targetStorage, $targetInternalPath] = $view->resolvePath($target);
404
-		/** @var \OC\Files\Storage\Storage $ownerTrashStorage */
405
-
406
-		$result = $targetStorage->copyFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
407
-		if ($result) {
408
-			$targetStorage->getUpdater()->update($targetInternalPath);
409
-		}
410
-		return $result;
411
-	}
412
-
413
-	/**
414
-	 * Restore a file or folder from trash bin
415
-	 *
416
-	 * @param string $file path to the deleted file/folder relative to "files_trashbin/files/",
417
-	 * including the timestamp suffix ".d12345678"
418
-	 * @param string $filename name of the file/folder
419
-	 * @param int $timestamp time when the file/folder was deleted
420
-	 *
421
-	 * @return bool true on success, false otherwise
422
-	 */
423
-	public static function restore($file, $filename, $timestamp) {
424
-		$user = User::getUser();
425
-		$view = new View('/' . $user);
426
-
427
-		$location = '';
428
-		if ($timestamp) {
429
-			$location = self::getLocation($user, $filename, $timestamp);
430
-			if ($location === false) {
431
-				\OC::$server->getLogger()->error('trash bin database inconsistent! ($user: ' . $user . ' $filename: ' . $filename . ', $timestamp: ' . $timestamp . ')', ['app' => 'files_trashbin']);
432
-			} else {
433
-				// if location no longer exists, restore file in the root directory
434
-				if ($location !== '/' &&
435
-					(!$view->is_dir('files/' . $location) ||
436
-						!$view->isCreatable('files/' . $location))
437
-				) {
438
-					$location = '';
439
-				}
440
-			}
441
-		}
442
-
443
-		// we need a  extension in case a file/dir with the same name already exists
444
-		$uniqueFilename = self::getUniqueFilename($location, $filename, $view);
445
-
446
-		$source = Filesystem::normalizePath('files_trashbin/files/' . $file);
447
-		$target = Filesystem::normalizePath('files/' . $location . '/' . $uniqueFilename);
448
-		if (!$view->file_exists($source)) {
449
-			return false;
450
-		}
451
-		$mtime = $view->filemtime($source);
452
-
453
-		// restore file
454
-		if (!$view->isCreatable(dirname($target))) {
455
-			throw new NotPermittedException("Can't restore trash item because the target folder is not writable");
456
-		}
457
-		$restoreResult = $view->rename($source, $target);
458
-
459
-		// handle the restore result
460
-		if ($restoreResult) {
461
-			$fakeRoot = $view->getRoot();
462
-			$view->chroot('/' . $user . '/files');
463
-			$view->touch('/' . $location . '/' . $uniqueFilename, $mtime);
464
-			$view->chroot($fakeRoot);
465
-			\OCP\Util::emitHook('\OCA\Files_Trashbin\Trashbin', 'post_restore', ['filePath' => Filesystem::normalizePath('/' . $location . '/' . $uniqueFilename),
466
-				'trashPath' => Filesystem::normalizePath($file)]);
467
-
468
-			self::restoreVersions($view, $file, $filename, $uniqueFilename, $location, $timestamp);
469
-
470
-			if ($timestamp) {
471
-				$query = \OC_DB::prepare('DELETE FROM `*PREFIX*files_trash` WHERE `user`=? AND `id`=? AND `timestamp`=?');
472
-				$query->execute([$user, $filename, $timestamp]);
473
-			}
474
-
475
-			return true;
476
-		}
477
-
478
-		return false;
479
-	}
480
-
481
-	/**
482
-	 * restore versions from trash bin
483
-	 *
484
-	 * @param View $view file view
485
-	 * @param string $file complete path to file
486
-	 * @param string $filename name of file once it was deleted
487
-	 * @param string $uniqueFilename new file name to restore the file without overwriting existing files
488
-	 * @param string $location location if file
489
-	 * @param int $timestamp deletion time
490
-	 * @return false|null
491
-	 */
492
-	private static function restoreVersions(View $view, $file, $filename, $uniqueFilename, $location, $timestamp) {
493
-		if (\OCP\App::isEnabled('files_versions')) {
494
-			$user = User::getUser();
495
-			$rootView = new View('/');
496
-
497
-			$target = Filesystem::normalizePath('/' . $location . '/' . $uniqueFilename);
498
-
499
-			[$owner, $ownerPath] = self::getUidAndFilename($target);
500
-
501
-			// file has been deleted in between
502
-			if (empty($ownerPath)) {
503
-				return false;
504
-			}
505
-
506
-			if ($timestamp) {
507
-				$versionedFile = $filename;
508
-			} else {
509
-				$versionedFile = $file;
510
-			}
511
-
512
-			if ($view->is_dir('/files_trashbin/versions/' . $file)) {
513
-				$rootView->rename(Filesystem::normalizePath($user . '/files_trashbin/versions/' . $file), Filesystem::normalizePath($owner . '/files_versions/' . $ownerPath));
514
-			} elseif ($versions = self::getVersionsFromTrash($versionedFile, $timestamp, $user)) {
515
-				foreach ($versions as $v) {
516
-					if ($timestamp) {
517
-						$rootView->rename($user . '/files_trashbin/versions/' . $versionedFile . '.v' . $v . '.d' . $timestamp, $owner . '/files_versions/' . $ownerPath . '.v' . $v);
518
-					} else {
519
-						$rootView->rename($user . '/files_trashbin/versions/' . $versionedFile . '.v' . $v, $owner . '/files_versions/' . $ownerPath . '.v' . $v);
520
-					}
521
-				}
522
-			}
523
-		}
524
-	}
525
-
526
-	/**
527
-	 * delete all files from the trash
528
-	 */
529
-	public static function deleteAll() {
530
-		$user = User::getUser();
531
-		$userRoot = \OC::$server->getUserFolder($user)->getParent();
532
-		$view = new View('/' . $user);
533
-		$fileInfos = $view->getDirectoryContent('files_trashbin/files');
534
-
535
-		try {
536
-			$trash = $userRoot->get('files_trashbin');
537
-		} catch (NotFoundException $e) {
538
-			return false;
539
-		}
540
-
541
-		// Array to store the relative path in (after the file is deleted, the view won't be able to relativise the path anymore)
542
-		$filePaths = [];
543
-		foreach ($fileInfos as $fileInfo) {
544
-			$filePaths[] = $view->getRelativePath($fileInfo->getPath());
545
-		}
546
-		unset($fileInfos); // save memory
547
-
548
-		// Bulk PreDelete-Hook
549
-		\OC_Hook::emit('\OCP\Trashbin', 'preDeleteAll', ['paths' => $filePaths]);
550
-
551
-		// Single-File Hooks
552
-		foreach ($filePaths as $path) {
553
-			self::emitTrashbinPreDelete($path);
554
-		}
555
-
556
-		// actual file deletion
557
-		$trash->delete();
558
-		$query = \OC_DB::prepare('DELETE FROM `*PREFIX*files_trash` WHERE `user`=?');
559
-		$query->execute([$user]);
560
-
561
-		// Bulk PostDelete-Hook
562
-		\OC_Hook::emit('\OCP\Trashbin', 'deleteAll', ['paths' => $filePaths]);
563
-
564
-		// Single-File Hooks
565
-		foreach ($filePaths as $path) {
566
-			self::emitTrashbinPostDelete($path);
567
-		}
568
-
569
-		$trash = $userRoot->newFolder('files_trashbin');
570
-		$trash->newFolder('files');
571
-
572
-		return true;
573
-	}
574
-
575
-	/**
576
-	 * wrapper function to emit the 'preDelete' hook of \OCP\Trashbin before a file is deleted
577
-	 *
578
-	 * @param string $path
579
-	 */
580
-	protected static function emitTrashbinPreDelete($path) {
581
-		\OC_Hook::emit('\OCP\Trashbin', 'preDelete', ['path' => $path]);
582
-	}
583
-
584
-	/**
585
-	 * wrapper function to emit the 'delete' hook of \OCP\Trashbin after a file has been deleted
586
-	 *
587
-	 * @param string $path
588
-	 */
589
-	protected static function emitTrashbinPostDelete($path) {
590
-		\OC_Hook::emit('\OCP\Trashbin', 'delete', ['path' => $path]);
591
-	}
592
-
593
-	/**
594
-	 * delete file from trash bin permanently
595
-	 *
596
-	 * @param string $filename path to the file
597
-	 * @param string $user
598
-	 * @param int $timestamp of deletion time
599
-	 *
600
-	 * @return int size of deleted files
601
-	 */
602
-	public static function delete($filename, $user, $timestamp = null) {
603
-		$userRoot = \OC::$server->getUserFolder($user)->getParent();
604
-		$view = new View('/' . $user);
605
-		$size = 0;
606
-
607
-		if ($timestamp) {
608
-			$query = \OC_DB::prepare('DELETE FROM `*PREFIX*files_trash` WHERE `user`=? AND `id`=? AND `timestamp`=?');
609
-			$query->execute([$user, $filename, $timestamp]);
610
-			$file = $filename . '.d' . $timestamp;
611
-		} else {
612
-			$file = $filename;
613
-		}
614
-
615
-		$size += self::deleteVersions($view, $file, $filename, $timestamp, $user);
616
-
617
-		try {
618
-			$node = $userRoot->get('/files_trashbin/files/' . $file);
619
-		} catch (NotFoundException $e) {
620
-			return $size;
621
-		}
622
-
623
-		if ($node instanceof Folder) {
624
-			$size += self::calculateSize(new View('/' . $user . '/files_trashbin/files/' . $file));
625
-		} elseif ($node instanceof File) {
626
-			$size += $view->filesize('/files_trashbin/files/' . $file);
627
-		}
628
-
629
-		self::emitTrashbinPreDelete('/files_trashbin/files/' . $file);
630
-		$node->delete();
631
-		self::emitTrashbinPostDelete('/files_trashbin/files/' . $file);
632
-
633
-		return $size;
634
-	}
635
-
636
-	/**
637
-	 * @param View $view
638
-	 * @param string $file
639
-	 * @param string $filename
640
-	 * @param integer|null $timestamp
641
-	 * @param string $user
642
-	 * @return int
643
-	 */
644
-	private static function deleteVersions(View $view, $file, $filename, $timestamp, $user) {
645
-		$size = 0;
646
-		if (\OCP\App::isEnabled('files_versions')) {
647
-			if ($view->is_dir('files_trashbin/versions/' . $file)) {
648
-				$size += self::calculateSize(new View('/' . $user . '/files_trashbin/versions/' . $file));
649
-				$view->unlink('files_trashbin/versions/' . $file);
650
-			} elseif ($versions = self::getVersionsFromTrash($filename, $timestamp, $user)) {
651
-				foreach ($versions as $v) {
652
-					if ($timestamp) {
653
-						$size += $view->filesize('/files_trashbin/versions/' . $filename . '.v' . $v . '.d' . $timestamp);
654
-						$view->unlink('/files_trashbin/versions/' . $filename . '.v' . $v . '.d' . $timestamp);
655
-					} else {
656
-						$size += $view->filesize('/files_trashbin/versions/' . $filename . '.v' . $v);
657
-						$view->unlink('/files_trashbin/versions/' . $filename . '.v' . $v);
658
-					}
659
-				}
660
-			}
661
-		}
662
-		return $size;
663
-	}
664
-
665
-	/**
666
-	 * check to see whether a file exists in trashbin
667
-	 *
668
-	 * @param string $filename path to the file
669
-	 * @param int $timestamp of deletion time
670
-	 * @return bool true if file exists, otherwise false
671
-	 */
672
-	public static function file_exists($filename, $timestamp = null) {
673
-		$user = User::getUser();
674
-		$view = new View('/' . $user);
675
-
676
-		if ($timestamp) {
677
-			$filename = $filename . '.d' . $timestamp;
678
-		}
679
-
680
-		$target = Filesystem::normalizePath('files_trashbin/files/' . $filename);
681
-		return $view->file_exists($target);
682
-	}
683
-
684
-	/**
685
-	 * deletes used space for trash bin in db if user was deleted
686
-	 *
687
-	 * @param string $uid id of deleted user
688
-	 * @return bool result of db delete operation
689
-	 */
690
-	public static function deleteUser($uid) {
691
-		$query = \OC_DB::prepare('DELETE FROM `*PREFIX*files_trash` WHERE `user`=?');
692
-		return $query->execute([$uid]);
693
-	}
694
-
695
-	/**
696
-	 * calculate remaining free space for trash bin
697
-	 *
698
-	 * @param integer $trashbinSize current size of the trash bin
699
-	 * @param string $user
700
-	 * @return int available free space for trash bin
701
-	 */
702
-	private static function calculateFreeSpace($trashbinSize, $user) {
703
-		$config = \OC::$server->getConfig();
704
-		$systemTrashbinSize = (int)$config->getAppValue('files_trashbin', 'trashbin_size', '-1');
705
-		$userTrashbinSize = (int)$config->getUserValue($user, 'files_trashbin', 'trashbin_size', '-1');
706
-		$configuredTrashbinSize = ($userTrashbinSize < 0) ? $systemTrashbinSize : $userTrashbinSize;
707
-		if ($configuredTrashbinSize) {
708
-			return $configuredTrashbinSize - $trashbinSize;
709
-		}
710
-
711
-		$softQuota = true;
712
-		$userObject = \OC::$server->getUserManager()->get($user);
713
-		if (is_null($userObject)) {
714
-			return 0;
715
-		}
716
-		$quota = $userObject->getQuota();
717
-		if ($quota === null || $quota === 'none') {
718
-			$quota = Filesystem::free_space('/');
719
-			$softQuota = false;
720
-			// inf or unknown free space
721
-			if ($quota < 0) {
722
-				$quota = PHP_INT_MAX;
723
-			}
724
-		} else {
725
-			$quota = \OCP\Util::computerFileSize($quota);
726
-		}
727
-
728
-		// calculate available space for trash bin
729
-		// subtract size of files and current trash bin size from quota
730
-		if ($softQuota) {
731
-			$userFolder = \OC::$server->getUserFolder($user);
732
-			if (is_null($userFolder)) {
733
-				return 0;
734
-			}
735
-			$free = $quota - $userFolder->getSize(false); // remaining free space for user
736
-			if ($free > 0) {
737
-				$availableSpace = ($free * self::DEFAULTMAXSIZE / 100) - $trashbinSize; // how much space can be used for versions
738
-			} else {
739
-				$availableSpace = $free - $trashbinSize;
740
-			}
741
-		} else {
742
-			$availableSpace = $quota;
743
-		}
744
-
745
-		return $availableSpace;
746
-	}
747
-
748
-	/**
749
-	 * resize trash bin if necessary after a new file was added to Nextcloud
750
-	 *
751
-	 * @param string $user user id
752
-	 */
753
-	public static function resizeTrash($user) {
754
-		$size = self::getTrashbinSize($user);
755
-
756
-		$freeSpace = self::calculateFreeSpace($size, $user);
757
-
758
-		if ($freeSpace < 0) {
759
-			self::scheduleExpire($user);
760
-		}
761
-	}
762
-
763
-	/**
764
-	 * clean up the trash bin
765
-	 *
766
-	 * @param string $user
767
-	 */
768
-	public static function expire($user) {
769
-		$trashBinSize = self::getTrashbinSize($user);
770
-		$availableSpace = self::calculateFreeSpace($trashBinSize, $user);
771
-
772
-		$dirContent = Helper::getTrashFiles('/', $user, 'mtime');
773
-
774
-		// delete all files older then $retention_obligation
775
-		[$delSize, $count] = self::deleteExpiredFiles($dirContent, $user);
776
-
777
-		$availableSpace += $delSize;
778
-
779
-		// delete files from trash until we meet the trash bin size limit again
780
-		self::deleteFiles(array_slice($dirContent, $count), $user, $availableSpace);
781
-	}
782
-
783
-	/**
784
-	 * @param string $user
785
-	 */
786
-	private static function scheduleExpire($user) {
787
-		// let the admin disable auto expire
788
-		/** @var Application $application */
789
-		$application = \OC::$server->query(Application::class);
790
-		$expiration = $application->getContainer()->query('Expiration');
791
-		if ($expiration->isEnabled()) {
792
-			\OC::$server->getCommandBus()->push(new Expire($user));
793
-		}
794
-	}
795
-
796
-	/**
797
-	 * if the size limit for the trash bin is reached, we delete the oldest
798
-	 * files in the trash bin until we meet the limit again
799
-	 *
800
-	 * @param array $files
801
-	 * @param string $user
802
-	 * @param int $availableSpace available disc space
803
-	 * @return int size of deleted files
804
-	 */
805
-	protected static function deleteFiles($files, $user, $availableSpace) {
806
-		/** @var Application $application */
807
-		$application = \OC::$server->query(Application::class);
808
-		$expiration = $application->getContainer()->query('Expiration');
809
-		$size = 0;
810
-
811
-		if ($availableSpace < 0) {
812
-			foreach ($files as $file) {
813
-				if ($availableSpace < 0 && $expiration->isExpired($file['mtime'], true)) {
814
-					$tmp = self::delete($file['name'], $user, $file['mtime']);
815
-					\OC::$server->getLogger()->info('remove "' . $file['name'] . '" (' . $tmp . 'B) to meet the limit of trash bin size (50% of available quota)', ['app' => 'files_trashbin']);
816
-					$availableSpace += $tmp;
817
-					$size += $tmp;
818
-				} else {
819
-					break;
820
-				}
821
-			}
822
-		}
823
-		return $size;
824
-	}
825
-
826
-	/**
827
-	 * delete files older then max storage time
828
-	 *
829
-	 * @param array $files list of files sorted by mtime
830
-	 * @param string $user
831
-	 * @return integer[] size of deleted files and number of deleted files
832
-	 */
833
-	public static function deleteExpiredFiles($files, $user) {
834
-		/** @var Expiration $expiration */
835
-		$expiration = \OC::$server->query(Expiration::class);
836
-		$size = 0;
837
-		$count = 0;
838
-		foreach ($files as $file) {
839
-			$timestamp = $file['mtime'];
840
-			$filename = $file['name'];
841
-			if ($expiration->isExpired($timestamp)) {
842
-				try {
843
-					$size += self::delete($filename, $user, $timestamp);
844
-					$count++;
845
-				} catch (\OCP\Files\NotPermittedException $e) {
846
-					\OC::$server->getLogger()->logException($e, ['app' => 'files_trashbin', 'level' => \OCP\ILogger::WARN, 'message' => 'Removing "' . $filename . '" from trashbin failed.']);
847
-				}
848
-				\OC::$server->getLogger()->info(
849
-					'Remove "' . $filename . '" from trashbin because it exceeds max retention obligation term.',
850
-					['app' => 'files_trashbin']
851
-				);
852
-			} else {
853
-				break;
854
-			}
855
-		}
856
-
857
-		return [$size, $count];
858
-	}
859
-
860
-	/**
861
-	 * recursive copy to copy a whole directory
862
-	 *
863
-	 * @param string $source source path, relative to the users files directory
864
-	 * @param string $destination destination path relative to the users root directoy
865
-	 * @param View $view file view for the users root directory
866
-	 * @return int
867
-	 * @throws Exceptions\CopyRecursiveException
868
-	 */
869
-	private static function copy_recursive($source, $destination, View $view) {
870
-		$size = 0;
871
-		if ($view->is_dir($source)) {
872
-			$view->mkdir($destination);
873
-			$view->touch($destination, $view->filemtime($source));
874
-			foreach ($view->getDirectoryContent($source) as $i) {
875
-				$pathDir = $source . '/' . $i['name'];
876
-				if ($view->is_dir($pathDir)) {
877
-					$size += self::copy_recursive($pathDir, $destination . '/' . $i['name'], $view);
878
-				} else {
879
-					$size += $view->filesize($pathDir);
880
-					$result = $view->copy($pathDir, $destination . '/' . $i['name']);
881
-					if (!$result) {
882
-						throw new \OCA\Files_Trashbin\Exceptions\CopyRecursiveException();
883
-					}
884
-					$view->touch($destination . '/' . $i['name'], $view->filemtime($pathDir));
885
-				}
886
-			}
887
-		} else {
888
-			$size += $view->filesize($source);
889
-			$result = $view->copy($source, $destination);
890
-			if (!$result) {
891
-				throw new \OCA\Files_Trashbin\Exceptions\CopyRecursiveException();
892
-			}
893
-			$view->touch($destination, $view->filemtime($source));
894
-		}
895
-		return $size;
896
-	}
897
-
898
-	/**
899
-	 * find all versions which belong to the file we want to restore
900
-	 *
901
-	 * @param string $filename name of the file which should be restored
902
-	 * @param int $timestamp timestamp when the file was deleted
903
-	 * @return array
904
-	 */
905
-	private static function getVersionsFromTrash($filename, $timestamp, $user) {
906
-		$view = new View('/' . $user . '/files_trashbin/versions');
907
-		$versions = [];
908
-
909
-		//force rescan of versions, local storage may not have updated the cache
910
-		if (!self::$scannedVersions) {
911
-			/** @var \OC\Files\Storage\Storage $storage */
912
-			[$storage,] = $view->resolvePath('/');
913
-			$storage->getScanner()->scan('files_trashbin/versions');
914
-			self::$scannedVersions = true;
915
-		}
916
-
917
-		if ($timestamp) {
918
-			// fetch for old versions
919
-			$matches = $view->searchRaw($filename . '.v%.d' . $timestamp);
920
-			$offset = -strlen($timestamp) - 2;
921
-		} else {
922
-			$matches = $view->searchRaw($filename . '.v%');
923
-		}
924
-
925
-		if (is_array($matches)) {
926
-			foreach ($matches as $ma) {
927
-				if ($timestamp) {
928
-					$parts = explode('.v', substr($ma['path'], 0, $offset));
929
-					$versions[] = end($parts);
930
-				} else {
931
-					$parts = explode('.v', $ma);
932
-					$versions[] = end($parts);
933
-				}
934
-			}
935
-		}
936
-		return $versions;
937
-	}
938
-
939
-	/**
940
-	 * find unique extension for restored file if a file with the same name already exists
941
-	 *
942
-	 * @param string $location where the file should be restored
943
-	 * @param string $filename name of the file
944
-	 * @param View $view filesystem view relative to users root directory
945
-	 * @return string with unique extension
946
-	 */
947
-	private static function getUniqueFilename($location, $filename, View $view) {
948
-		$ext = pathinfo($filename, PATHINFO_EXTENSION);
949
-		$name = pathinfo($filename, PATHINFO_FILENAME);
950
-		$l = \OC::$server->getL10N('files_trashbin');
951
-
952
-		$location = '/' . trim($location, '/');
953
-
954
-		// if extension is not empty we set a dot in front of it
955
-		if ($ext !== '') {
956
-			$ext = '.' . $ext;
957
-		}
958
-
959
-		if ($view->file_exists('files' . $location . '/' . $filename)) {
960
-			$i = 2;
961
-			$uniqueName = $name . " (" . $l->t("restored") . ")" . $ext;
962
-			while ($view->file_exists('files' . $location . '/' . $uniqueName)) {
963
-				$uniqueName = $name . " (" . $l->t("restored") . " " . $i . ")" . $ext;
964
-				$i++;
965
-			}
966
-
967
-			return $uniqueName;
968
-		}
969
-
970
-		return $filename;
971
-	}
972
-
973
-	/**
974
-	 * get the size from a given root folder
975
-	 *
976
-	 * @param View $view file view on the root folder
977
-	 * @return integer size of the folder
978
-	 */
979
-	private static function calculateSize($view) {
980
-		$root = \OC::$server->getConfig()->getSystemValue('datadirectory', \OC::$SERVERROOT . '/data') . $view->getAbsolutePath('');
981
-		if (!file_exists($root)) {
982
-			return 0;
983
-		}
984
-		$iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($root), \RecursiveIteratorIterator::CHILD_FIRST);
985
-		$size = 0;
986
-
987
-		/**
988
-		 * RecursiveDirectoryIterator on an NFS path isn't iterable with foreach
989
-		 * This bug is fixed in PHP 5.5.9 or before
990
-		 * See #8376
991
-		 */
992
-		$iterator->rewind();
993
-		while ($iterator->valid()) {
994
-			$path = $iterator->current();
995
-			$relpath = substr($path, strlen($root) - 1);
996
-			if (!$view->is_dir($relpath)) {
997
-				$size += $view->filesize($relpath);
998
-			}
999
-			$iterator->next();
1000
-		}
1001
-		return $size;
1002
-	}
1003
-
1004
-	/**
1005
-	 * get current size of trash bin from a given user
1006
-	 *
1007
-	 * @param string $user user who owns the trash bin
1008
-	 * @return integer trash bin size
1009
-	 */
1010
-	private static function getTrashbinSize($user) {
1011
-		$view = new View('/' . $user);
1012
-		$fileInfo = $view->getFileInfo('/files_trashbin');
1013
-		return isset($fileInfo['size']) ? $fileInfo['size'] : 0;
1014
-	}
1015
-
1016
-	/**
1017
-	 * register hooks
1018
-	 */
1019
-	public static function registerHooks() {
1020
-		// create storage wrapper on setup
1021
-		\OCP\Util::connectHook('OC_Filesystem', 'preSetup', 'OCA\Files_Trashbin\Storage', 'setupStorage');
1022
-		//Listen to delete user signal
1023
-		\OCP\Util::connectHook('OC_User', 'pre_deleteUser', 'OCA\Files_Trashbin\Hooks', 'deleteUser_hook');
1024
-		//Listen to post write hook
1025
-		\OCP\Util::connectHook('OC_Filesystem', 'post_write', 'OCA\Files_Trashbin\Hooks', 'post_write_hook');
1026
-		// pre and post-rename, disable trash logic for the copy+unlink case
1027
-		\OCP\Util::connectHook('OC_Filesystem', 'delete', 'OCA\Files_Trashbin\Trashbin', 'ensureFileScannedHook');
1028
-	}
1029
-
1030
-	/**
1031
-	 * check if trash bin is empty for a given user
1032
-	 *
1033
-	 * @param string $user
1034
-	 * @return bool
1035
-	 */
1036
-	public static function isEmpty($user) {
1037
-		$view = new View('/' . $user . '/files_trashbin');
1038
-		if ($view->is_dir('/files') && $dh = $view->opendir('/files')) {
1039
-			while ($file = readdir($dh)) {
1040
-				if (!Filesystem::isIgnoredDir($file)) {
1041
-					return false;
1042
-				}
1043
-			}
1044
-		}
1045
-		return true;
1046
-	}
1047
-
1048
-	/**
1049
-	 * @param $path
1050
-	 * @return string
1051
-	 */
1052
-	public static function preview_icon($path) {
1053
-		return \OC::$server->getURLGenerator()->linkToRoute('core_ajax_trashbin_preview', ['x' => 32, 'y' => 32, 'file' => $path]);
1054
-	}
61
+    // unit: percentage; 50% of available disk space/quota
62
+    public const DEFAULTMAXSIZE = 50;
63
+
64
+    /**
65
+     * Whether versions have already be rescanned during this PHP request
66
+     *
67
+     * @var bool
68
+     */
69
+    private static $scannedVersions = false;
70
+
71
+    /**
72
+     * Ensure we don't need to scan the file during the move to trash
73
+     * by triggering the scan in the pre-hook
74
+     *
75
+     * @param array $params
76
+     */
77
+    public static function ensureFileScannedHook($params) {
78
+        try {
79
+            self::getUidAndFilename($params['path']);
80
+        } catch (NotFoundException $e) {
81
+            // nothing to scan for non existing files
82
+        }
83
+    }
84
+
85
+    /**
86
+     * get the UID of the owner of the file and the path to the file relative to
87
+     * owners files folder
88
+     *
89
+     * @param string $filename
90
+     * @return array
91
+     * @throws \OC\User\NoUserException
92
+     */
93
+    public static function getUidAndFilename($filename) {
94
+        $uid = Filesystem::getOwner($filename);
95
+        $userManager = \OC::$server->getUserManager();
96
+        // if the user with the UID doesn't exists, e.g. because the UID points
97
+        // to a remote user with a federated cloud ID we use the current logged-in
98
+        // user. We need a valid local user to move the file to the right trash bin
99
+        if (!$userManager->userExists($uid)) {
100
+            $uid = User::getUser();
101
+        }
102
+        if (!$uid) {
103
+            // no owner, usually because of share link from ext storage
104
+            return [null, null];
105
+        }
106
+        Filesystem::initMountPoints($uid);
107
+        if ($uid !== User::getUser()) {
108
+            $info = Filesystem::getFileInfo($filename);
109
+            $ownerView = new View('/' . $uid . '/files');
110
+            try {
111
+                $filename = $ownerView->getPath($info['fileid']);
112
+            } catch (NotFoundException $e) {
113
+                $filename = null;
114
+            }
115
+        }
116
+        return [$uid, $filename];
117
+    }
118
+
119
+    /**
120
+     * get original location of files for user
121
+     *
122
+     * @param string $user
123
+     * @return array (filename => array (timestamp => original location))
124
+     */
125
+    public static function getLocations($user) {
126
+        $query = \OC_DB::prepare('SELECT `id`, `timestamp`, `location`'
127
+            . ' FROM `*PREFIX*files_trash` WHERE `user`=?');
128
+        $result = $query->execute([$user]);
129
+        $array = [];
130
+        while ($row = $result->fetchRow()) {
131
+            if (isset($array[$row['id']])) {
132
+                $array[$row['id']][$row['timestamp']] = $row['location'];
133
+            } else {
134
+                $array[$row['id']] = [$row['timestamp'] => $row['location']];
135
+            }
136
+        }
137
+        return $array;
138
+    }
139
+
140
+    /**
141
+     * get original location of file
142
+     *
143
+     * @param string $user
144
+     * @param string $filename
145
+     * @param string $timestamp
146
+     * @return string original location
147
+     */
148
+    public static function getLocation($user, $filename, $timestamp) {
149
+        $query = \OC_DB::prepare('SELECT `location` FROM `*PREFIX*files_trash`'
150
+            . ' WHERE `user`=? AND `id`=? AND `timestamp`=?');
151
+        $result = $query->execute([$user, $filename, $timestamp])->fetchAll();
152
+        if (isset($result[0]['location'])) {
153
+            return $result[0]['location'];
154
+        } else {
155
+            return false;
156
+        }
157
+    }
158
+
159
+    private static function setUpTrash($user) {
160
+        $view = new View('/' . $user);
161
+        if (!$view->is_dir('files_trashbin')) {
162
+            $view->mkdir('files_trashbin');
163
+        }
164
+        if (!$view->is_dir('files_trashbin/files')) {
165
+            $view->mkdir('files_trashbin/files');
166
+        }
167
+        if (!$view->is_dir('files_trashbin/versions')) {
168
+            $view->mkdir('files_trashbin/versions');
169
+        }
170
+        if (!$view->is_dir('files_trashbin/keys')) {
171
+            $view->mkdir('files_trashbin/keys');
172
+        }
173
+    }
174
+
175
+
176
+    /**
177
+     * copy file to owners trash
178
+     *
179
+     * @param string $sourcePath
180
+     * @param string $owner
181
+     * @param string $targetPath
182
+     * @param $user
183
+     * @param integer $timestamp
184
+     */
185
+    private static function copyFilesToUser($sourcePath, $owner, $targetPath, $user, $timestamp) {
186
+        self::setUpTrash($owner);
187
+
188
+        $targetFilename = basename($targetPath);
189
+        $targetLocation = dirname($targetPath);
190
+
191
+        $sourceFilename = basename($sourcePath);
192
+
193
+        $view = new View('/');
194
+
195
+        $target = $user . '/files_trashbin/files/' . $targetFilename . '.d' . $timestamp;
196
+        $source = $owner . '/files_trashbin/files/' . $sourceFilename . '.d' . $timestamp;
197
+        $free = $view->free_space($target);
198
+        $isUnknownOrUnlimitedFreeSpace = $free < 0;
199
+        $isEnoughFreeSpaceLeft = $view->filesize($source) < $free;
200
+        if ($isUnknownOrUnlimitedFreeSpace || $isEnoughFreeSpaceLeft) {
201
+            self::copy_recursive($source, $target, $view);
202
+        }
203
+
204
+
205
+        if ($view->file_exists($target)) {
206
+            $query = \OC_DB::prepare("INSERT INTO `*PREFIX*files_trash` (`id`,`timestamp`,`location`,`user`) VALUES (?,?,?,?)");
207
+            $result = $query->execute([$targetFilename, $timestamp, $targetLocation, $user]);
208
+            if (!$result) {
209
+                \OC::$server->getLogger()->error('trash bin database couldn\'t be updated for the files owner', ['app' => 'files_trashbin']);
210
+            }
211
+        }
212
+    }
213
+
214
+
215
+    /**
216
+     * move file to the trash bin
217
+     *
218
+     * @param string $file_path path to the deleted file/directory relative to the files root directory
219
+     * @param bool $ownerOnly delete for owner only (if file gets moved out of a shared folder)
220
+     *
221
+     * @return bool
222
+     */
223
+    public static function move2trash($file_path, $ownerOnly = false) {
224
+        // get the user for which the filesystem is setup
225
+        $root = Filesystem::getRoot();
226
+        [, $user] = explode('/', $root);
227
+        [$owner, $ownerPath] = self::getUidAndFilename($file_path);
228
+
229
+        // if no owner found (ex: ext storage + share link), will use the current user's trashbin then
230
+        if (is_null($owner)) {
231
+            $owner = $user;
232
+            $ownerPath = $file_path;
233
+        }
234
+
235
+        $ownerView = new View('/' . $owner);
236
+        // file has been deleted in between
237
+        if (is_null($ownerPath) || $ownerPath === '' || !$ownerView->file_exists('/files/' . $ownerPath)) {
238
+            return true;
239
+        }
240
+
241
+        self::setUpTrash($user);
242
+        if ($owner !== $user) {
243
+            // also setup for owner
244
+            self::setUpTrash($owner);
245
+        }
246
+
247
+        $path_parts = pathinfo($ownerPath);
248
+
249
+        $filename = $path_parts['basename'];
250
+        $location = $path_parts['dirname'];
251
+        /** @var ITimeFactory $timeFactory */
252
+        $timeFactory = \OC::$server->query(ITimeFactory::class);
253
+        $timestamp = $timeFactory->getTime();
254
+
255
+        $lockingProvider = \OC::$server->getLockingProvider();
256
+
257
+        // disable proxy to prevent recursive calls
258
+        $trashPath = '/files_trashbin/files/' . $filename . '.d' . $timestamp;
259
+        $gotLock = false;
260
+
261
+        while (!$gotLock) {
262
+            try {
263
+                /** @var \OC\Files\Storage\Storage $trashStorage */
264
+                [$trashStorage, $trashInternalPath] = $ownerView->resolvePath($trashPath);
265
+
266
+                $trashStorage->acquireLock($trashInternalPath, ILockingProvider::LOCK_EXCLUSIVE, $lockingProvider);
267
+                $gotLock = true;
268
+            } catch (LockedException $e) {
269
+                // a file with the same name is being deleted concurrently
270
+                // nudge the timestamp a bit to resolve the conflict
271
+
272
+                $timestamp = $timestamp + 1;
273
+
274
+                $trashPath = '/files_trashbin/files/' . $filename . '.d' . $timestamp;
275
+            }
276
+        }
277
+
278
+        /** @var \OC\Files\Storage\Storage $sourceStorage */
279
+        [$sourceStorage, $sourceInternalPath] = $ownerView->resolvePath('/files/' . $ownerPath);
280
+
281
+        $connection = \OC::$server->getDatabaseConnection();
282
+        $connection->beginTransaction();
283
+        $trashStorage->getUpdater()->renameFromStorage($sourceStorage, $sourceInternalPath, $trashInternalPath);
284
+
285
+        try {
286
+            $moveSuccessful = true;
287
+            if ($trashStorage->file_exists($trashInternalPath)) {
288
+                $trashStorage->unlink($trashInternalPath);
289
+            }
290
+            $trashStorage->moveFromStorage($sourceStorage, $sourceInternalPath, $trashInternalPath);
291
+        } catch (\OCA\Files_Trashbin\Exceptions\CopyRecursiveException $e) {
292
+            $moveSuccessful = false;
293
+            if ($trashStorage->file_exists($trashInternalPath)) {
294
+                $trashStorage->unlink($trashInternalPath);
295
+            }
296
+            \OC::$server->getLogger()->error('Couldn\'t move ' . $file_path . ' to the trash bin', ['app' => 'files_trashbin']);
297
+        }
298
+
299
+        if ($sourceStorage->file_exists($sourceInternalPath)) { // failed to delete the original file, abort
300
+            if ($sourceStorage->is_dir($sourceInternalPath)) {
301
+                $sourceStorage->rmdir($sourceInternalPath);
302
+            } else {
303
+                $sourceStorage->unlink($sourceInternalPath);
304
+            }
305
+            $connection->rollBack();
306
+            return false;
307
+        }
308
+
309
+        $connection->commit();
310
+
311
+        if ($moveSuccessful) {
312
+            $query = \OC_DB::prepare("INSERT INTO `*PREFIX*files_trash` (`id`,`timestamp`,`location`,`user`) VALUES (?,?,?,?)");
313
+            $result = $query->execute([$filename, $timestamp, $location, $owner]);
314
+            if (!$result) {
315
+                \OC::$server->getLogger()->error('trash bin database couldn\'t be updated', ['app' => 'files_trashbin']);
316
+            }
317
+            \OCP\Util::emitHook('\OCA\Files_Trashbin\Trashbin', 'post_moveToTrash', ['filePath' => Filesystem::normalizePath($file_path),
318
+                'trashPath' => Filesystem::normalizePath($filename . '.d' . $timestamp)]);
319
+
320
+            self::retainVersions($filename, $owner, $ownerPath, $timestamp);
321
+
322
+            // if owner !== user we need to also add a copy to the users trash
323
+            if ($user !== $owner && $ownerOnly === false) {
324
+                self::copyFilesToUser($ownerPath, $owner, $file_path, $user, $timestamp);
325
+            }
326
+        }
327
+
328
+        $trashStorage->releaseLock($trashInternalPath, ILockingProvider::LOCK_EXCLUSIVE, $lockingProvider);
329
+
330
+        self::scheduleExpire($user);
331
+
332
+        // if owner !== user we also need to update the owners trash size
333
+        if ($owner !== $user) {
334
+            self::scheduleExpire($owner);
335
+        }
336
+
337
+        return $moveSuccessful;
338
+    }
339
+
340
+    /**
341
+     * Move file versions to trash so that they can be restored later
342
+     *
343
+     * @param string $filename of deleted file
344
+     * @param string $owner owner user id
345
+     * @param string $ownerPath path relative to the owner's home storage
346
+     * @param integer $timestamp when the file was deleted
347
+     */
348
+    private static function retainVersions($filename, $owner, $ownerPath, $timestamp) {
349
+        if (\OCP\App::isEnabled('files_versions') && !empty($ownerPath)) {
350
+            $user = User::getUser();
351
+            $rootView = new View('/');
352
+
353
+            if ($rootView->is_dir($owner . '/files_versions/' . $ownerPath)) {
354
+                if ($owner !== $user) {
355
+                    self::copy_recursive($owner . '/files_versions/' . $ownerPath, $owner . '/files_trashbin/versions/' . basename($ownerPath) . '.d' . $timestamp, $rootView);
356
+                }
357
+                self::move($rootView, $owner . '/files_versions/' . $ownerPath, $user . '/files_trashbin/versions/' . $filename . '.d' . $timestamp);
358
+            } elseif ($versions = \OCA\Files_Versions\Storage::getVersions($owner, $ownerPath)) {
359
+                foreach ($versions as $v) {
360
+                    if ($owner !== $user) {
361
+                        self::copy($rootView, $owner . '/files_versions' . $v['path'] . '.v' . $v['version'], $owner . '/files_trashbin/versions/' . $v['name'] . '.v' . $v['version'] . '.d' . $timestamp);
362
+                    }
363
+                    self::move($rootView, $owner . '/files_versions' . $v['path'] . '.v' . $v['version'], $user . '/files_trashbin/versions/' . $filename . '.v' . $v['version'] . '.d' . $timestamp);
364
+                }
365
+            }
366
+        }
367
+    }
368
+
369
+    /**
370
+     * Move a file or folder on storage level
371
+     *
372
+     * @param View $view
373
+     * @param string $source
374
+     * @param string $target
375
+     * @return bool
376
+     */
377
+    private static function move(View $view, $source, $target) {
378
+        /** @var \OC\Files\Storage\Storage $sourceStorage */
379
+        [$sourceStorage, $sourceInternalPath] = $view->resolvePath($source);
380
+        /** @var \OC\Files\Storage\Storage $targetStorage */
381
+        [$targetStorage, $targetInternalPath] = $view->resolvePath($target);
382
+        /** @var \OC\Files\Storage\Storage $ownerTrashStorage */
383
+
384
+        $result = $targetStorage->moveFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
385
+        if ($result) {
386
+            $targetStorage->getUpdater()->renameFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
387
+        }
388
+        return $result;
389
+    }
390
+
391
+    /**
392
+     * Copy a file or folder on storage level
393
+     *
394
+     * @param View $view
395
+     * @param string $source
396
+     * @param string $target
397
+     * @return bool
398
+     */
399
+    private static function copy(View $view, $source, $target) {
400
+        /** @var \OC\Files\Storage\Storage $sourceStorage */
401
+        [$sourceStorage, $sourceInternalPath] = $view->resolvePath($source);
402
+        /** @var \OC\Files\Storage\Storage $targetStorage */
403
+        [$targetStorage, $targetInternalPath] = $view->resolvePath($target);
404
+        /** @var \OC\Files\Storage\Storage $ownerTrashStorage */
405
+
406
+        $result = $targetStorage->copyFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
407
+        if ($result) {
408
+            $targetStorage->getUpdater()->update($targetInternalPath);
409
+        }
410
+        return $result;
411
+    }
412
+
413
+    /**
414
+     * Restore a file or folder from trash bin
415
+     *
416
+     * @param string $file path to the deleted file/folder relative to "files_trashbin/files/",
417
+     * including the timestamp suffix ".d12345678"
418
+     * @param string $filename name of the file/folder
419
+     * @param int $timestamp time when the file/folder was deleted
420
+     *
421
+     * @return bool true on success, false otherwise
422
+     */
423
+    public static function restore($file, $filename, $timestamp) {
424
+        $user = User::getUser();
425
+        $view = new View('/' . $user);
426
+
427
+        $location = '';
428
+        if ($timestamp) {
429
+            $location = self::getLocation($user, $filename, $timestamp);
430
+            if ($location === false) {
431
+                \OC::$server->getLogger()->error('trash bin database inconsistent! ($user: ' . $user . ' $filename: ' . $filename . ', $timestamp: ' . $timestamp . ')', ['app' => 'files_trashbin']);
432
+            } else {
433
+                // if location no longer exists, restore file in the root directory
434
+                if ($location !== '/' &&
435
+                    (!$view->is_dir('files/' . $location) ||
436
+                        !$view->isCreatable('files/' . $location))
437
+                ) {
438
+                    $location = '';
439
+                }
440
+            }
441
+        }
442
+
443
+        // we need a  extension in case a file/dir with the same name already exists
444
+        $uniqueFilename = self::getUniqueFilename($location, $filename, $view);
445
+
446
+        $source = Filesystem::normalizePath('files_trashbin/files/' . $file);
447
+        $target = Filesystem::normalizePath('files/' . $location . '/' . $uniqueFilename);
448
+        if (!$view->file_exists($source)) {
449
+            return false;
450
+        }
451
+        $mtime = $view->filemtime($source);
452
+
453
+        // restore file
454
+        if (!$view->isCreatable(dirname($target))) {
455
+            throw new NotPermittedException("Can't restore trash item because the target folder is not writable");
456
+        }
457
+        $restoreResult = $view->rename($source, $target);
458
+
459
+        // handle the restore result
460
+        if ($restoreResult) {
461
+            $fakeRoot = $view->getRoot();
462
+            $view->chroot('/' . $user . '/files');
463
+            $view->touch('/' . $location . '/' . $uniqueFilename, $mtime);
464
+            $view->chroot($fakeRoot);
465
+            \OCP\Util::emitHook('\OCA\Files_Trashbin\Trashbin', 'post_restore', ['filePath' => Filesystem::normalizePath('/' . $location . '/' . $uniqueFilename),
466
+                'trashPath' => Filesystem::normalizePath($file)]);
467
+
468
+            self::restoreVersions($view, $file, $filename, $uniqueFilename, $location, $timestamp);
469
+
470
+            if ($timestamp) {
471
+                $query = \OC_DB::prepare('DELETE FROM `*PREFIX*files_trash` WHERE `user`=? AND `id`=? AND `timestamp`=?');
472
+                $query->execute([$user, $filename, $timestamp]);
473
+            }
474
+
475
+            return true;
476
+        }
477
+
478
+        return false;
479
+    }
480
+
481
+    /**
482
+     * restore versions from trash bin
483
+     *
484
+     * @param View $view file view
485
+     * @param string $file complete path to file
486
+     * @param string $filename name of file once it was deleted
487
+     * @param string $uniqueFilename new file name to restore the file without overwriting existing files
488
+     * @param string $location location if file
489
+     * @param int $timestamp deletion time
490
+     * @return false|null
491
+     */
492
+    private static function restoreVersions(View $view, $file, $filename, $uniqueFilename, $location, $timestamp) {
493
+        if (\OCP\App::isEnabled('files_versions')) {
494
+            $user = User::getUser();
495
+            $rootView = new View('/');
496
+
497
+            $target = Filesystem::normalizePath('/' . $location . '/' . $uniqueFilename);
498
+
499
+            [$owner, $ownerPath] = self::getUidAndFilename($target);
500
+
501
+            // file has been deleted in between
502
+            if (empty($ownerPath)) {
503
+                return false;
504
+            }
505
+
506
+            if ($timestamp) {
507
+                $versionedFile = $filename;
508
+            } else {
509
+                $versionedFile = $file;
510
+            }
511
+
512
+            if ($view->is_dir('/files_trashbin/versions/' . $file)) {
513
+                $rootView->rename(Filesystem::normalizePath($user . '/files_trashbin/versions/' . $file), Filesystem::normalizePath($owner . '/files_versions/' . $ownerPath));
514
+            } elseif ($versions = self::getVersionsFromTrash($versionedFile, $timestamp, $user)) {
515
+                foreach ($versions as $v) {
516
+                    if ($timestamp) {
517
+                        $rootView->rename($user . '/files_trashbin/versions/' . $versionedFile . '.v' . $v . '.d' . $timestamp, $owner . '/files_versions/' . $ownerPath . '.v' . $v);
518
+                    } else {
519
+                        $rootView->rename($user . '/files_trashbin/versions/' . $versionedFile . '.v' . $v, $owner . '/files_versions/' . $ownerPath . '.v' . $v);
520
+                    }
521
+                }
522
+            }
523
+        }
524
+    }
525
+
526
+    /**
527
+     * delete all files from the trash
528
+     */
529
+    public static function deleteAll() {
530
+        $user = User::getUser();
531
+        $userRoot = \OC::$server->getUserFolder($user)->getParent();
532
+        $view = new View('/' . $user);
533
+        $fileInfos = $view->getDirectoryContent('files_trashbin/files');
534
+
535
+        try {
536
+            $trash = $userRoot->get('files_trashbin');
537
+        } catch (NotFoundException $e) {
538
+            return false;
539
+        }
540
+
541
+        // Array to store the relative path in (after the file is deleted, the view won't be able to relativise the path anymore)
542
+        $filePaths = [];
543
+        foreach ($fileInfos as $fileInfo) {
544
+            $filePaths[] = $view->getRelativePath($fileInfo->getPath());
545
+        }
546
+        unset($fileInfos); // save memory
547
+
548
+        // Bulk PreDelete-Hook
549
+        \OC_Hook::emit('\OCP\Trashbin', 'preDeleteAll', ['paths' => $filePaths]);
550
+
551
+        // Single-File Hooks
552
+        foreach ($filePaths as $path) {
553
+            self::emitTrashbinPreDelete($path);
554
+        }
555
+
556
+        // actual file deletion
557
+        $trash->delete();
558
+        $query = \OC_DB::prepare('DELETE FROM `*PREFIX*files_trash` WHERE `user`=?');
559
+        $query->execute([$user]);
560
+
561
+        // Bulk PostDelete-Hook
562
+        \OC_Hook::emit('\OCP\Trashbin', 'deleteAll', ['paths' => $filePaths]);
563
+
564
+        // Single-File Hooks
565
+        foreach ($filePaths as $path) {
566
+            self::emitTrashbinPostDelete($path);
567
+        }
568
+
569
+        $trash = $userRoot->newFolder('files_trashbin');
570
+        $trash->newFolder('files');
571
+
572
+        return true;
573
+    }
574
+
575
+    /**
576
+     * wrapper function to emit the 'preDelete' hook of \OCP\Trashbin before a file is deleted
577
+     *
578
+     * @param string $path
579
+     */
580
+    protected static function emitTrashbinPreDelete($path) {
581
+        \OC_Hook::emit('\OCP\Trashbin', 'preDelete', ['path' => $path]);
582
+    }
583
+
584
+    /**
585
+     * wrapper function to emit the 'delete' hook of \OCP\Trashbin after a file has been deleted
586
+     *
587
+     * @param string $path
588
+     */
589
+    protected static function emitTrashbinPostDelete($path) {
590
+        \OC_Hook::emit('\OCP\Trashbin', 'delete', ['path' => $path]);
591
+    }
592
+
593
+    /**
594
+     * delete file from trash bin permanently
595
+     *
596
+     * @param string $filename path to the file
597
+     * @param string $user
598
+     * @param int $timestamp of deletion time
599
+     *
600
+     * @return int size of deleted files
601
+     */
602
+    public static function delete($filename, $user, $timestamp = null) {
603
+        $userRoot = \OC::$server->getUserFolder($user)->getParent();
604
+        $view = new View('/' . $user);
605
+        $size = 0;
606
+
607
+        if ($timestamp) {
608
+            $query = \OC_DB::prepare('DELETE FROM `*PREFIX*files_trash` WHERE `user`=? AND `id`=? AND `timestamp`=?');
609
+            $query->execute([$user, $filename, $timestamp]);
610
+            $file = $filename . '.d' . $timestamp;
611
+        } else {
612
+            $file = $filename;
613
+        }
614
+
615
+        $size += self::deleteVersions($view, $file, $filename, $timestamp, $user);
616
+
617
+        try {
618
+            $node = $userRoot->get('/files_trashbin/files/' . $file);
619
+        } catch (NotFoundException $e) {
620
+            return $size;
621
+        }
622
+
623
+        if ($node instanceof Folder) {
624
+            $size += self::calculateSize(new View('/' . $user . '/files_trashbin/files/' . $file));
625
+        } elseif ($node instanceof File) {
626
+            $size += $view->filesize('/files_trashbin/files/' . $file);
627
+        }
628
+
629
+        self::emitTrashbinPreDelete('/files_trashbin/files/' . $file);
630
+        $node->delete();
631
+        self::emitTrashbinPostDelete('/files_trashbin/files/' . $file);
632
+
633
+        return $size;
634
+    }
635
+
636
+    /**
637
+     * @param View $view
638
+     * @param string $file
639
+     * @param string $filename
640
+     * @param integer|null $timestamp
641
+     * @param string $user
642
+     * @return int
643
+     */
644
+    private static function deleteVersions(View $view, $file, $filename, $timestamp, $user) {
645
+        $size = 0;
646
+        if (\OCP\App::isEnabled('files_versions')) {
647
+            if ($view->is_dir('files_trashbin/versions/' . $file)) {
648
+                $size += self::calculateSize(new View('/' . $user . '/files_trashbin/versions/' . $file));
649
+                $view->unlink('files_trashbin/versions/' . $file);
650
+            } elseif ($versions = self::getVersionsFromTrash($filename, $timestamp, $user)) {
651
+                foreach ($versions as $v) {
652
+                    if ($timestamp) {
653
+                        $size += $view->filesize('/files_trashbin/versions/' . $filename . '.v' . $v . '.d' . $timestamp);
654
+                        $view->unlink('/files_trashbin/versions/' . $filename . '.v' . $v . '.d' . $timestamp);
655
+                    } else {
656
+                        $size += $view->filesize('/files_trashbin/versions/' . $filename . '.v' . $v);
657
+                        $view->unlink('/files_trashbin/versions/' . $filename . '.v' . $v);
658
+                    }
659
+                }
660
+            }
661
+        }
662
+        return $size;
663
+    }
664
+
665
+    /**
666
+     * check to see whether a file exists in trashbin
667
+     *
668
+     * @param string $filename path to the file
669
+     * @param int $timestamp of deletion time
670
+     * @return bool true if file exists, otherwise false
671
+     */
672
+    public static function file_exists($filename, $timestamp = null) {
673
+        $user = User::getUser();
674
+        $view = new View('/' . $user);
675
+
676
+        if ($timestamp) {
677
+            $filename = $filename . '.d' . $timestamp;
678
+        }
679
+
680
+        $target = Filesystem::normalizePath('files_trashbin/files/' . $filename);
681
+        return $view->file_exists($target);
682
+    }
683
+
684
+    /**
685
+     * deletes used space for trash bin in db if user was deleted
686
+     *
687
+     * @param string $uid id of deleted user
688
+     * @return bool result of db delete operation
689
+     */
690
+    public static function deleteUser($uid) {
691
+        $query = \OC_DB::prepare('DELETE FROM `*PREFIX*files_trash` WHERE `user`=?');
692
+        return $query->execute([$uid]);
693
+    }
694
+
695
+    /**
696
+     * calculate remaining free space for trash bin
697
+     *
698
+     * @param integer $trashbinSize current size of the trash bin
699
+     * @param string $user
700
+     * @return int available free space for trash bin
701
+     */
702
+    private static function calculateFreeSpace($trashbinSize, $user) {
703
+        $config = \OC::$server->getConfig();
704
+        $systemTrashbinSize = (int)$config->getAppValue('files_trashbin', 'trashbin_size', '-1');
705
+        $userTrashbinSize = (int)$config->getUserValue($user, 'files_trashbin', 'trashbin_size', '-1');
706
+        $configuredTrashbinSize = ($userTrashbinSize < 0) ? $systemTrashbinSize : $userTrashbinSize;
707
+        if ($configuredTrashbinSize) {
708
+            return $configuredTrashbinSize - $trashbinSize;
709
+        }
710
+
711
+        $softQuota = true;
712
+        $userObject = \OC::$server->getUserManager()->get($user);
713
+        if (is_null($userObject)) {
714
+            return 0;
715
+        }
716
+        $quota = $userObject->getQuota();
717
+        if ($quota === null || $quota === 'none') {
718
+            $quota = Filesystem::free_space('/');
719
+            $softQuota = false;
720
+            // inf or unknown free space
721
+            if ($quota < 0) {
722
+                $quota = PHP_INT_MAX;
723
+            }
724
+        } else {
725
+            $quota = \OCP\Util::computerFileSize($quota);
726
+        }
727
+
728
+        // calculate available space for trash bin
729
+        // subtract size of files and current trash bin size from quota
730
+        if ($softQuota) {
731
+            $userFolder = \OC::$server->getUserFolder($user);
732
+            if (is_null($userFolder)) {
733
+                return 0;
734
+            }
735
+            $free = $quota - $userFolder->getSize(false); // remaining free space for user
736
+            if ($free > 0) {
737
+                $availableSpace = ($free * self::DEFAULTMAXSIZE / 100) - $trashbinSize; // how much space can be used for versions
738
+            } else {
739
+                $availableSpace = $free - $trashbinSize;
740
+            }
741
+        } else {
742
+            $availableSpace = $quota;
743
+        }
744
+
745
+        return $availableSpace;
746
+    }
747
+
748
+    /**
749
+     * resize trash bin if necessary after a new file was added to Nextcloud
750
+     *
751
+     * @param string $user user id
752
+     */
753
+    public static function resizeTrash($user) {
754
+        $size = self::getTrashbinSize($user);
755
+
756
+        $freeSpace = self::calculateFreeSpace($size, $user);
757
+
758
+        if ($freeSpace < 0) {
759
+            self::scheduleExpire($user);
760
+        }
761
+    }
762
+
763
+    /**
764
+     * clean up the trash bin
765
+     *
766
+     * @param string $user
767
+     */
768
+    public static function expire($user) {
769
+        $trashBinSize = self::getTrashbinSize($user);
770
+        $availableSpace = self::calculateFreeSpace($trashBinSize, $user);
771
+
772
+        $dirContent = Helper::getTrashFiles('/', $user, 'mtime');
773
+
774
+        // delete all files older then $retention_obligation
775
+        [$delSize, $count] = self::deleteExpiredFiles($dirContent, $user);
776
+
777
+        $availableSpace += $delSize;
778
+
779
+        // delete files from trash until we meet the trash bin size limit again
780
+        self::deleteFiles(array_slice($dirContent, $count), $user, $availableSpace);
781
+    }
782
+
783
+    /**
784
+     * @param string $user
785
+     */
786
+    private static function scheduleExpire($user) {
787
+        // let the admin disable auto expire
788
+        /** @var Application $application */
789
+        $application = \OC::$server->query(Application::class);
790
+        $expiration = $application->getContainer()->query('Expiration');
791
+        if ($expiration->isEnabled()) {
792
+            \OC::$server->getCommandBus()->push(new Expire($user));
793
+        }
794
+    }
795
+
796
+    /**
797
+     * if the size limit for the trash bin is reached, we delete the oldest
798
+     * files in the trash bin until we meet the limit again
799
+     *
800
+     * @param array $files
801
+     * @param string $user
802
+     * @param int $availableSpace available disc space
803
+     * @return int size of deleted files
804
+     */
805
+    protected static function deleteFiles($files, $user, $availableSpace) {
806
+        /** @var Application $application */
807
+        $application = \OC::$server->query(Application::class);
808
+        $expiration = $application->getContainer()->query('Expiration');
809
+        $size = 0;
810
+
811
+        if ($availableSpace < 0) {
812
+            foreach ($files as $file) {
813
+                if ($availableSpace < 0 && $expiration->isExpired($file['mtime'], true)) {
814
+                    $tmp = self::delete($file['name'], $user, $file['mtime']);
815
+                    \OC::$server->getLogger()->info('remove "' . $file['name'] . '" (' . $tmp . 'B) to meet the limit of trash bin size (50% of available quota)', ['app' => 'files_trashbin']);
816
+                    $availableSpace += $tmp;
817
+                    $size += $tmp;
818
+                } else {
819
+                    break;
820
+                }
821
+            }
822
+        }
823
+        return $size;
824
+    }
825
+
826
+    /**
827
+     * delete files older then max storage time
828
+     *
829
+     * @param array $files list of files sorted by mtime
830
+     * @param string $user
831
+     * @return integer[] size of deleted files and number of deleted files
832
+     */
833
+    public static function deleteExpiredFiles($files, $user) {
834
+        /** @var Expiration $expiration */
835
+        $expiration = \OC::$server->query(Expiration::class);
836
+        $size = 0;
837
+        $count = 0;
838
+        foreach ($files as $file) {
839
+            $timestamp = $file['mtime'];
840
+            $filename = $file['name'];
841
+            if ($expiration->isExpired($timestamp)) {
842
+                try {
843
+                    $size += self::delete($filename, $user, $timestamp);
844
+                    $count++;
845
+                } catch (\OCP\Files\NotPermittedException $e) {
846
+                    \OC::$server->getLogger()->logException($e, ['app' => 'files_trashbin', 'level' => \OCP\ILogger::WARN, 'message' => 'Removing "' . $filename . '" from trashbin failed.']);
847
+                }
848
+                \OC::$server->getLogger()->info(
849
+                    'Remove "' . $filename . '" from trashbin because it exceeds max retention obligation term.',
850
+                    ['app' => 'files_trashbin']
851
+                );
852
+            } else {
853
+                break;
854
+            }
855
+        }
856
+
857
+        return [$size, $count];
858
+    }
859
+
860
+    /**
861
+     * recursive copy to copy a whole directory
862
+     *
863
+     * @param string $source source path, relative to the users files directory
864
+     * @param string $destination destination path relative to the users root directoy
865
+     * @param View $view file view for the users root directory
866
+     * @return int
867
+     * @throws Exceptions\CopyRecursiveException
868
+     */
869
+    private static function copy_recursive($source, $destination, View $view) {
870
+        $size = 0;
871
+        if ($view->is_dir($source)) {
872
+            $view->mkdir($destination);
873
+            $view->touch($destination, $view->filemtime($source));
874
+            foreach ($view->getDirectoryContent($source) as $i) {
875
+                $pathDir = $source . '/' . $i['name'];
876
+                if ($view->is_dir($pathDir)) {
877
+                    $size += self::copy_recursive($pathDir, $destination . '/' . $i['name'], $view);
878
+                } else {
879
+                    $size += $view->filesize($pathDir);
880
+                    $result = $view->copy($pathDir, $destination . '/' . $i['name']);
881
+                    if (!$result) {
882
+                        throw new \OCA\Files_Trashbin\Exceptions\CopyRecursiveException();
883
+                    }
884
+                    $view->touch($destination . '/' . $i['name'], $view->filemtime($pathDir));
885
+                }
886
+            }
887
+        } else {
888
+            $size += $view->filesize($source);
889
+            $result = $view->copy($source, $destination);
890
+            if (!$result) {
891
+                throw new \OCA\Files_Trashbin\Exceptions\CopyRecursiveException();
892
+            }
893
+            $view->touch($destination, $view->filemtime($source));
894
+        }
895
+        return $size;
896
+    }
897
+
898
+    /**
899
+     * find all versions which belong to the file we want to restore
900
+     *
901
+     * @param string $filename name of the file which should be restored
902
+     * @param int $timestamp timestamp when the file was deleted
903
+     * @return array
904
+     */
905
+    private static function getVersionsFromTrash($filename, $timestamp, $user) {
906
+        $view = new View('/' . $user . '/files_trashbin/versions');
907
+        $versions = [];
908
+
909
+        //force rescan of versions, local storage may not have updated the cache
910
+        if (!self::$scannedVersions) {
911
+            /** @var \OC\Files\Storage\Storage $storage */
912
+            [$storage,] = $view->resolvePath('/');
913
+            $storage->getScanner()->scan('files_trashbin/versions');
914
+            self::$scannedVersions = true;
915
+        }
916
+
917
+        if ($timestamp) {
918
+            // fetch for old versions
919
+            $matches = $view->searchRaw($filename . '.v%.d' . $timestamp);
920
+            $offset = -strlen($timestamp) - 2;
921
+        } else {
922
+            $matches = $view->searchRaw($filename . '.v%');
923
+        }
924
+
925
+        if (is_array($matches)) {
926
+            foreach ($matches as $ma) {
927
+                if ($timestamp) {
928
+                    $parts = explode('.v', substr($ma['path'], 0, $offset));
929
+                    $versions[] = end($parts);
930
+                } else {
931
+                    $parts = explode('.v', $ma);
932
+                    $versions[] = end($parts);
933
+                }
934
+            }
935
+        }
936
+        return $versions;
937
+    }
938
+
939
+    /**
940
+     * find unique extension for restored file if a file with the same name already exists
941
+     *
942
+     * @param string $location where the file should be restored
943
+     * @param string $filename name of the file
944
+     * @param View $view filesystem view relative to users root directory
945
+     * @return string with unique extension
946
+     */
947
+    private static function getUniqueFilename($location, $filename, View $view) {
948
+        $ext = pathinfo($filename, PATHINFO_EXTENSION);
949
+        $name = pathinfo($filename, PATHINFO_FILENAME);
950
+        $l = \OC::$server->getL10N('files_trashbin');
951
+
952
+        $location = '/' . trim($location, '/');
953
+
954
+        // if extension is not empty we set a dot in front of it
955
+        if ($ext !== '') {
956
+            $ext = '.' . $ext;
957
+        }
958
+
959
+        if ($view->file_exists('files' . $location . '/' . $filename)) {
960
+            $i = 2;
961
+            $uniqueName = $name . " (" . $l->t("restored") . ")" . $ext;
962
+            while ($view->file_exists('files' . $location . '/' . $uniqueName)) {
963
+                $uniqueName = $name . " (" . $l->t("restored") . " " . $i . ")" . $ext;
964
+                $i++;
965
+            }
966
+
967
+            return $uniqueName;
968
+        }
969
+
970
+        return $filename;
971
+    }
972
+
973
+    /**
974
+     * get the size from a given root folder
975
+     *
976
+     * @param View $view file view on the root folder
977
+     * @return integer size of the folder
978
+     */
979
+    private static function calculateSize($view) {
980
+        $root = \OC::$server->getConfig()->getSystemValue('datadirectory', \OC::$SERVERROOT . '/data') . $view->getAbsolutePath('');
981
+        if (!file_exists($root)) {
982
+            return 0;
983
+        }
984
+        $iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($root), \RecursiveIteratorIterator::CHILD_FIRST);
985
+        $size = 0;
986
+
987
+        /**
988
+         * RecursiveDirectoryIterator on an NFS path isn't iterable with foreach
989
+         * This bug is fixed in PHP 5.5.9 or before
990
+         * See #8376
991
+         */
992
+        $iterator->rewind();
993
+        while ($iterator->valid()) {
994
+            $path = $iterator->current();
995
+            $relpath = substr($path, strlen($root) - 1);
996
+            if (!$view->is_dir($relpath)) {
997
+                $size += $view->filesize($relpath);
998
+            }
999
+            $iterator->next();
1000
+        }
1001
+        return $size;
1002
+    }
1003
+
1004
+    /**
1005
+     * get current size of trash bin from a given user
1006
+     *
1007
+     * @param string $user user who owns the trash bin
1008
+     * @return integer trash bin size
1009
+     */
1010
+    private static function getTrashbinSize($user) {
1011
+        $view = new View('/' . $user);
1012
+        $fileInfo = $view->getFileInfo('/files_trashbin');
1013
+        return isset($fileInfo['size']) ? $fileInfo['size'] : 0;
1014
+    }
1015
+
1016
+    /**
1017
+     * register hooks
1018
+     */
1019
+    public static function registerHooks() {
1020
+        // create storage wrapper on setup
1021
+        \OCP\Util::connectHook('OC_Filesystem', 'preSetup', 'OCA\Files_Trashbin\Storage', 'setupStorage');
1022
+        //Listen to delete user signal
1023
+        \OCP\Util::connectHook('OC_User', 'pre_deleteUser', 'OCA\Files_Trashbin\Hooks', 'deleteUser_hook');
1024
+        //Listen to post write hook
1025
+        \OCP\Util::connectHook('OC_Filesystem', 'post_write', 'OCA\Files_Trashbin\Hooks', 'post_write_hook');
1026
+        // pre and post-rename, disable trash logic for the copy+unlink case
1027
+        \OCP\Util::connectHook('OC_Filesystem', 'delete', 'OCA\Files_Trashbin\Trashbin', 'ensureFileScannedHook');
1028
+    }
1029
+
1030
+    /**
1031
+     * check if trash bin is empty for a given user
1032
+     *
1033
+     * @param string $user
1034
+     * @return bool
1035
+     */
1036
+    public static function isEmpty($user) {
1037
+        $view = new View('/' . $user . '/files_trashbin');
1038
+        if ($view->is_dir('/files') && $dh = $view->opendir('/files')) {
1039
+            while ($file = readdir($dh)) {
1040
+                if (!Filesystem::isIgnoredDir($file)) {
1041
+                    return false;
1042
+                }
1043
+            }
1044
+        }
1045
+        return true;
1046
+    }
1047
+
1048
+    /**
1049
+     * @param $path
1050
+     * @return string
1051
+     */
1052
+    public static function preview_icon($path) {
1053
+        return \OC::$server->getURLGenerator()->linkToRoute('core_ajax_trashbin_preview', ['x' => 32, 'y' => 32, 'file' => $path]);
1054
+    }
1055 1055
 }
Please login to merge, or discard this patch.
Spacing   +70 added lines, -70 removed lines patch added patch discarded remove patch
@@ -106,7 +106,7 @@  discard block
 block discarded – undo
106 106
 		Filesystem::initMountPoints($uid);
107 107
 		if ($uid !== User::getUser()) {
108 108
 			$info = Filesystem::getFileInfo($filename);
109
-			$ownerView = new View('/' . $uid . '/files');
109
+			$ownerView = new View('/'.$uid.'/files');
110 110
 			try {
111 111
 				$filename = $ownerView->getPath($info['fileid']);
112 112
 			} catch (NotFoundException $e) {
@@ -157,7 +157,7 @@  discard block
 block discarded – undo
157 157
 	}
158 158
 
159 159
 	private static function setUpTrash($user) {
160
-		$view = new View('/' . $user);
160
+		$view = new View('/'.$user);
161 161
 		if (!$view->is_dir('files_trashbin')) {
162 162
 			$view->mkdir('files_trashbin');
163 163
 		}
@@ -192,8 +192,8 @@  discard block
 block discarded – undo
192 192
 
193 193
 		$view = new View('/');
194 194
 
195
-		$target = $user . '/files_trashbin/files/' . $targetFilename . '.d' . $timestamp;
196
-		$source = $owner . '/files_trashbin/files/' . $sourceFilename . '.d' . $timestamp;
195
+		$target = $user.'/files_trashbin/files/'.$targetFilename.'.d'.$timestamp;
196
+		$source = $owner.'/files_trashbin/files/'.$sourceFilename.'.d'.$timestamp;
197 197
 		$free = $view->free_space($target);
198 198
 		$isUnknownOrUnlimitedFreeSpace = $free < 0;
199 199
 		$isEnoughFreeSpaceLeft = $view->filesize($source) < $free;
@@ -232,9 +232,9 @@  discard block
 block discarded – undo
232 232
 			$ownerPath = $file_path;
233 233
 		}
234 234
 
235
-		$ownerView = new View('/' . $owner);
235
+		$ownerView = new View('/'.$owner);
236 236
 		// file has been deleted in between
237
-		if (is_null($ownerPath) || $ownerPath === '' || !$ownerView->file_exists('/files/' . $ownerPath)) {
237
+		if (is_null($ownerPath) || $ownerPath === '' || !$ownerView->file_exists('/files/'.$ownerPath)) {
238 238
 			return true;
239 239
 		}
240 240
 
@@ -255,7 +255,7 @@  discard block
 block discarded – undo
255 255
 		$lockingProvider = \OC::$server->getLockingProvider();
256 256
 
257 257
 		// disable proxy to prevent recursive calls
258
-		$trashPath = '/files_trashbin/files/' . $filename . '.d' . $timestamp;
258
+		$trashPath = '/files_trashbin/files/'.$filename.'.d'.$timestamp;
259 259
 		$gotLock = false;
260 260
 
261 261
 		while (!$gotLock) {
@@ -271,12 +271,12 @@  discard block
 block discarded – undo
271 271
 
272 272
 				$timestamp = $timestamp + 1;
273 273
 
274
-				$trashPath = '/files_trashbin/files/' . $filename . '.d' . $timestamp;
274
+				$trashPath = '/files_trashbin/files/'.$filename.'.d'.$timestamp;
275 275
 			}
276 276
 		}
277 277
 
278 278
 		/** @var \OC\Files\Storage\Storage $sourceStorage */
279
-		[$sourceStorage, $sourceInternalPath] = $ownerView->resolvePath('/files/' . $ownerPath);
279
+		[$sourceStorage, $sourceInternalPath] = $ownerView->resolvePath('/files/'.$ownerPath);
280 280
 
281 281
 		$connection = \OC::$server->getDatabaseConnection();
282 282
 		$connection->beginTransaction();
@@ -293,7 +293,7 @@  discard block
 block discarded – undo
293 293
 			if ($trashStorage->file_exists($trashInternalPath)) {
294 294
 				$trashStorage->unlink($trashInternalPath);
295 295
 			}
296
-			\OC::$server->getLogger()->error('Couldn\'t move ' . $file_path . ' to the trash bin', ['app' => 'files_trashbin']);
296
+			\OC::$server->getLogger()->error('Couldn\'t move '.$file_path.' to the trash bin', ['app' => 'files_trashbin']);
297 297
 		}
298 298
 
299 299
 		if ($sourceStorage->file_exists($sourceInternalPath)) { // failed to delete the original file, abort
@@ -315,7 +315,7 @@  discard block
 block discarded – undo
315 315
 				\OC::$server->getLogger()->error('trash bin database couldn\'t be updated', ['app' => 'files_trashbin']);
316 316
 			}
317 317
 			\OCP\Util::emitHook('\OCA\Files_Trashbin\Trashbin', 'post_moveToTrash', ['filePath' => Filesystem::normalizePath($file_path),
318
-				'trashPath' => Filesystem::normalizePath($filename . '.d' . $timestamp)]);
318
+				'trashPath' => Filesystem::normalizePath($filename.'.d'.$timestamp)]);
319 319
 
320 320
 			self::retainVersions($filename, $owner, $ownerPath, $timestamp);
321 321
 
@@ -350,17 +350,17 @@  discard block
 block discarded – undo
350 350
 			$user = User::getUser();
351 351
 			$rootView = new View('/');
352 352
 
353
-			if ($rootView->is_dir($owner . '/files_versions/' . $ownerPath)) {
353
+			if ($rootView->is_dir($owner.'/files_versions/'.$ownerPath)) {
354 354
 				if ($owner !== $user) {
355
-					self::copy_recursive($owner . '/files_versions/' . $ownerPath, $owner . '/files_trashbin/versions/' . basename($ownerPath) . '.d' . $timestamp, $rootView);
355
+					self::copy_recursive($owner.'/files_versions/'.$ownerPath, $owner.'/files_trashbin/versions/'.basename($ownerPath).'.d'.$timestamp, $rootView);
356 356
 				}
357
-				self::move($rootView, $owner . '/files_versions/' . $ownerPath, $user . '/files_trashbin/versions/' . $filename . '.d' . $timestamp);
357
+				self::move($rootView, $owner.'/files_versions/'.$ownerPath, $user.'/files_trashbin/versions/'.$filename.'.d'.$timestamp);
358 358
 			} elseif ($versions = \OCA\Files_Versions\Storage::getVersions($owner, $ownerPath)) {
359 359
 				foreach ($versions as $v) {
360 360
 					if ($owner !== $user) {
361
-						self::copy($rootView, $owner . '/files_versions' . $v['path'] . '.v' . $v['version'], $owner . '/files_trashbin/versions/' . $v['name'] . '.v' . $v['version'] . '.d' . $timestamp);
361
+						self::copy($rootView, $owner.'/files_versions'.$v['path'].'.v'.$v['version'], $owner.'/files_trashbin/versions/'.$v['name'].'.v'.$v['version'].'.d'.$timestamp);
362 362
 					}
363
-					self::move($rootView, $owner . '/files_versions' . $v['path'] . '.v' . $v['version'], $user . '/files_trashbin/versions/' . $filename . '.v' . $v['version'] . '.d' . $timestamp);
363
+					self::move($rootView, $owner.'/files_versions'.$v['path'].'.v'.$v['version'], $user.'/files_trashbin/versions/'.$filename.'.v'.$v['version'].'.d'.$timestamp);
364 364
 				}
365 365
 			}
366 366
 		}
@@ -422,18 +422,18 @@  discard block
 block discarded – undo
422 422
 	 */
423 423
 	public static function restore($file, $filename, $timestamp) {
424 424
 		$user = User::getUser();
425
-		$view = new View('/' . $user);
425
+		$view = new View('/'.$user);
426 426
 
427 427
 		$location = '';
428 428
 		if ($timestamp) {
429 429
 			$location = self::getLocation($user, $filename, $timestamp);
430 430
 			if ($location === false) {
431
-				\OC::$server->getLogger()->error('trash bin database inconsistent! ($user: ' . $user . ' $filename: ' . $filename . ', $timestamp: ' . $timestamp . ')', ['app' => 'files_trashbin']);
431
+				\OC::$server->getLogger()->error('trash bin database inconsistent! ($user: '.$user.' $filename: '.$filename.', $timestamp: '.$timestamp.')', ['app' => 'files_trashbin']);
432 432
 			} else {
433 433
 				// if location no longer exists, restore file in the root directory
434 434
 				if ($location !== '/' &&
435
-					(!$view->is_dir('files/' . $location) ||
436
-						!$view->isCreatable('files/' . $location))
435
+					(!$view->is_dir('files/'.$location) ||
436
+						!$view->isCreatable('files/'.$location))
437 437
 				) {
438 438
 					$location = '';
439 439
 				}
@@ -443,8 +443,8 @@  discard block
 block discarded – undo
443 443
 		// we need a  extension in case a file/dir with the same name already exists
444 444
 		$uniqueFilename = self::getUniqueFilename($location, $filename, $view);
445 445
 
446
-		$source = Filesystem::normalizePath('files_trashbin/files/' . $file);
447
-		$target = Filesystem::normalizePath('files/' . $location . '/' . $uniqueFilename);
446
+		$source = Filesystem::normalizePath('files_trashbin/files/'.$file);
447
+		$target = Filesystem::normalizePath('files/'.$location.'/'.$uniqueFilename);
448 448
 		if (!$view->file_exists($source)) {
449 449
 			return false;
450 450
 		}
@@ -459,10 +459,10 @@  discard block
 block discarded – undo
459 459
 		// handle the restore result
460 460
 		if ($restoreResult) {
461 461
 			$fakeRoot = $view->getRoot();
462
-			$view->chroot('/' . $user . '/files');
463
-			$view->touch('/' . $location . '/' . $uniqueFilename, $mtime);
462
+			$view->chroot('/'.$user.'/files');
463
+			$view->touch('/'.$location.'/'.$uniqueFilename, $mtime);
464 464
 			$view->chroot($fakeRoot);
465
-			\OCP\Util::emitHook('\OCA\Files_Trashbin\Trashbin', 'post_restore', ['filePath' => Filesystem::normalizePath('/' . $location . '/' . $uniqueFilename),
465
+			\OCP\Util::emitHook('\OCA\Files_Trashbin\Trashbin', 'post_restore', ['filePath' => Filesystem::normalizePath('/'.$location.'/'.$uniqueFilename),
466 466
 				'trashPath' => Filesystem::normalizePath($file)]);
467 467
 
468 468
 			self::restoreVersions($view, $file, $filename, $uniqueFilename, $location, $timestamp);
@@ -494,7 +494,7 @@  discard block
 block discarded – undo
494 494
 			$user = User::getUser();
495 495
 			$rootView = new View('/');
496 496
 
497
-			$target = Filesystem::normalizePath('/' . $location . '/' . $uniqueFilename);
497
+			$target = Filesystem::normalizePath('/'.$location.'/'.$uniqueFilename);
498 498
 
499 499
 			[$owner, $ownerPath] = self::getUidAndFilename($target);
500 500
 
@@ -509,14 +509,14 @@  discard block
 block discarded – undo
509 509
 				$versionedFile = $file;
510 510
 			}
511 511
 
512
-			if ($view->is_dir('/files_trashbin/versions/' . $file)) {
513
-				$rootView->rename(Filesystem::normalizePath($user . '/files_trashbin/versions/' . $file), Filesystem::normalizePath($owner . '/files_versions/' . $ownerPath));
512
+			if ($view->is_dir('/files_trashbin/versions/'.$file)) {
513
+				$rootView->rename(Filesystem::normalizePath($user.'/files_trashbin/versions/'.$file), Filesystem::normalizePath($owner.'/files_versions/'.$ownerPath));
514 514
 			} elseif ($versions = self::getVersionsFromTrash($versionedFile, $timestamp, $user)) {
515 515
 				foreach ($versions as $v) {
516 516
 					if ($timestamp) {
517
-						$rootView->rename($user . '/files_trashbin/versions/' . $versionedFile . '.v' . $v . '.d' . $timestamp, $owner . '/files_versions/' . $ownerPath . '.v' . $v);
517
+						$rootView->rename($user.'/files_trashbin/versions/'.$versionedFile.'.v'.$v.'.d'.$timestamp, $owner.'/files_versions/'.$ownerPath.'.v'.$v);
518 518
 					} else {
519
-						$rootView->rename($user . '/files_trashbin/versions/' . $versionedFile . '.v' . $v, $owner . '/files_versions/' . $ownerPath . '.v' . $v);
519
+						$rootView->rename($user.'/files_trashbin/versions/'.$versionedFile.'.v'.$v, $owner.'/files_versions/'.$ownerPath.'.v'.$v);
520 520
 					}
521 521
 				}
522 522
 			}
@@ -529,7 +529,7 @@  discard block
 block discarded – undo
529 529
 	public static function deleteAll() {
530 530
 		$user = User::getUser();
531 531
 		$userRoot = \OC::$server->getUserFolder($user)->getParent();
532
-		$view = new View('/' . $user);
532
+		$view = new View('/'.$user);
533 533
 		$fileInfos = $view->getDirectoryContent('files_trashbin/files');
534 534
 
535 535
 		try {
@@ -601,13 +601,13 @@  discard block
 block discarded – undo
601 601
 	 */
602 602
 	public static function delete($filename, $user, $timestamp = null) {
603 603
 		$userRoot = \OC::$server->getUserFolder($user)->getParent();
604
-		$view = new View('/' . $user);
604
+		$view = new View('/'.$user);
605 605
 		$size = 0;
606 606
 
607 607
 		if ($timestamp) {
608 608
 			$query = \OC_DB::prepare('DELETE FROM `*PREFIX*files_trash` WHERE `user`=? AND `id`=? AND `timestamp`=?');
609 609
 			$query->execute([$user, $filename, $timestamp]);
610
-			$file = $filename . '.d' . $timestamp;
610
+			$file = $filename.'.d'.$timestamp;
611 611
 		} else {
612 612
 			$file = $filename;
613 613
 		}
@@ -615,20 +615,20 @@  discard block
 block discarded – undo
615 615
 		$size += self::deleteVersions($view, $file, $filename, $timestamp, $user);
616 616
 
617 617
 		try {
618
-			$node = $userRoot->get('/files_trashbin/files/' . $file);
618
+			$node = $userRoot->get('/files_trashbin/files/'.$file);
619 619
 		} catch (NotFoundException $e) {
620 620
 			return $size;
621 621
 		}
622 622
 
623 623
 		if ($node instanceof Folder) {
624
-			$size += self::calculateSize(new View('/' . $user . '/files_trashbin/files/' . $file));
624
+			$size += self::calculateSize(new View('/'.$user.'/files_trashbin/files/'.$file));
625 625
 		} elseif ($node instanceof File) {
626
-			$size += $view->filesize('/files_trashbin/files/' . $file);
626
+			$size += $view->filesize('/files_trashbin/files/'.$file);
627 627
 		}
628 628
 
629
-		self::emitTrashbinPreDelete('/files_trashbin/files/' . $file);
629
+		self::emitTrashbinPreDelete('/files_trashbin/files/'.$file);
630 630
 		$node->delete();
631
-		self::emitTrashbinPostDelete('/files_trashbin/files/' . $file);
631
+		self::emitTrashbinPostDelete('/files_trashbin/files/'.$file);
632 632
 
633 633
 		return $size;
634 634
 	}
@@ -644,17 +644,17 @@  discard block
 block discarded – undo
644 644
 	private static function deleteVersions(View $view, $file, $filename, $timestamp, $user) {
645 645
 		$size = 0;
646 646
 		if (\OCP\App::isEnabled('files_versions')) {
647
-			if ($view->is_dir('files_trashbin/versions/' . $file)) {
648
-				$size += self::calculateSize(new View('/' . $user . '/files_trashbin/versions/' . $file));
649
-				$view->unlink('files_trashbin/versions/' . $file);
647
+			if ($view->is_dir('files_trashbin/versions/'.$file)) {
648
+				$size += self::calculateSize(new View('/'.$user.'/files_trashbin/versions/'.$file));
649
+				$view->unlink('files_trashbin/versions/'.$file);
650 650
 			} elseif ($versions = self::getVersionsFromTrash($filename, $timestamp, $user)) {
651 651
 				foreach ($versions as $v) {
652 652
 					if ($timestamp) {
653
-						$size += $view->filesize('/files_trashbin/versions/' . $filename . '.v' . $v . '.d' . $timestamp);
654
-						$view->unlink('/files_trashbin/versions/' . $filename . '.v' . $v . '.d' . $timestamp);
653
+						$size += $view->filesize('/files_trashbin/versions/'.$filename.'.v'.$v.'.d'.$timestamp);
654
+						$view->unlink('/files_trashbin/versions/'.$filename.'.v'.$v.'.d'.$timestamp);
655 655
 					} else {
656
-						$size += $view->filesize('/files_trashbin/versions/' . $filename . '.v' . $v);
657
-						$view->unlink('/files_trashbin/versions/' . $filename . '.v' . $v);
656
+						$size += $view->filesize('/files_trashbin/versions/'.$filename.'.v'.$v);
657
+						$view->unlink('/files_trashbin/versions/'.$filename.'.v'.$v);
658 658
 					}
659 659
 				}
660 660
 			}
@@ -671,13 +671,13 @@  discard block
 block discarded – undo
671 671
 	 */
672 672
 	public static function file_exists($filename, $timestamp = null) {
673 673
 		$user = User::getUser();
674
-		$view = new View('/' . $user);
674
+		$view = new View('/'.$user);
675 675
 
676 676
 		if ($timestamp) {
677
-			$filename = $filename . '.d' . $timestamp;
677
+			$filename = $filename.'.d'.$timestamp;
678 678
 		}
679 679
 
680
-		$target = Filesystem::normalizePath('files_trashbin/files/' . $filename);
680
+		$target = Filesystem::normalizePath('files_trashbin/files/'.$filename);
681 681
 		return $view->file_exists($target);
682 682
 	}
683 683
 
@@ -701,8 +701,8 @@  discard block
 block discarded – undo
701 701
 	 */
702 702
 	private static function calculateFreeSpace($trashbinSize, $user) {
703 703
 		$config = \OC::$server->getConfig();
704
-		$systemTrashbinSize = (int)$config->getAppValue('files_trashbin', 'trashbin_size', '-1');
705
-		$userTrashbinSize = (int)$config->getUserValue($user, 'files_trashbin', 'trashbin_size', '-1');
704
+		$systemTrashbinSize = (int) $config->getAppValue('files_trashbin', 'trashbin_size', '-1');
705
+		$userTrashbinSize = (int) $config->getUserValue($user, 'files_trashbin', 'trashbin_size', '-1');
706 706
 		$configuredTrashbinSize = ($userTrashbinSize < 0) ? $systemTrashbinSize : $userTrashbinSize;
707 707
 		if ($configuredTrashbinSize) {
708 708
 			return $configuredTrashbinSize - $trashbinSize;
@@ -812,7 +812,7 @@  discard block
 block discarded – undo
812 812
 			foreach ($files as $file) {
813 813
 				if ($availableSpace < 0 && $expiration->isExpired($file['mtime'], true)) {
814 814
 					$tmp = self::delete($file['name'], $user, $file['mtime']);
815
-					\OC::$server->getLogger()->info('remove "' . $file['name'] . '" (' . $tmp . 'B) to meet the limit of trash bin size (50% of available quota)', ['app' => 'files_trashbin']);
815
+					\OC::$server->getLogger()->info('remove "'.$file['name'].'" ('.$tmp.'B) to meet the limit of trash bin size (50% of available quota)', ['app' => 'files_trashbin']);
816 816
 					$availableSpace += $tmp;
817 817
 					$size += $tmp;
818 818
 				} else {
@@ -843,10 +843,10 @@  discard block
 block discarded – undo
843 843
 					$size += self::delete($filename, $user, $timestamp);
844 844
 					$count++;
845 845
 				} catch (\OCP\Files\NotPermittedException $e) {
846
-					\OC::$server->getLogger()->logException($e, ['app' => 'files_trashbin', 'level' => \OCP\ILogger::WARN, 'message' => 'Removing "' . $filename . '" from trashbin failed.']);
846
+					\OC::$server->getLogger()->logException($e, ['app' => 'files_trashbin', 'level' => \OCP\ILogger::WARN, 'message' => 'Removing "'.$filename.'" from trashbin failed.']);
847 847
 				}
848 848
 				\OC::$server->getLogger()->info(
849
-					'Remove "' . $filename . '" from trashbin because it exceeds max retention obligation term.',
849
+					'Remove "'.$filename.'" from trashbin because it exceeds max retention obligation term.',
850 850
 					['app' => 'files_trashbin']
851 851
 				);
852 852
 			} else {
@@ -872,16 +872,16 @@  discard block
 block discarded – undo
872 872
 			$view->mkdir($destination);
873 873
 			$view->touch($destination, $view->filemtime($source));
874 874
 			foreach ($view->getDirectoryContent($source) as $i) {
875
-				$pathDir = $source . '/' . $i['name'];
875
+				$pathDir = $source.'/'.$i['name'];
876 876
 				if ($view->is_dir($pathDir)) {
877
-					$size += self::copy_recursive($pathDir, $destination . '/' . $i['name'], $view);
877
+					$size += self::copy_recursive($pathDir, $destination.'/'.$i['name'], $view);
878 878
 				} else {
879 879
 					$size += $view->filesize($pathDir);
880
-					$result = $view->copy($pathDir, $destination . '/' . $i['name']);
880
+					$result = $view->copy($pathDir, $destination.'/'.$i['name']);
881 881
 					if (!$result) {
882 882
 						throw new \OCA\Files_Trashbin\Exceptions\CopyRecursiveException();
883 883
 					}
884
-					$view->touch($destination . '/' . $i['name'], $view->filemtime($pathDir));
884
+					$view->touch($destination.'/'.$i['name'], $view->filemtime($pathDir));
885 885
 				}
886 886
 			}
887 887
 		} else {
@@ -903,23 +903,23 @@  discard block
 block discarded – undo
903 903
 	 * @return array
904 904
 	 */
905 905
 	private static function getVersionsFromTrash($filename, $timestamp, $user) {
906
-		$view = new View('/' . $user . '/files_trashbin/versions');
906
+		$view = new View('/'.$user.'/files_trashbin/versions');
907 907
 		$versions = [];
908 908
 
909 909
 		//force rescan of versions, local storage may not have updated the cache
910 910
 		if (!self::$scannedVersions) {
911 911
 			/** @var \OC\Files\Storage\Storage $storage */
912
-			[$storage,] = $view->resolvePath('/');
912
+			[$storage, ] = $view->resolvePath('/');
913 913
 			$storage->getScanner()->scan('files_trashbin/versions');
914 914
 			self::$scannedVersions = true;
915 915
 		}
916 916
 
917 917
 		if ($timestamp) {
918 918
 			// fetch for old versions
919
-			$matches = $view->searchRaw($filename . '.v%.d' . $timestamp);
919
+			$matches = $view->searchRaw($filename.'.v%.d'.$timestamp);
920 920
 			$offset = -strlen($timestamp) - 2;
921 921
 		} else {
922
-			$matches = $view->searchRaw($filename . '.v%');
922
+			$matches = $view->searchRaw($filename.'.v%');
923 923
 		}
924 924
 
925 925
 		if (is_array($matches)) {
@@ -949,18 +949,18 @@  discard block
 block discarded – undo
949 949
 		$name = pathinfo($filename, PATHINFO_FILENAME);
950 950
 		$l = \OC::$server->getL10N('files_trashbin');
951 951
 
952
-		$location = '/' . trim($location, '/');
952
+		$location = '/'.trim($location, '/');
953 953
 
954 954
 		// if extension is not empty we set a dot in front of it
955 955
 		if ($ext !== '') {
956
-			$ext = '.' . $ext;
956
+			$ext = '.'.$ext;
957 957
 		}
958 958
 
959
-		if ($view->file_exists('files' . $location . '/' . $filename)) {
959
+		if ($view->file_exists('files'.$location.'/'.$filename)) {
960 960
 			$i = 2;
961
-			$uniqueName = $name . " (" . $l->t("restored") . ")" . $ext;
962
-			while ($view->file_exists('files' . $location . '/' . $uniqueName)) {
963
-				$uniqueName = $name . " (" . $l->t("restored") . " " . $i . ")" . $ext;
961
+			$uniqueName = $name." (".$l->t("restored").")".$ext;
962
+			while ($view->file_exists('files'.$location.'/'.$uniqueName)) {
963
+				$uniqueName = $name." (".$l->t("restored")." ".$i.")".$ext;
964 964
 				$i++;
965 965
 			}
966 966
 
@@ -977,7 +977,7 @@  discard block
 block discarded – undo
977 977
 	 * @return integer size of the folder
978 978
 	 */
979 979
 	private static function calculateSize($view) {
980
-		$root = \OC::$server->getConfig()->getSystemValue('datadirectory', \OC::$SERVERROOT . '/data') . $view->getAbsolutePath('');
980
+		$root = \OC::$server->getConfig()->getSystemValue('datadirectory', \OC::$SERVERROOT.'/data').$view->getAbsolutePath('');
981 981
 		if (!file_exists($root)) {
982 982
 			return 0;
983 983
 		}
@@ -1008,7 +1008,7 @@  discard block
 block discarded – undo
1008 1008
 	 * @return integer trash bin size
1009 1009
 	 */
1010 1010
 	private static function getTrashbinSize($user) {
1011
-		$view = new View('/' . $user);
1011
+		$view = new View('/'.$user);
1012 1012
 		$fileInfo = $view->getFileInfo('/files_trashbin');
1013 1013
 		return isset($fileInfo['size']) ? $fileInfo['size'] : 0;
1014 1014
 	}
@@ -1034,7 +1034,7 @@  discard block
 block discarded – undo
1034 1034
 	 * @return bool
1035 1035
 	 */
1036 1036
 	public static function isEmpty($user) {
1037
-		$view = new View('/' . $user . '/files_trashbin');
1037
+		$view = new View('/'.$user.'/files_trashbin');
1038 1038
 		if ($view->is_dir('/files') && $dh = $view->opendir('/files')) {
1039 1039
 			while ($file = readdir($dh)) {
1040 1040
 				if (!Filesystem::isIgnoredDir($file)) {
Please login to merge, or discard this patch.
lib/private/Files/Storage/Common.php 1 patch
Indentation   +808 added lines, -808 removed lines patch added patch discarded remove patch
@@ -77,816 +77,816 @@
 block discarded – undo
77 77
  * in classes which extend it, e.g. $this->stat() .
78 78
  */
79 79
 abstract class Common implements Storage, ILockingStorage, IWriteStreamStorage {
80
-	use LocalTempFileTrait;
81
-
82
-	protected $cache;
83
-	protected $scanner;
84
-	protected $watcher;
85
-	protected $propagator;
86
-	protected $storageCache;
87
-	protected $updater;
88
-
89
-	protected $mountOptions = [];
90
-	protected $owner = null;
91
-
92
-	private $shouldLogLocks = null;
93
-	private $logger;
94
-
95
-	public function __construct($parameters) {
96
-	}
97
-
98
-	/**
99
-	 * Remove a file or folder
100
-	 *
101
-	 * @param string $path
102
-	 * @return bool
103
-	 */
104
-	protected function remove($path) {
105
-		if ($this->is_dir($path)) {
106
-			return $this->rmdir($path);
107
-		} elseif ($this->is_file($path)) {
108
-			return $this->unlink($path);
109
-		} else {
110
-			return false;
111
-		}
112
-	}
113
-
114
-	public function is_dir($path) {
115
-		return $this->filetype($path) === 'dir';
116
-	}
117
-
118
-	public function is_file($path) {
119
-		return $this->filetype($path) === 'file';
120
-	}
121
-
122
-	public function filesize($path) {
123
-		if ($this->is_dir($path)) {
124
-			return 0; //by definition
125
-		} else {
126
-			$stat = $this->stat($path);
127
-			if (isset($stat['size'])) {
128
-				return $stat['size'];
129
-			} else {
130
-				return 0;
131
-			}
132
-		}
133
-	}
134
-
135
-	public function isReadable($path) {
136
-		// at least check whether it exists
137
-		// subclasses might want to implement this more thoroughly
138
-		return $this->file_exists($path);
139
-	}
140
-
141
-	public function isUpdatable($path) {
142
-		// at least check whether it exists
143
-		// subclasses might want to implement this more thoroughly
144
-		// a non-existing file/folder isn't updatable
145
-		return $this->file_exists($path);
146
-	}
147
-
148
-	public function isCreatable($path) {
149
-		if ($this->is_dir($path) && $this->isUpdatable($path)) {
150
-			return true;
151
-		}
152
-		return false;
153
-	}
154
-
155
-	public function isDeletable($path) {
156
-		if ($path === '' || $path === '/') {
157
-			return false;
158
-		}
159
-		$parent = dirname($path);
160
-		return $this->isUpdatable($parent) && $this->isUpdatable($path);
161
-	}
162
-
163
-	public function isSharable($path) {
164
-		return $this->isReadable($path);
165
-	}
166
-
167
-	public function getPermissions($path) {
168
-		$permissions = 0;
169
-		if ($this->isCreatable($path)) {
170
-			$permissions |= \OCP\Constants::PERMISSION_CREATE;
171
-		}
172
-		if ($this->isReadable($path)) {
173
-			$permissions |= \OCP\Constants::PERMISSION_READ;
174
-		}
175
-		if ($this->isUpdatable($path)) {
176
-			$permissions |= \OCP\Constants::PERMISSION_UPDATE;
177
-		}
178
-		if ($this->isDeletable($path)) {
179
-			$permissions |= \OCP\Constants::PERMISSION_DELETE;
180
-		}
181
-		if ($this->isSharable($path)) {
182
-			$permissions |= \OCP\Constants::PERMISSION_SHARE;
183
-		}
184
-		return $permissions;
185
-	}
186
-
187
-	public function filemtime($path) {
188
-		$stat = $this->stat($path);
189
-		if (isset($stat['mtime']) && $stat['mtime'] > 0) {
190
-			return $stat['mtime'];
191
-		} else {
192
-			return 0;
193
-		}
194
-	}
195
-
196
-	public function file_get_contents($path) {
197
-		$handle = $this->fopen($path, "r");
198
-		if (!$handle) {
199
-			return false;
200
-		}
201
-		$data = stream_get_contents($handle);
202
-		fclose($handle);
203
-		return $data;
204
-	}
205
-
206
-	public function file_put_contents($path, $data) {
207
-		$handle = $this->fopen($path, "w");
208
-		$this->removeCachedFile($path);
209
-		$count = fwrite($handle, $data);
210
-		fclose($handle);
211
-		return $count;
212
-	}
213
-
214
-	public function rename($path1, $path2) {
215
-		$this->remove($path2);
216
-
217
-		$this->removeCachedFile($path1);
218
-		return $this->copy($path1, $path2) and $this->remove($path1);
219
-	}
220
-
221
-	public function copy($path1, $path2) {
222
-		if ($this->is_dir($path1)) {
223
-			$this->remove($path2);
224
-			$dir = $this->opendir($path1);
225
-			$this->mkdir($path2);
226
-			while ($file = readdir($dir)) {
227
-				if (!Filesystem::isIgnoredDir($file)) {
228
-					if (!$this->copy($path1 . '/' . $file, $path2 . '/' . $file)) {
229
-						return false;
230
-					}
231
-				}
232
-			}
233
-			closedir($dir);
234
-			return true;
235
-		} else {
236
-			$source = $this->fopen($path1, 'r');
237
-			$target = $this->fopen($path2, 'w');
238
-			[, $result] = \OC_Helper::streamCopy($source, $target);
239
-			if (!$result) {
240
-				\OC::$server->getLogger()->warning("Failed to write data while copying $path1 to $path2");
241
-			}
242
-			$this->removeCachedFile($path2);
243
-			return $result;
244
-		}
245
-	}
246
-
247
-	public function getMimeType($path) {
248
-		if ($this->is_dir($path)) {
249
-			return 'httpd/unix-directory';
250
-		} elseif ($this->file_exists($path)) {
251
-			return \OC::$server->getMimeTypeDetector()->detectPath($path);
252
-		} else {
253
-			return false;
254
-		}
255
-	}
256
-
257
-	public function hash($type, $path, $raw = false) {
258
-		$fh = $this->fopen($path, 'rb');
259
-		$ctx = hash_init($type);
260
-		hash_update_stream($ctx, $fh);
261
-		fclose($fh);
262
-		return hash_final($ctx, $raw);
263
-	}
264
-
265
-	public function search($query) {
266
-		return $this->searchInDir($query);
267
-	}
268
-
269
-	public function getLocalFile($path) {
270
-		return $this->getCachedFile($path);
271
-	}
272
-
273
-	/**
274
-	 * @param string $path
275
-	 * @param string $target
276
-	 */
277
-	private function addLocalFolder($path, $target) {
278
-		$dh = $this->opendir($path);
279
-		if (is_resource($dh)) {
280
-			while (($file = readdir($dh)) !== false) {
281
-				if (!\OC\Files\Filesystem::isIgnoredDir($file)) {
282
-					if ($this->is_dir($path . '/' . $file)) {
283
-						mkdir($target . '/' . $file);
284
-						$this->addLocalFolder($path . '/' . $file, $target . '/' . $file);
285
-					} else {
286
-						$tmp = $this->toTmpFile($path . '/' . $file);
287
-						rename($tmp, $target . '/' . $file);
288
-					}
289
-				}
290
-			}
291
-		}
292
-	}
293
-
294
-	/**
295
-	 * @param string $query
296
-	 * @param string $dir
297
-	 * @return array
298
-	 */
299
-	protected function searchInDir($query, $dir = '') {
300
-		$files = [];
301
-		$dh = $this->opendir($dir);
302
-		if (is_resource($dh)) {
303
-			while (($item = readdir($dh)) !== false) {
304
-				if (\OC\Files\Filesystem::isIgnoredDir($item)) {
305
-					continue;
306
-				}
307
-				if (strstr(strtolower($item), strtolower($query)) !== false) {
308
-					$files[] = $dir . '/' . $item;
309
-				}
310
-				if ($this->is_dir($dir . '/' . $item)) {
311
-					$files = array_merge($files, $this->searchInDir($query, $dir . '/' . $item));
312
-				}
313
-			}
314
-		}
315
-		closedir($dh);
316
-		return $files;
317
-	}
318
-
319
-	/**
320
-	 * check if a file or folder has been updated since $time
321
-	 *
322
-	 * The method is only used to check if the cache needs to be updated. Storage backends that don't support checking
323
-	 * the mtime should always return false here. As a result storage implementations that always return false expect
324
-	 * exclusive access to the backend and will not pick up files that have been added in a way that circumvents
325
-	 * ownClouds filesystem.
326
-	 *
327
-	 * @param string $path
328
-	 * @param int $time
329
-	 * @return bool
330
-	 */
331
-	public function hasUpdated($path, $time) {
332
-		return $this->filemtime($path) > $time;
333
-	}
334
-
335
-	public function getCache($path = '', $storage = null) {
336
-		if (!$storage) {
337
-			$storage = $this;
338
-		}
339
-		if (!isset($storage->cache)) {
340
-			$storage->cache = new Cache($storage);
341
-		}
342
-		return $storage->cache;
343
-	}
344
-
345
-	public function getScanner($path = '', $storage = null) {
346
-		if (!$storage) {
347
-			$storage = $this;
348
-		}
349
-		if (!isset($storage->scanner)) {
350
-			$storage->scanner = new Scanner($storage);
351
-		}
352
-		return $storage->scanner;
353
-	}
354
-
355
-	public function getWatcher($path = '', $storage = null) {
356
-		if (!$storage) {
357
-			$storage = $this;
358
-		}
359
-		if (!isset($this->watcher)) {
360
-			$this->watcher = new Watcher($storage);
361
-			$globalPolicy = \OC::$server->getConfig()->getSystemValue('filesystem_check_changes', Watcher::CHECK_NEVER);
362
-			$this->watcher->setPolicy((int)$this->getMountOption('filesystem_check_changes', $globalPolicy));
363
-		}
364
-		return $this->watcher;
365
-	}
366
-
367
-	/**
368
-	 * get a propagator instance for the cache
369
-	 *
370
-	 * @param \OC\Files\Storage\Storage (optional) the storage to pass to the watcher
371
-	 * @return \OC\Files\Cache\Propagator
372
-	 */
373
-	public function getPropagator($storage = null) {
374
-		if (!$storage) {
375
-			$storage = $this;
376
-		}
377
-		if (!isset($storage->propagator)) {
378
-			$config = \OC::$server->getSystemConfig();
379
-			$storage->propagator = new Propagator($storage, \OC::$server->getDatabaseConnection(), ['appdata_' . $config->getValue('instanceid')]);
380
-		}
381
-		return $storage->propagator;
382
-	}
383
-
384
-	public function getUpdater($storage = null) {
385
-		if (!$storage) {
386
-			$storage = $this;
387
-		}
388
-		if (!isset($storage->updater)) {
389
-			$storage->updater = new Updater($storage);
390
-		}
391
-		return $storage->updater;
392
-	}
393
-
394
-	public function getStorageCache($storage = null) {
395
-		if (!$storage) {
396
-			$storage = $this;
397
-		}
398
-		if (!isset($this->storageCache)) {
399
-			$this->storageCache = new \OC\Files\Cache\Storage($storage);
400
-		}
401
-		return $this->storageCache;
402
-	}
403
-
404
-	/**
405
-	 * get the owner of a path
406
-	 *
407
-	 * @param string $path The path to get the owner
408
-	 * @return string|false uid or false
409
-	 */
410
-	public function getOwner($path) {
411
-		if ($this->owner === null) {
412
-			$this->owner = \OC_User::getUser();
413
-		}
414
-
415
-		return $this->owner;
416
-	}
417
-
418
-	/**
419
-	 * get the ETag for a file or folder
420
-	 *
421
-	 * @param string $path
422
-	 * @return string
423
-	 */
424
-	public function getETag($path) {
425
-		return uniqid();
426
-	}
427
-
428
-	/**
429
-	 * clean a path, i.e. remove all redundant '.' and '..'
430
-	 * making sure that it can't point to higher than '/'
431
-	 *
432
-	 * @param string $path The path to clean
433
-	 * @return string cleaned path
434
-	 */
435
-	public function cleanPath($path) {
436
-		if (strlen($path) == 0 or $path[0] != '/') {
437
-			$path = '/' . $path;
438
-		}
439
-
440
-		$output = [];
441
-		foreach (explode('/', $path) as $chunk) {
442
-			if ($chunk == '..') {
443
-				array_pop($output);
444
-			} elseif ($chunk == '.') {
445
-			} else {
446
-				$output[] = $chunk;
447
-			}
448
-		}
449
-		return implode('/', $output);
450
-	}
451
-
452
-	/**
453
-	 * Test a storage for availability
454
-	 *
455
-	 * @return bool
456
-	 */
457
-	public function test() {
458
-		try {
459
-			if ($this->stat('')) {
460
-				return true;
461
-			}
462
-			\OC::$server->getLogger()->info("External storage not available: stat() failed");
463
-			return false;
464
-		} catch (\Exception $e) {
465
-			\OC::$server->getLogger()->warning("External storage not available: " . $e->getMessage());
466
-			\OC::$server->getLogger()->logException($e, ['level' => ILogger::WARN]);
467
-			return false;
468
-		}
469
-	}
470
-
471
-	/**
472
-	 * get the free space in the storage
473
-	 *
474
-	 * @param string $path
475
-	 * @return int|false
476
-	 */
477
-	public function free_space($path) {
478
-		return \OCP\Files\FileInfo::SPACE_UNKNOWN;
479
-	}
480
-
481
-	/**
482
-	 * {@inheritdoc}
483
-	 */
484
-	public function isLocal() {
485
-		// the common implementation returns a temporary file by
486
-		// default, which is not local
487
-		return false;
488
-	}
489
-
490
-	/**
491
-	 * Check if the storage is an instance of $class or is a wrapper for a storage that is an instance of $class
492
-	 *
493
-	 * @param string $class
494
-	 * @return bool
495
-	 */
496
-	public function instanceOfStorage($class) {
497
-		if (ltrim($class, '\\') === 'OC\Files\Storage\Shared') {
498
-			// FIXME Temporary fix to keep existing checks working
499
-			$class = '\OCA\Files_Sharing\SharedStorage';
500
-		}
501
-		return is_a($this, $class);
502
-	}
503
-
504
-	/**
505
-	 * A custom storage implementation can return an url for direct download of a give file.
506
-	 *
507
-	 * For now the returned array can hold the parameter url - in future more attributes might follow.
508
-	 *
509
-	 * @param string $path
510
-	 * @return array|false
511
-	 */
512
-	public function getDirectDownload($path) {
513
-		return [];
514
-	}
515
-
516
-	/**
517
-	 * @inheritdoc
518
-	 * @throws InvalidPathException
519
-	 */
520
-	public function verifyPath($path, $fileName) {
521
-
522
-		// verify empty and dot files
523
-		$trimmed = trim($fileName);
524
-		if ($trimmed === '') {
525
-			throw new EmptyFileNameException();
526
-		}
527
-
528
-		if (\OC\Files\Filesystem::isIgnoredDir($trimmed)) {
529
-			throw new InvalidDirectoryException();
530
-		}
531
-
532
-		if (!\OC::$server->getDatabaseConnection()->supports4ByteText()) {
533
-			// verify database - e.g. mysql only 3-byte chars
534
-			if (preg_match('%(?:
80
+    use LocalTempFileTrait;
81
+
82
+    protected $cache;
83
+    protected $scanner;
84
+    protected $watcher;
85
+    protected $propagator;
86
+    protected $storageCache;
87
+    protected $updater;
88
+
89
+    protected $mountOptions = [];
90
+    protected $owner = null;
91
+
92
+    private $shouldLogLocks = null;
93
+    private $logger;
94
+
95
+    public function __construct($parameters) {
96
+    }
97
+
98
+    /**
99
+     * Remove a file or folder
100
+     *
101
+     * @param string $path
102
+     * @return bool
103
+     */
104
+    protected function remove($path) {
105
+        if ($this->is_dir($path)) {
106
+            return $this->rmdir($path);
107
+        } elseif ($this->is_file($path)) {
108
+            return $this->unlink($path);
109
+        } else {
110
+            return false;
111
+        }
112
+    }
113
+
114
+    public function is_dir($path) {
115
+        return $this->filetype($path) === 'dir';
116
+    }
117
+
118
+    public function is_file($path) {
119
+        return $this->filetype($path) === 'file';
120
+    }
121
+
122
+    public function filesize($path) {
123
+        if ($this->is_dir($path)) {
124
+            return 0; //by definition
125
+        } else {
126
+            $stat = $this->stat($path);
127
+            if (isset($stat['size'])) {
128
+                return $stat['size'];
129
+            } else {
130
+                return 0;
131
+            }
132
+        }
133
+    }
134
+
135
+    public function isReadable($path) {
136
+        // at least check whether it exists
137
+        // subclasses might want to implement this more thoroughly
138
+        return $this->file_exists($path);
139
+    }
140
+
141
+    public function isUpdatable($path) {
142
+        // at least check whether it exists
143
+        // subclasses might want to implement this more thoroughly
144
+        // a non-existing file/folder isn't updatable
145
+        return $this->file_exists($path);
146
+    }
147
+
148
+    public function isCreatable($path) {
149
+        if ($this->is_dir($path) && $this->isUpdatable($path)) {
150
+            return true;
151
+        }
152
+        return false;
153
+    }
154
+
155
+    public function isDeletable($path) {
156
+        if ($path === '' || $path === '/') {
157
+            return false;
158
+        }
159
+        $parent = dirname($path);
160
+        return $this->isUpdatable($parent) && $this->isUpdatable($path);
161
+    }
162
+
163
+    public function isSharable($path) {
164
+        return $this->isReadable($path);
165
+    }
166
+
167
+    public function getPermissions($path) {
168
+        $permissions = 0;
169
+        if ($this->isCreatable($path)) {
170
+            $permissions |= \OCP\Constants::PERMISSION_CREATE;
171
+        }
172
+        if ($this->isReadable($path)) {
173
+            $permissions |= \OCP\Constants::PERMISSION_READ;
174
+        }
175
+        if ($this->isUpdatable($path)) {
176
+            $permissions |= \OCP\Constants::PERMISSION_UPDATE;
177
+        }
178
+        if ($this->isDeletable($path)) {
179
+            $permissions |= \OCP\Constants::PERMISSION_DELETE;
180
+        }
181
+        if ($this->isSharable($path)) {
182
+            $permissions |= \OCP\Constants::PERMISSION_SHARE;
183
+        }
184
+        return $permissions;
185
+    }
186
+
187
+    public function filemtime($path) {
188
+        $stat = $this->stat($path);
189
+        if (isset($stat['mtime']) && $stat['mtime'] > 0) {
190
+            return $stat['mtime'];
191
+        } else {
192
+            return 0;
193
+        }
194
+    }
195
+
196
+    public function file_get_contents($path) {
197
+        $handle = $this->fopen($path, "r");
198
+        if (!$handle) {
199
+            return false;
200
+        }
201
+        $data = stream_get_contents($handle);
202
+        fclose($handle);
203
+        return $data;
204
+    }
205
+
206
+    public function file_put_contents($path, $data) {
207
+        $handle = $this->fopen($path, "w");
208
+        $this->removeCachedFile($path);
209
+        $count = fwrite($handle, $data);
210
+        fclose($handle);
211
+        return $count;
212
+    }
213
+
214
+    public function rename($path1, $path2) {
215
+        $this->remove($path2);
216
+
217
+        $this->removeCachedFile($path1);
218
+        return $this->copy($path1, $path2) and $this->remove($path1);
219
+    }
220
+
221
+    public function copy($path1, $path2) {
222
+        if ($this->is_dir($path1)) {
223
+            $this->remove($path2);
224
+            $dir = $this->opendir($path1);
225
+            $this->mkdir($path2);
226
+            while ($file = readdir($dir)) {
227
+                if (!Filesystem::isIgnoredDir($file)) {
228
+                    if (!$this->copy($path1 . '/' . $file, $path2 . '/' . $file)) {
229
+                        return false;
230
+                    }
231
+                }
232
+            }
233
+            closedir($dir);
234
+            return true;
235
+        } else {
236
+            $source = $this->fopen($path1, 'r');
237
+            $target = $this->fopen($path2, 'w');
238
+            [, $result] = \OC_Helper::streamCopy($source, $target);
239
+            if (!$result) {
240
+                \OC::$server->getLogger()->warning("Failed to write data while copying $path1 to $path2");
241
+            }
242
+            $this->removeCachedFile($path2);
243
+            return $result;
244
+        }
245
+    }
246
+
247
+    public function getMimeType($path) {
248
+        if ($this->is_dir($path)) {
249
+            return 'httpd/unix-directory';
250
+        } elseif ($this->file_exists($path)) {
251
+            return \OC::$server->getMimeTypeDetector()->detectPath($path);
252
+        } else {
253
+            return false;
254
+        }
255
+    }
256
+
257
+    public function hash($type, $path, $raw = false) {
258
+        $fh = $this->fopen($path, 'rb');
259
+        $ctx = hash_init($type);
260
+        hash_update_stream($ctx, $fh);
261
+        fclose($fh);
262
+        return hash_final($ctx, $raw);
263
+    }
264
+
265
+    public function search($query) {
266
+        return $this->searchInDir($query);
267
+    }
268
+
269
+    public function getLocalFile($path) {
270
+        return $this->getCachedFile($path);
271
+    }
272
+
273
+    /**
274
+     * @param string $path
275
+     * @param string $target
276
+     */
277
+    private function addLocalFolder($path, $target) {
278
+        $dh = $this->opendir($path);
279
+        if (is_resource($dh)) {
280
+            while (($file = readdir($dh)) !== false) {
281
+                if (!\OC\Files\Filesystem::isIgnoredDir($file)) {
282
+                    if ($this->is_dir($path . '/' . $file)) {
283
+                        mkdir($target . '/' . $file);
284
+                        $this->addLocalFolder($path . '/' . $file, $target . '/' . $file);
285
+                    } else {
286
+                        $tmp = $this->toTmpFile($path . '/' . $file);
287
+                        rename($tmp, $target . '/' . $file);
288
+                    }
289
+                }
290
+            }
291
+        }
292
+    }
293
+
294
+    /**
295
+     * @param string $query
296
+     * @param string $dir
297
+     * @return array
298
+     */
299
+    protected function searchInDir($query, $dir = '') {
300
+        $files = [];
301
+        $dh = $this->opendir($dir);
302
+        if (is_resource($dh)) {
303
+            while (($item = readdir($dh)) !== false) {
304
+                if (\OC\Files\Filesystem::isIgnoredDir($item)) {
305
+                    continue;
306
+                }
307
+                if (strstr(strtolower($item), strtolower($query)) !== false) {
308
+                    $files[] = $dir . '/' . $item;
309
+                }
310
+                if ($this->is_dir($dir . '/' . $item)) {
311
+                    $files = array_merge($files, $this->searchInDir($query, $dir . '/' . $item));
312
+                }
313
+            }
314
+        }
315
+        closedir($dh);
316
+        return $files;
317
+    }
318
+
319
+    /**
320
+     * check if a file or folder has been updated since $time
321
+     *
322
+     * The method is only used to check if the cache needs to be updated. Storage backends that don't support checking
323
+     * the mtime should always return false here. As a result storage implementations that always return false expect
324
+     * exclusive access to the backend and will not pick up files that have been added in a way that circumvents
325
+     * ownClouds filesystem.
326
+     *
327
+     * @param string $path
328
+     * @param int $time
329
+     * @return bool
330
+     */
331
+    public function hasUpdated($path, $time) {
332
+        return $this->filemtime($path) > $time;
333
+    }
334
+
335
+    public function getCache($path = '', $storage = null) {
336
+        if (!$storage) {
337
+            $storage = $this;
338
+        }
339
+        if (!isset($storage->cache)) {
340
+            $storage->cache = new Cache($storage);
341
+        }
342
+        return $storage->cache;
343
+    }
344
+
345
+    public function getScanner($path = '', $storage = null) {
346
+        if (!$storage) {
347
+            $storage = $this;
348
+        }
349
+        if (!isset($storage->scanner)) {
350
+            $storage->scanner = new Scanner($storage);
351
+        }
352
+        return $storage->scanner;
353
+    }
354
+
355
+    public function getWatcher($path = '', $storage = null) {
356
+        if (!$storage) {
357
+            $storage = $this;
358
+        }
359
+        if (!isset($this->watcher)) {
360
+            $this->watcher = new Watcher($storage);
361
+            $globalPolicy = \OC::$server->getConfig()->getSystemValue('filesystem_check_changes', Watcher::CHECK_NEVER);
362
+            $this->watcher->setPolicy((int)$this->getMountOption('filesystem_check_changes', $globalPolicy));
363
+        }
364
+        return $this->watcher;
365
+    }
366
+
367
+    /**
368
+     * get a propagator instance for the cache
369
+     *
370
+     * @param \OC\Files\Storage\Storage (optional) the storage to pass to the watcher
371
+     * @return \OC\Files\Cache\Propagator
372
+     */
373
+    public function getPropagator($storage = null) {
374
+        if (!$storage) {
375
+            $storage = $this;
376
+        }
377
+        if (!isset($storage->propagator)) {
378
+            $config = \OC::$server->getSystemConfig();
379
+            $storage->propagator = new Propagator($storage, \OC::$server->getDatabaseConnection(), ['appdata_' . $config->getValue('instanceid')]);
380
+        }
381
+        return $storage->propagator;
382
+    }
383
+
384
+    public function getUpdater($storage = null) {
385
+        if (!$storage) {
386
+            $storage = $this;
387
+        }
388
+        if (!isset($storage->updater)) {
389
+            $storage->updater = new Updater($storage);
390
+        }
391
+        return $storage->updater;
392
+    }
393
+
394
+    public function getStorageCache($storage = null) {
395
+        if (!$storage) {
396
+            $storage = $this;
397
+        }
398
+        if (!isset($this->storageCache)) {
399
+            $this->storageCache = new \OC\Files\Cache\Storage($storage);
400
+        }
401
+        return $this->storageCache;
402
+    }
403
+
404
+    /**
405
+     * get the owner of a path
406
+     *
407
+     * @param string $path The path to get the owner
408
+     * @return string|false uid or false
409
+     */
410
+    public function getOwner($path) {
411
+        if ($this->owner === null) {
412
+            $this->owner = \OC_User::getUser();
413
+        }
414
+
415
+        return $this->owner;
416
+    }
417
+
418
+    /**
419
+     * get the ETag for a file or folder
420
+     *
421
+     * @param string $path
422
+     * @return string
423
+     */
424
+    public function getETag($path) {
425
+        return uniqid();
426
+    }
427
+
428
+    /**
429
+     * clean a path, i.e. remove all redundant '.' and '..'
430
+     * making sure that it can't point to higher than '/'
431
+     *
432
+     * @param string $path The path to clean
433
+     * @return string cleaned path
434
+     */
435
+    public function cleanPath($path) {
436
+        if (strlen($path) == 0 or $path[0] != '/') {
437
+            $path = '/' . $path;
438
+        }
439
+
440
+        $output = [];
441
+        foreach (explode('/', $path) as $chunk) {
442
+            if ($chunk == '..') {
443
+                array_pop($output);
444
+            } elseif ($chunk == '.') {
445
+            } else {
446
+                $output[] = $chunk;
447
+            }
448
+        }
449
+        return implode('/', $output);
450
+    }
451
+
452
+    /**
453
+     * Test a storage for availability
454
+     *
455
+     * @return bool
456
+     */
457
+    public function test() {
458
+        try {
459
+            if ($this->stat('')) {
460
+                return true;
461
+            }
462
+            \OC::$server->getLogger()->info("External storage not available: stat() failed");
463
+            return false;
464
+        } catch (\Exception $e) {
465
+            \OC::$server->getLogger()->warning("External storage not available: " . $e->getMessage());
466
+            \OC::$server->getLogger()->logException($e, ['level' => ILogger::WARN]);
467
+            return false;
468
+        }
469
+    }
470
+
471
+    /**
472
+     * get the free space in the storage
473
+     *
474
+     * @param string $path
475
+     * @return int|false
476
+     */
477
+    public function free_space($path) {
478
+        return \OCP\Files\FileInfo::SPACE_UNKNOWN;
479
+    }
480
+
481
+    /**
482
+     * {@inheritdoc}
483
+     */
484
+    public function isLocal() {
485
+        // the common implementation returns a temporary file by
486
+        // default, which is not local
487
+        return false;
488
+    }
489
+
490
+    /**
491
+     * Check if the storage is an instance of $class or is a wrapper for a storage that is an instance of $class
492
+     *
493
+     * @param string $class
494
+     * @return bool
495
+     */
496
+    public function instanceOfStorage($class) {
497
+        if (ltrim($class, '\\') === 'OC\Files\Storage\Shared') {
498
+            // FIXME Temporary fix to keep existing checks working
499
+            $class = '\OCA\Files_Sharing\SharedStorage';
500
+        }
501
+        return is_a($this, $class);
502
+    }
503
+
504
+    /**
505
+     * A custom storage implementation can return an url for direct download of a give file.
506
+     *
507
+     * For now the returned array can hold the parameter url - in future more attributes might follow.
508
+     *
509
+     * @param string $path
510
+     * @return array|false
511
+     */
512
+    public function getDirectDownload($path) {
513
+        return [];
514
+    }
515
+
516
+    /**
517
+     * @inheritdoc
518
+     * @throws InvalidPathException
519
+     */
520
+    public function verifyPath($path, $fileName) {
521
+
522
+        // verify empty and dot files
523
+        $trimmed = trim($fileName);
524
+        if ($trimmed === '') {
525
+            throw new EmptyFileNameException();
526
+        }
527
+
528
+        if (\OC\Files\Filesystem::isIgnoredDir($trimmed)) {
529
+            throw new InvalidDirectoryException();
530
+        }
531
+
532
+        if (!\OC::$server->getDatabaseConnection()->supports4ByteText()) {
533
+            // verify database - e.g. mysql only 3-byte chars
534
+            if (preg_match('%(?:
535 535
       \xF0[\x90-\xBF][\x80-\xBF]{2}      # planes 1-3
536 536
     | [\xF1-\xF3][\x80-\xBF]{3}          # planes 4-15
537 537
     | \xF4[\x80-\x8F][\x80-\xBF]{2}      # plane 16
538 538
 )%xs', $fileName)) {
539
-				throw new InvalidCharacterInPathException();
540
-			}
541
-		}
542
-
543
-		// 255 characters is the limit on common file systems (ext/xfs)
544
-		// oc_filecache has a 250 char length limit for the filename
545
-		if (isset($fileName[250])) {
546
-			throw new FileNameTooLongException();
547
-		}
548
-
549
-		// NOTE: $path will remain unverified for now
550
-		$this->verifyPosixPath($fileName);
551
-	}
552
-
553
-	/**
554
-	 * @param string $fileName
555
-	 * @throws InvalidPathException
556
-	 */
557
-	protected function verifyPosixPath($fileName) {
558
-		$fileName = trim($fileName);
559
-		$this->scanForInvalidCharacters($fileName, "\\/");
560
-		$reservedNames = ['*'];
561
-		if (in_array($fileName, $reservedNames)) {
562
-			throw new ReservedWordException();
563
-		}
564
-	}
565
-
566
-	/**
567
-	 * @param string $fileName
568
-	 * @param string $invalidChars
569
-	 * @throws InvalidPathException
570
-	 */
571
-	private function scanForInvalidCharacters($fileName, $invalidChars) {
572
-		foreach (str_split($invalidChars) as $char) {
573
-			if (strpos($fileName, $char) !== false) {
574
-				throw new InvalidCharacterInPathException();
575
-			}
576
-		}
577
-
578
-		$sanitizedFileName = filter_var($fileName, FILTER_UNSAFE_RAW, FILTER_FLAG_STRIP_LOW);
579
-		if ($sanitizedFileName !== $fileName) {
580
-			throw new InvalidCharacterInPathException();
581
-		}
582
-	}
583
-
584
-	/**
585
-	 * @param array $options
586
-	 */
587
-	public function setMountOptions(array $options) {
588
-		$this->mountOptions = $options;
589
-	}
590
-
591
-	/**
592
-	 * @param string $name
593
-	 * @param mixed $default
594
-	 * @return mixed
595
-	 */
596
-	public function getMountOption($name, $default = null) {
597
-		return isset($this->mountOptions[$name]) ? $this->mountOptions[$name] : $default;
598
-	}
599
-
600
-	/**
601
-	 * @param IStorage $sourceStorage
602
-	 * @param string $sourceInternalPath
603
-	 * @param string $targetInternalPath
604
-	 * @param bool $preserveMtime
605
-	 * @return bool
606
-	 */
607
-	public function copyFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath, $preserveMtime = false) {
608
-		if ($sourceStorage === $this) {
609
-			return $this->copy($sourceInternalPath, $targetInternalPath);
610
-		}
611
-
612
-		if ($sourceStorage->is_dir($sourceInternalPath)) {
613
-			$dh = $sourceStorage->opendir($sourceInternalPath);
614
-			$result = $this->mkdir($targetInternalPath);
615
-			if (is_resource($dh)) {
616
-				while ($result and ($file = readdir($dh)) !== false) {
617
-					if (!Filesystem::isIgnoredDir($file)) {
618
-						$result &= $this->copyFromStorage($sourceStorage, $sourceInternalPath . '/' . $file, $targetInternalPath . '/' . $file);
619
-					}
620
-				}
621
-			}
622
-		} else {
623
-			$source = $sourceStorage->fopen($sourceInternalPath, 'r');
624
-			$result = false;
625
-			if ($source) {
626
-				try {
627
-					$this->writeStream($targetInternalPath, $source);
628
-					$result = true;
629
-				} catch (\Exception $e) {
630
-					\OC::$server->getLogger()->logException($e, ['level' => ILogger::WARN, 'message' => 'Failed to copy stream to storage']);
631
-				}
632
-			}
633
-
634
-			if ($result and $preserveMtime) {
635
-				$this->touch($targetInternalPath, $sourceStorage->filemtime($sourceInternalPath));
636
-			}
637
-
638
-			if (!$result) {
639
-				// delete partially written target file
640
-				$this->unlink($targetInternalPath);
641
-				// delete cache entry that was created by fopen
642
-				$this->getCache()->remove($targetInternalPath);
643
-			}
644
-		}
645
-		return (bool)$result;
646
-	}
647
-
648
-	/**
649
-	 * Check if a storage is the same as the current one, including wrapped storages
650
-	 *
651
-	 * @param IStorage $storage
652
-	 * @return bool
653
-	 */
654
-	private function isSameStorage(IStorage $storage): bool {
655
-		while ($storage->instanceOfStorage(Wrapper::class)) {
656
-			/**
657
-			 * @var Wrapper $sourceStorage
658
-			 */
659
-			$storage = $storage->getWrapperStorage();
660
-		}
661
-
662
-		return $storage === $this;
663
-	}
664
-
665
-	/**
666
-	 * @param IStorage $sourceStorage
667
-	 * @param string $sourceInternalPath
668
-	 * @param string $targetInternalPath
669
-	 * @return bool
670
-	 */
671
-	public function moveFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) {
672
-		if ($this->isSameStorage($sourceStorage)) {
673
-			// resolve any jailed paths
674
-			while ($sourceStorage->instanceOfStorage(Jail::class)) {
675
-				/**
676
-				 * @var Jail $sourceStorage
677
-				 */
678
-				$sourceInternalPath = $sourceStorage->getUnjailedPath($sourceInternalPath);
679
-				$sourceStorage = $sourceStorage->getUnjailedStorage();
680
-			}
681
-
682
-			return $this->rename($sourceInternalPath, $targetInternalPath);
683
-		}
684
-
685
-		if (!$sourceStorage->isDeletable($sourceInternalPath)) {
686
-			return false;
687
-		}
688
-
689
-		$result = $this->copyFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath, true);
690
-		if ($result) {
691
-			if ($sourceStorage->is_dir($sourceInternalPath)) {
692
-				$result &= $sourceStorage->rmdir($sourceInternalPath);
693
-			} else {
694
-				$result &= $sourceStorage->unlink($sourceInternalPath);
695
-			}
696
-		}
697
-		return $result;
698
-	}
699
-
700
-	/**
701
-	 * @inheritdoc
702
-	 */
703
-	public function getMetaData($path) {
704
-		$permissions = $this->getPermissions($path);
705
-		if (!$permissions & \OCP\Constants::PERMISSION_READ) {
706
-			//can't read, nothing we can do
707
-			return null;
708
-		}
709
-
710
-		$data = [];
711
-		$data['mimetype'] = $this->getMimeType($path);
712
-		$data['mtime'] = $this->filemtime($path);
713
-		if ($data['mtime'] === false) {
714
-			$data['mtime'] = time();
715
-		}
716
-		if ($data['mimetype'] == 'httpd/unix-directory') {
717
-			$data['size'] = -1; //unknown
718
-		} else {
719
-			$data['size'] = $this->filesize($path);
720
-		}
721
-		$data['etag'] = $this->getETag($path);
722
-		$data['storage_mtime'] = $data['mtime'];
723
-		$data['permissions'] = $permissions;
724
-		$data['name'] = basename($path);
725
-
726
-		return $data;
727
-	}
728
-
729
-	/**
730
-	 * @param string $path
731
-	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
732
-	 * @param \OCP\Lock\ILockingProvider $provider
733
-	 * @throws \OCP\Lock\LockedException
734
-	 */
735
-	public function acquireLock($path, $type, ILockingProvider $provider) {
736
-		$logger = $this->getLockLogger();
737
-		if ($logger) {
738
-			$typeString = ($type === ILockingProvider::LOCK_SHARED) ? 'shared' : 'exclusive';
739
-			$logger->info(
740
-				sprintf(
741
-					'acquire %s lock on "%s" on storage "%s"',
742
-					$typeString,
743
-					$path,
744
-					$this->getId()
745
-				),
746
-				[
747
-					'app' => 'locking',
748
-				]
749
-			);
750
-		}
751
-		try {
752
-			$provider->acquireLock('files/' . md5($this->getId() . '::' . trim($path, '/')), $type, $this->getId() . '::' . $path);
753
-		} catch (LockedException $e) {
754
-			if ($logger) {
755
-				$logger->logException($e, ['level' => ILogger::INFO]);
756
-			}
757
-			throw $e;
758
-		}
759
-	}
760
-
761
-	/**
762
-	 * @param string $path
763
-	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
764
-	 * @param \OCP\Lock\ILockingProvider $provider
765
-	 * @throws \OCP\Lock\LockedException
766
-	 */
767
-	public function releaseLock($path, $type, ILockingProvider $provider) {
768
-		$logger = $this->getLockLogger();
769
-		if ($logger) {
770
-			$typeString = ($type === ILockingProvider::LOCK_SHARED) ? 'shared' : 'exclusive';
771
-			$logger->info(
772
-				sprintf(
773
-					'release %s lock on "%s" on storage "%s"',
774
-					$typeString,
775
-					$path,
776
-					$this->getId()
777
-				),
778
-				[
779
-					'app' => 'locking',
780
-				]
781
-			);
782
-		}
783
-		try {
784
-			$provider->releaseLock('files/' . md5($this->getId() . '::' . trim($path, '/')), $type);
785
-		} catch (LockedException $e) {
786
-			if ($logger) {
787
-				$logger->logException($e, ['level' => ILogger::INFO]);
788
-			}
789
-			throw $e;
790
-		}
791
-	}
792
-
793
-	/**
794
-	 * @param string $path
795
-	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
796
-	 * @param \OCP\Lock\ILockingProvider $provider
797
-	 * @throws \OCP\Lock\LockedException
798
-	 */
799
-	public function changeLock($path, $type, ILockingProvider $provider) {
800
-		$logger = $this->getLockLogger();
801
-		if ($logger) {
802
-			$typeString = ($type === ILockingProvider::LOCK_SHARED) ? 'shared' : 'exclusive';
803
-			$logger->info(
804
-				sprintf(
805
-					'change lock on "%s" to %s on storage "%s"',
806
-					$path,
807
-					$typeString,
808
-					$this->getId()
809
-				),
810
-				[
811
-					'app' => 'locking',
812
-				]
813
-			);
814
-		}
815
-		try {
816
-			$provider->changeLock('files/' . md5($this->getId() . '::' . trim($path, '/')), $type);
817
-		} catch (LockedException $e) {
818
-			\OC::$server->getLogger()->logException($e, ['level' => ILogger::INFO]);
819
-			throw $e;
820
-		}
821
-	}
822
-
823
-	private function getLockLogger() {
824
-		if (is_null($this->shouldLogLocks)) {
825
-			$this->shouldLogLocks = \OC::$server->getConfig()->getSystemValue('filelocking.debug', false);
826
-			$this->logger = $this->shouldLogLocks ? \OC::$server->getLogger() : null;
827
-		}
828
-		return $this->logger;
829
-	}
830
-
831
-	/**
832
-	 * @return array [ available, last_checked ]
833
-	 */
834
-	public function getAvailability() {
835
-		return $this->getStorageCache()->getAvailability();
836
-	}
837
-
838
-	/**
839
-	 * @param bool $isAvailable
840
-	 */
841
-	public function setAvailability($isAvailable) {
842
-		$this->getStorageCache()->setAvailability($isAvailable);
843
-	}
844
-
845
-	/**
846
-	 * @return bool
847
-	 */
848
-	public function needsPartFile() {
849
-		return true;
850
-	}
851
-
852
-	/**
853
-	 * fallback implementation
854
-	 *
855
-	 * @param string $path
856
-	 * @param resource $stream
857
-	 * @param int $size
858
-	 * @return int
859
-	 */
860
-	public function writeStream(string $path, $stream, int $size = null): int {
861
-		$target = $this->fopen($path, 'w');
862
-		if (!$target) {
863
-			throw new GenericFileException("Failed to open $path for writing");
864
-		}
865
-		try {
866
-			[$count, $result] = \OC_Helper::streamCopy($stream, $target);
867
-			if (!$result) {
868
-				throw new GenericFileException("Failed to copy stream");
869
-			}
870
-		} finally {
871
-			fclose($target);
872
-			fclose($stream);
873
-		}
874
-		return $count;
875
-	}
876
-
877
-	public function getDirectoryContent($directory): \Traversable {
878
-		$dh = $this->opendir($directory);
879
-		if (is_resource($dh)) {
880
-			$basePath = rtrim($directory, '/');
881
-			while (($file = readdir($dh)) !== false) {
882
-				if (!Filesystem::isIgnoredDir($file) && !Filesystem::isFileBlacklisted($file)) {
883
-					$childPath = $basePath . '/' . trim($file, '/');
884
-					$metadata = $this->getMetaData($childPath);
885
-					if ($metadata !== null) {
886
-						yield $metadata;
887
-					}
888
-				}
889
-			}
890
-		}
891
-	}
539
+                throw new InvalidCharacterInPathException();
540
+            }
541
+        }
542
+
543
+        // 255 characters is the limit on common file systems (ext/xfs)
544
+        // oc_filecache has a 250 char length limit for the filename
545
+        if (isset($fileName[250])) {
546
+            throw new FileNameTooLongException();
547
+        }
548
+
549
+        // NOTE: $path will remain unverified for now
550
+        $this->verifyPosixPath($fileName);
551
+    }
552
+
553
+    /**
554
+     * @param string $fileName
555
+     * @throws InvalidPathException
556
+     */
557
+    protected function verifyPosixPath($fileName) {
558
+        $fileName = trim($fileName);
559
+        $this->scanForInvalidCharacters($fileName, "\\/");
560
+        $reservedNames = ['*'];
561
+        if (in_array($fileName, $reservedNames)) {
562
+            throw new ReservedWordException();
563
+        }
564
+    }
565
+
566
+    /**
567
+     * @param string $fileName
568
+     * @param string $invalidChars
569
+     * @throws InvalidPathException
570
+     */
571
+    private function scanForInvalidCharacters($fileName, $invalidChars) {
572
+        foreach (str_split($invalidChars) as $char) {
573
+            if (strpos($fileName, $char) !== false) {
574
+                throw new InvalidCharacterInPathException();
575
+            }
576
+        }
577
+
578
+        $sanitizedFileName = filter_var($fileName, FILTER_UNSAFE_RAW, FILTER_FLAG_STRIP_LOW);
579
+        if ($sanitizedFileName !== $fileName) {
580
+            throw new InvalidCharacterInPathException();
581
+        }
582
+    }
583
+
584
+    /**
585
+     * @param array $options
586
+     */
587
+    public function setMountOptions(array $options) {
588
+        $this->mountOptions = $options;
589
+    }
590
+
591
+    /**
592
+     * @param string $name
593
+     * @param mixed $default
594
+     * @return mixed
595
+     */
596
+    public function getMountOption($name, $default = null) {
597
+        return isset($this->mountOptions[$name]) ? $this->mountOptions[$name] : $default;
598
+    }
599
+
600
+    /**
601
+     * @param IStorage $sourceStorage
602
+     * @param string $sourceInternalPath
603
+     * @param string $targetInternalPath
604
+     * @param bool $preserveMtime
605
+     * @return bool
606
+     */
607
+    public function copyFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath, $preserveMtime = false) {
608
+        if ($sourceStorage === $this) {
609
+            return $this->copy($sourceInternalPath, $targetInternalPath);
610
+        }
611
+
612
+        if ($sourceStorage->is_dir($sourceInternalPath)) {
613
+            $dh = $sourceStorage->opendir($sourceInternalPath);
614
+            $result = $this->mkdir($targetInternalPath);
615
+            if (is_resource($dh)) {
616
+                while ($result and ($file = readdir($dh)) !== false) {
617
+                    if (!Filesystem::isIgnoredDir($file)) {
618
+                        $result &= $this->copyFromStorage($sourceStorage, $sourceInternalPath . '/' . $file, $targetInternalPath . '/' . $file);
619
+                    }
620
+                }
621
+            }
622
+        } else {
623
+            $source = $sourceStorage->fopen($sourceInternalPath, 'r');
624
+            $result = false;
625
+            if ($source) {
626
+                try {
627
+                    $this->writeStream($targetInternalPath, $source);
628
+                    $result = true;
629
+                } catch (\Exception $e) {
630
+                    \OC::$server->getLogger()->logException($e, ['level' => ILogger::WARN, 'message' => 'Failed to copy stream to storage']);
631
+                }
632
+            }
633
+
634
+            if ($result and $preserveMtime) {
635
+                $this->touch($targetInternalPath, $sourceStorage->filemtime($sourceInternalPath));
636
+            }
637
+
638
+            if (!$result) {
639
+                // delete partially written target file
640
+                $this->unlink($targetInternalPath);
641
+                // delete cache entry that was created by fopen
642
+                $this->getCache()->remove($targetInternalPath);
643
+            }
644
+        }
645
+        return (bool)$result;
646
+    }
647
+
648
+    /**
649
+     * Check if a storage is the same as the current one, including wrapped storages
650
+     *
651
+     * @param IStorage $storage
652
+     * @return bool
653
+     */
654
+    private function isSameStorage(IStorage $storage): bool {
655
+        while ($storage->instanceOfStorage(Wrapper::class)) {
656
+            /**
657
+             * @var Wrapper $sourceStorage
658
+             */
659
+            $storage = $storage->getWrapperStorage();
660
+        }
661
+
662
+        return $storage === $this;
663
+    }
664
+
665
+    /**
666
+     * @param IStorage $sourceStorage
667
+     * @param string $sourceInternalPath
668
+     * @param string $targetInternalPath
669
+     * @return bool
670
+     */
671
+    public function moveFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) {
672
+        if ($this->isSameStorage($sourceStorage)) {
673
+            // resolve any jailed paths
674
+            while ($sourceStorage->instanceOfStorage(Jail::class)) {
675
+                /**
676
+                 * @var Jail $sourceStorage
677
+                 */
678
+                $sourceInternalPath = $sourceStorage->getUnjailedPath($sourceInternalPath);
679
+                $sourceStorage = $sourceStorage->getUnjailedStorage();
680
+            }
681
+
682
+            return $this->rename($sourceInternalPath, $targetInternalPath);
683
+        }
684
+
685
+        if (!$sourceStorage->isDeletable($sourceInternalPath)) {
686
+            return false;
687
+        }
688
+
689
+        $result = $this->copyFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath, true);
690
+        if ($result) {
691
+            if ($sourceStorage->is_dir($sourceInternalPath)) {
692
+                $result &= $sourceStorage->rmdir($sourceInternalPath);
693
+            } else {
694
+                $result &= $sourceStorage->unlink($sourceInternalPath);
695
+            }
696
+        }
697
+        return $result;
698
+    }
699
+
700
+    /**
701
+     * @inheritdoc
702
+     */
703
+    public function getMetaData($path) {
704
+        $permissions = $this->getPermissions($path);
705
+        if (!$permissions & \OCP\Constants::PERMISSION_READ) {
706
+            //can't read, nothing we can do
707
+            return null;
708
+        }
709
+
710
+        $data = [];
711
+        $data['mimetype'] = $this->getMimeType($path);
712
+        $data['mtime'] = $this->filemtime($path);
713
+        if ($data['mtime'] === false) {
714
+            $data['mtime'] = time();
715
+        }
716
+        if ($data['mimetype'] == 'httpd/unix-directory') {
717
+            $data['size'] = -1; //unknown
718
+        } else {
719
+            $data['size'] = $this->filesize($path);
720
+        }
721
+        $data['etag'] = $this->getETag($path);
722
+        $data['storage_mtime'] = $data['mtime'];
723
+        $data['permissions'] = $permissions;
724
+        $data['name'] = basename($path);
725
+
726
+        return $data;
727
+    }
728
+
729
+    /**
730
+     * @param string $path
731
+     * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
732
+     * @param \OCP\Lock\ILockingProvider $provider
733
+     * @throws \OCP\Lock\LockedException
734
+     */
735
+    public function acquireLock($path, $type, ILockingProvider $provider) {
736
+        $logger = $this->getLockLogger();
737
+        if ($logger) {
738
+            $typeString = ($type === ILockingProvider::LOCK_SHARED) ? 'shared' : 'exclusive';
739
+            $logger->info(
740
+                sprintf(
741
+                    'acquire %s lock on "%s" on storage "%s"',
742
+                    $typeString,
743
+                    $path,
744
+                    $this->getId()
745
+                ),
746
+                [
747
+                    'app' => 'locking',
748
+                ]
749
+            );
750
+        }
751
+        try {
752
+            $provider->acquireLock('files/' . md5($this->getId() . '::' . trim($path, '/')), $type, $this->getId() . '::' . $path);
753
+        } catch (LockedException $e) {
754
+            if ($logger) {
755
+                $logger->logException($e, ['level' => ILogger::INFO]);
756
+            }
757
+            throw $e;
758
+        }
759
+    }
760
+
761
+    /**
762
+     * @param string $path
763
+     * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
764
+     * @param \OCP\Lock\ILockingProvider $provider
765
+     * @throws \OCP\Lock\LockedException
766
+     */
767
+    public function releaseLock($path, $type, ILockingProvider $provider) {
768
+        $logger = $this->getLockLogger();
769
+        if ($logger) {
770
+            $typeString = ($type === ILockingProvider::LOCK_SHARED) ? 'shared' : 'exclusive';
771
+            $logger->info(
772
+                sprintf(
773
+                    'release %s lock on "%s" on storage "%s"',
774
+                    $typeString,
775
+                    $path,
776
+                    $this->getId()
777
+                ),
778
+                [
779
+                    'app' => 'locking',
780
+                ]
781
+            );
782
+        }
783
+        try {
784
+            $provider->releaseLock('files/' . md5($this->getId() . '::' . trim($path, '/')), $type);
785
+        } catch (LockedException $e) {
786
+            if ($logger) {
787
+                $logger->logException($e, ['level' => ILogger::INFO]);
788
+            }
789
+            throw $e;
790
+        }
791
+    }
792
+
793
+    /**
794
+     * @param string $path
795
+     * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
796
+     * @param \OCP\Lock\ILockingProvider $provider
797
+     * @throws \OCP\Lock\LockedException
798
+     */
799
+    public function changeLock($path, $type, ILockingProvider $provider) {
800
+        $logger = $this->getLockLogger();
801
+        if ($logger) {
802
+            $typeString = ($type === ILockingProvider::LOCK_SHARED) ? 'shared' : 'exclusive';
803
+            $logger->info(
804
+                sprintf(
805
+                    'change lock on "%s" to %s on storage "%s"',
806
+                    $path,
807
+                    $typeString,
808
+                    $this->getId()
809
+                ),
810
+                [
811
+                    'app' => 'locking',
812
+                ]
813
+            );
814
+        }
815
+        try {
816
+            $provider->changeLock('files/' . md5($this->getId() . '::' . trim($path, '/')), $type);
817
+        } catch (LockedException $e) {
818
+            \OC::$server->getLogger()->logException($e, ['level' => ILogger::INFO]);
819
+            throw $e;
820
+        }
821
+    }
822
+
823
+    private function getLockLogger() {
824
+        if (is_null($this->shouldLogLocks)) {
825
+            $this->shouldLogLocks = \OC::$server->getConfig()->getSystemValue('filelocking.debug', false);
826
+            $this->logger = $this->shouldLogLocks ? \OC::$server->getLogger() : null;
827
+        }
828
+        return $this->logger;
829
+    }
830
+
831
+    /**
832
+     * @return array [ available, last_checked ]
833
+     */
834
+    public function getAvailability() {
835
+        return $this->getStorageCache()->getAvailability();
836
+    }
837
+
838
+    /**
839
+     * @param bool $isAvailable
840
+     */
841
+    public function setAvailability($isAvailable) {
842
+        $this->getStorageCache()->setAvailability($isAvailable);
843
+    }
844
+
845
+    /**
846
+     * @return bool
847
+     */
848
+    public function needsPartFile() {
849
+        return true;
850
+    }
851
+
852
+    /**
853
+     * fallback implementation
854
+     *
855
+     * @param string $path
856
+     * @param resource $stream
857
+     * @param int $size
858
+     * @return int
859
+     */
860
+    public function writeStream(string $path, $stream, int $size = null): int {
861
+        $target = $this->fopen($path, 'w');
862
+        if (!$target) {
863
+            throw new GenericFileException("Failed to open $path for writing");
864
+        }
865
+        try {
866
+            [$count, $result] = \OC_Helper::streamCopy($stream, $target);
867
+            if (!$result) {
868
+                throw new GenericFileException("Failed to copy stream");
869
+            }
870
+        } finally {
871
+            fclose($target);
872
+            fclose($stream);
873
+        }
874
+        return $count;
875
+    }
876
+
877
+    public function getDirectoryContent($directory): \Traversable {
878
+        $dh = $this->opendir($directory);
879
+        if (is_resource($dh)) {
880
+            $basePath = rtrim($directory, '/');
881
+            while (($file = readdir($dh)) !== false) {
882
+                if (!Filesystem::isIgnoredDir($file) && !Filesystem::isFileBlacklisted($file)) {
883
+                    $childPath = $basePath . '/' . trim($file, '/');
884
+                    $metadata = $this->getMetaData($childPath);
885
+                    if ($metadata !== null) {
886
+                        yield $metadata;
887
+                    }
888
+                }
889
+            }
890
+        }
891
+    }
892 892
 }
Please login to merge, or discard this patch.
lib/private/Files/Storage/Local.php 2 patches
Indentation   +509 added lines, -509 removed lines patch added patch discarded remove patch
@@ -52,513 +52,513 @@
 block discarded – undo
52 52
  * for local filestore, we only have to map the paths
53 53
  */
54 54
 class Local extends \OC\Files\Storage\Common {
55
-	protected $datadir;
56
-
57
-	protected $dataDirLength;
58
-
59
-	protected $allowSymlinks = false;
60
-
61
-	protected $realDataDir;
62
-
63
-	public function __construct($arguments) {
64
-		if (!isset($arguments['datadir']) || !is_string($arguments['datadir'])) {
65
-			throw new \InvalidArgumentException('No data directory set for local storage');
66
-		}
67
-		$this->datadir = str_replace('//', '/', $arguments['datadir']);
68
-		// some crazy code uses a local storage on root...
69
-		if ($this->datadir === '/') {
70
-			$this->realDataDir = $this->datadir;
71
-		} else {
72
-			$realPath = realpath($this->datadir) ?: $this->datadir;
73
-			$this->realDataDir = rtrim($realPath, '/') . '/';
74
-		}
75
-		if (substr($this->datadir, -1) !== '/') {
76
-			$this->datadir .= '/';
77
-		}
78
-		$this->dataDirLength = strlen($this->realDataDir);
79
-	}
80
-
81
-	public function __destruct() {
82
-	}
83
-
84
-	public function getId() {
85
-		return 'local::' . $this->datadir;
86
-	}
87
-
88
-	public function mkdir($path) {
89
-		return @mkdir($this->getSourcePath($path), 0777, true);
90
-	}
91
-
92
-	public function rmdir($path) {
93
-		if (!$this->isDeletable($path)) {
94
-			return false;
95
-		}
96
-		try {
97
-			$it = new \RecursiveIteratorIterator(
98
-				new \RecursiveDirectoryIterator($this->getSourcePath($path)),
99
-				\RecursiveIteratorIterator::CHILD_FIRST
100
-			);
101
-			/**
102
-			 * RecursiveDirectoryIterator on an NFS path isn't iterable with foreach
103
-			 * This bug is fixed in PHP 5.5.9 or before
104
-			 * See #8376
105
-			 */
106
-			$it->rewind();
107
-			while ($it->valid()) {
108
-				/**
109
-				 * @var \SplFileInfo $file
110
-				 */
111
-				$file = $it->current();
112
-				if (in_array($file->getBasename(), ['.', '..'])) {
113
-					$it->next();
114
-					continue;
115
-				} elseif ($file->isDir()) {
116
-					rmdir($file->getPathname());
117
-				} elseif ($file->isFile() || $file->isLink()) {
118
-					unlink($file->getPathname());
119
-				}
120
-				$it->next();
121
-			}
122
-			return rmdir($this->getSourcePath($path));
123
-		} catch (\UnexpectedValueException $e) {
124
-			return false;
125
-		}
126
-	}
127
-
128
-	public function opendir($path) {
129
-		return opendir($this->getSourcePath($path));
130
-	}
131
-
132
-	public function is_dir($path) {
133
-		if (substr($path, -1) == '/') {
134
-			$path = substr($path, 0, -1);
135
-		}
136
-		return is_dir($this->getSourcePath($path));
137
-	}
138
-
139
-	public function is_file($path) {
140
-		return is_file($this->getSourcePath($path));
141
-	}
142
-
143
-	public function stat($path) {
144
-		clearstatcache();
145
-		$fullPath = $this->getSourcePath($path);
146
-		$statResult = stat($fullPath);
147
-		if (PHP_INT_SIZE === 4 && !$this->is_dir($path)) {
148
-			$filesize = $this->filesize($path);
149
-			$statResult['size'] = $filesize;
150
-			$statResult[7] = $filesize;
151
-		}
152
-		return $statResult;
153
-	}
154
-
155
-	/**
156
-	 * @inheritdoc
157
-	 */
158
-	public function getMetaData($path) {
159
-		$fullPath = $this->getSourcePath($path);
160
-		clearstatcache();
161
-		$stat = @stat($fullPath);
162
-		if (!$stat) {
163
-			return null;
164
-		}
165
-
166
-		$permissions = Constants::PERMISSION_SHARE;
167
-		$statPermissions = $stat['mode'];
168
-		$isDir = ($statPermissions & 0x4000) === 0x4000;
169
-		if ($statPermissions & 0x0100) {
170
-			$permissions += Constants::PERMISSION_READ;
171
-		}
172
-		if ($statPermissions & 0x0080) {
173
-			$permissions += Constants::PERMISSION_UPDATE;
174
-			if ($isDir) {
175
-				$permissions += Constants::PERMISSION_CREATE;
176
-			}
177
-		}
178
-
179
-		if (!($path === '' || $path === '/')) { // deletable depends on the parents unix permissions
180
-			$parent = dirname($fullPath);
181
-			if (is_writable($parent)) {
182
-				$permissions += Constants::PERMISSION_DELETE;
183
-			}
184
-		}
185
-
186
-		$data = [];
187
-		$data['mimetype'] = $isDir ? 'httpd/unix-directory' : \OC::$server->getMimeTypeDetector()->detectPath($path);
188
-		$data['mtime'] = $stat['mtime'];
189
-		if ($data['mtime'] === false) {
190
-			$data['mtime'] = time();
191
-		}
192
-		if ($isDir) {
193
-			$data['size'] = -1; //unknown
194
-		} else {
195
-			$data['size'] = $stat['size'];
196
-		}
197
-		$data['etag'] = $this->calculateEtag($path, $stat);
198
-		$data['storage_mtime'] = $data['mtime'];
199
-		$data['permissions'] = $permissions;
200
-		$data['name'] = basename($path);
201
-
202
-		return $data;
203
-	}
204
-
205
-	public function filetype($path) {
206
-		$filetype = filetype($this->getSourcePath($path));
207
-		if ($filetype == 'link') {
208
-			$filetype = filetype(realpath($this->getSourcePath($path)));
209
-		}
210
-		return $filetype;
211
-	}
212
-
213
-	public function filesize($path) {
214
-		if ($this->is_dir($path)) {
215
-			return 0;
216
-		}
217
-		$fullPath = $this->getSourcePath($path);
218
-		if (PHP_INT_SIZE === 4) {
219
-			$helper = new \OC\LargeFileHelper;
220
-			return $helper->getFileSize($fullPath);
221
-		}
222
-		return filesize($fullPath);
223
-	}
224
-
225
-	public function isReadable($path) {
226
-		return is_readable($this->getSourcePath($path));
227
-	}
228
-
229
-	public function isUpdatable($path) {
230
-		return is_writable($this->getSourcePath($path));
231
-	}
232
-
233
-	public function file_exists($path) {
234
-		return file_exists($this->getSourcePath($path));
235
-	}
236
-
237
-	public function filemtime($path) {
238
-		$fullPath = $this->getSourcePath($path);
239
-		clearstatcache(true, $fullPath);
240
-		if (!$this->file_exists($path)) {
241
-			return false;
242
-		}
243
-		if (PHP_INT_SIZE === 4) {
244
-			$helper = new \OC\LargeFileHelper();
245
-			return $helper->getFileMtime($fullPath);
246
-		}
247
-		return filemtime($fullPath);
248
-	}
249
-
250
-	public function touch($path, $mtime = null) {
251
-		// sets the modification time of the file to the given value.
252
-		// If mtime is nil the current time is set.
253
-		// note that the access time of the file always changes to the current time.
254
-		if ($this->file_exists($path) and !$this->isUpdatable($path)) {
255
-			return false;
256
-		}
257
-		if (!is_null($mtime)) {
258
-			$result = @touch($this->getSourcePath($path), $mtime);
259
-		} else {
260
-			$result = @touch($this->getSourcePath($path));
261
-		}
262
-		if ($result) {
263
-			clearstatcache(true, $this->getSourcePath($path));
264
-		}
265
-
266
-		return $result;
267
-	}
268
-
269
-	public function file_get_contents($path) {
270
-		return file_get_contents($this->getSourcePath($path));
271
-	}
272
-
273
-	public function file_put_contents($path, $data) {
274
-		return file_put_contents($this->getSourcePath($path), $data);
275
-	}
276
-
277
-	public function unlink($path) {
278
-		if ($this->is_dir($path)) {
279
-			return $this->rmdir($path);
280
-		} elseif ($this->is_file($path)) {
281
-			return unlink($this->getSourcePath($path));
282
-		} else {
283
-			return false;
284
-		}
285
-	}
286
-
287
-	private function treeContainsBlacklistedFile(string $path): bool {
288
-		$iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($path));
289
-		foreach ($iterator as $file) {
290
-			/** @var \SplFileInfo $file */
291
-			if (Filesystem::isFileBlacklisted($file->getBasename())) {
292
-				return true;
293
-			}
294
-		}
295
-
296
-		return false;
297
-	}
298
-
299
-	public function rename($path1, $path2) {
300
-		$srcParent = dirname($path1);
301
-		$dstParent = dirname($path2);
302
-
303
-		if (!$this->isUpdatable($srcParent)) {
304
-			\OCP\Util::writeLog('core', 'unable to rename, source directory is not writable : ' . $srcParent, ILogger::ERROR);
305
-			return false;
306
-		}
307
-
308
-		if (!$this->isUpdatable($dstParent)) {
309
-			\OCP\Util::writeLog('core', 'unable to rename, destination directory is not writable : ' . $dstParent, ILogger::ERROR);
310
-			return false;
311
-		}
312
-
313
-		if (!$this->file_exists($path1)) {
314
-			\OCP\Util::writeLog('core', 'unable to rename, file does not exists : ' . $path1, ILogger::ERROR);
315
-			return false;
316
-		}
317
-
318
-		if ($this->is_dir($path2)) {
319
-			$this->rmdir($path2);
320
-		} elseif ($this->is_file($path2)) {
321
-			$this->unlink($path2);
322
-		}
323
-
324
-		if ($this->is_dir($path1)) {
325
-			// we can't move folders across devices, use copy instead
326
-			$stat1 = stat(dirname($this->getSourcePath($path1)));
327
-			$stat2 = stat(dirname($this->getSourcePath($path2)));
328
-			if ($stat1['dev'] !== $stat2['dev']) {
329
-				$result = $this->copy($path1, $path2);
330
-				if ($result) {
331
-					$result &= $this->rmdir($path1);
332
-				}
333
-				return $result;
334
-			}
335
-
336
-			if ($this->treeContainsBlacklistedFile($this->getSourcePath($path1))) {
337
-				throw new ForbiddenException('Invalid path', false);
338
-			}
339
-		}
340
-
341
-		return rename($this->getSourcePath($path1), $this->getSourcePath($path2));
342
-	}
343
-
344
-	public function copy($path1, $path2) {
345
-		if ($this->is_dir($path1)) {
346
-			return parent::copy($path1, $path2);
347
-		} else {
348
-			return copy($this->getSourcePath($path1), $this->getSourcePath($path2));
349
-		}
350
-	}
351
-
352
-	public function fopen($path, $mode) {
353
-		return fopen($this->getSourcePath($path), $mode);
354
-	}
355
-
356
-	public function hash($type, $path, $raw = false) {
357
-		return hash_file($type, $this->getSourcePath($path), $raw);
358
-	}
359
-
360
-	public function free_space($path) {
361
-		$sourcePath = $this->getSourcePath($path);
362
-		// using !is_dir because $sourcePath might be a part file or
363
-		// non-existing file, so we'd still want to use the parent dir
364
-		// in such cases
365
-		if (!is_dir($sourcePath)) {
366
-			// disk_free_space doesn't work on files
367
-			$sourcePath = dirname($sourcePath);
368
-		}
369
-		$space = @disk_free_space($sourcePath);
370
-		if ($space === false || is_null($space)) {
371
-			return \OCP\Files\FileInfo::SPACE_UNKNOWN;
372
-		}
373
-		return $space;
374
-	}
375
-
376
-	public function search($query) {
377
-		return $this->searchInDir($query);
378
-	}
379
-
380
-	public function getLocalFile($path) {
381
-		return $this->getSourcePath($path);
382
-	}
383
-
384
-	public function getLocalFolder($path) {
385
-		return $this->getSourcePath($path);
386
-	}
387
-
388
-	/**
389
-	 * @param string $query
390
-	 * @param string $dir
391
-	 * @return array
392
-	 */
393
-	protected function searchInDir($query, $dir = '') {
394
-		$files = [];
395
-		$physicalDir = $this->getSourcePath($dir);
396
-		foreach (scandir($physicalDir) as $item) {
397
-			if (\OC\Files\Filesystem::isIgnoredDir($item)) {
398
-				continue;
399
-			}
400
-			$physicalItem = $physicalDir . '/' . $item;
401
-
402
-			if (strstr(strtolower($item), strtolower($query)) !== false) {
403
-				$files[] = $dir . '/' . $item;
404
-			}
405
-			if (is_dir($physicalItem)) {
406
-				$files = array_merge($files, $this->searchInDir($query, $dir . '/' . $item));
407
-			}
408
-		}
409
-		return $files;
410
-	}
411
-
412
-	/**
413
-	 * check if a file or folder has been updated since $time
414
-	 *
415
-	 * @param string $path
416
-	 * @param int $time
417
-	 * @return bool
418
-	 */
419
-	public function hasUpdated($path, $time) {
420
-		if ($this->file_exists($path)) {
421
-			return $this->filemtime($path) > $time;
422
-		} else {
423
-			return true;
424
-		}
425
-	}
426
-
427
-	/**
428
-	 * Get the source path (on disk) of a given path
429
-	 *
430
-	 * @param string $path
431
-	 * @return string
432
-	 * @throws ForbiddenException
433
-	 */
434
-	public function getSourcePath($path) {
435
-		if (Filesystem::isFileBlacklisted($path)) {
436
-			throw new ForbiddenException('Invalid path', false);
437
-		}
438
-
439
-		$fullPath = $this->datadir . $path;
440
-		$currentPath = $path;
441
-		if ($this->allowSymlinks || $currentPath === '') {
442
-			return $fullPath;
443
-		}
444
-		$pathToResolve = $fullPath;
445
-		$realPath = realpath($pathToResolve);
446
-		while ($realPath === false) { // for non existing files check the parent directory
447
-			$currentPath = dirname($currentPath);
448
-			if ($currentPath === '' || $currentPath === '.') {
449
-				return $fullPath;
450
-			}
451
-			$realPath = realpath($this->datadir . $currentPath);
452
-		}
453
-		if ($realPath) {
454
-			$realPath = $realPath . '/';
455
-		}
456
-		if (substr($realPath, 0, $this->dataDirLength) === $this->realDataDir) {
457
-			return $fullPath;
458
-		}
459
-
460
-		\OCP\Util::writeLog('core', "Following symlinks is not allowed ('$fullPath' -> '$realPath' not inside '{$this->realDataDir}')", ILogger::ERROR);
461
-		throw new ForbiddenException('Following symlinks is not allowed', false);
462
-	}
463
-
464
-	/**
465
-	 * {@inheritdoc}
466
-	 */
467
-	public function isLocal() {
468
-		return true;
469
-	}
470
-
471
-	/**
472
-	 * get the ETag for a file or folder
473
-	 *
474
-	 * @param string $path
475
-	 * @return string
476
-	 */
477
-	public function getETag($path) {
478
-		return $this->calculateEtag($path, $this->stat($path));
479
-	}
480
-
481
-	private function calculateEtag(string $path, array $stat): string {
482
-		if ($stat['mode'] & 0x4000) { // is_dir
483
-			return parent::getETag($path);
484
-		} else {
485
-			if ($stat === false) {
486
-				return md5('');
487
-			}
488
-
489
-			$toHash = '';
490
-			if (isset($stat['mtime'])) {
491
-				$toHash .= $stat['mtime'];
492
-			}
493
-			if (isset($stat['ino'])) {
494
-				$toHash .= $stat['ino'];
495
-			}
496
-			if (isset($stat['dev'])) {
497
-				$toHash .= $stat['dev'];
498
-			}
499
-			if (isset($stat['size'])) {
500
-				$toHash .= $stat['size'];
501
-			}
502
-
503
-			return md5($toHash);
504
-		}
505
-	}
506
-
507
-	/**
508
-	 * @param IStorage $sourceStorage
509
-	 * @param string $sourceInternalPath
510
-	 * @param string $targetInternalPath
511
-	 * @param bool $preserveMtime
512
-	 * @return bool
513
-	 */
514
-	public function copyFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath, $preserveMtime = false) {
515
-		if ($sourceStorage->instanceOfStorage(Local::class)) {
516
-			if ($sourceStorage->instanceOfStorage(Jail::class)) {
517
-				/**
518
-				 * @var \OC\Files\Storage\Wrapper\Jail $sourceStorage
519
-				 */
520
-				$sourceInternalPath = $sourceStorage->getUnjailedPath($sourceInternalPath);
521
-			}
522
-			/**
523
-			 * @var \OC\Files\Storage\Local $sourceStorage
524
-			 */
525
-			$rootStorage = new Local(['datadir' => '/']);
526
-			return $rootStorage->copy($sourceStorage->getSourcePath($sourceInternalPath), $this->getSourcePath($targetInternalPath));
527
-		} else {
528
-			return parent::copyFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
529
-		}
530
-	}
531
-
532
-	/**
533
-	 * @param IStorage $sourceStorage
534
-	 * @param string $sourceInternalPath
535
-	 * @param string $targetInternalPath
536
-	 * @return bool
537
-	 */
538
-	public function moveFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) {
539
-		if ($sourceStorage->instanceOfStorage(Local::class)) {
540
-			if ($sourceStorage->instanceOfStorage(Jail::class)) {
541
-				/**
542
-				 * @var \OC\Files\Storage\Wrapper\Jail $sourceStorage
543
-				 */
544
-				$sourceInternalPath = $sourceStorage->getUnjailedPath($sourceInternalPath);
545
-			}
546
-			/**
547
-			 * @var \OC\Files\Storage\Local $sourceStorage
548
-			 */
549
-			$rootStorage = new Local(['datadir' => '/']);
550
-			return $rootStorage->rename($sourceStorage->getSourcePath($sourceInternalPath), $this->getSourcePath($targetInternalPath));
551
-		} else {
552
-			return parent::moveFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
553
-		}
554
-	}
555
-
556
-	public function writeStream(string $path, $stream, int $size = null): int {
557
-		$result = file_put_contents($this->getSourcePath($path), $stream);
558
-		if ($result === false) {
559
-			throw new GenericFileException("Failed write steam to $path");
560
-		} else {
561
-			return $result;
562
-		}
563
-	}
55
+    protected $datadir;
56
+
57
+    protected $dataDirLength;
58
+
59
+    protected $allowSymlinks = false;
60
+
61
+    protected $realDataDir;
62
+
63
+    public function __construct($arguments) {
64
+        if (!isset($arguments['datadir']) || !is_string($arguments['datadir'])) {
65
+            throw new \InvalidArgumentException('No data directory set for local storage');
66
+        }
67
+        $this->datadir = str_replace('//', '/', $arguments['datadir']);
68
+        // some crazy code uses a local storage on root...
69
+        if ($this->datadir === '/') {
70
+            $this->realDataDir = $this->datadir;
71
+        } else {
72
+            $realPath = realpath($this->datadir) ?: $this->datadir;
73
+            $this->realDataDir = rtrim($realPath, '/') . '/';
74
+        }
75
+        if (substr($this->datadir, -1) !== '/') {
76
+            $this->datadir .= '/';
77
+        }
78
+        $this->dataDirLength = strlen($this->realDataDir);
79
+    }
80
+
81
+    public function __destruct() {
82
+    }
83
+
84
+    public function getId() {
85
+        return 'local::' . $this->datadir;
86
+    }
87
+
88
+    public function mkdir($path) {
89
+        return @mkdir($this->getSourcePath($path), 0777, true);
90
+    }
91
+
92
+    public function rmdir($path) {
93
+        if (!$this->isDeletable($path)) {
94
+            return false;
95
+        }
96
+        try {
97
+            $it = new \RecursiveIteratorIterator(
98
+                new \RecursiveDirectoryIterator($this->getSourcePath($path)),
99
+                \RecursiveIteratorIterator::CHILD_FIRST
100
+            );
101
+            /**
102
+             * RecursiveDirectoryIterator on an NFS path isn't iterable with foreach
103
+             * This bug is fixed in PHP 5.5.9 or before
104
+             * See #8376
105
+             */
106
+            $it->rewind();
107
+            while ($it->valid()) {
108
+                /**
109
+                 * @var \SplFileInfo $file
110
+                 */
111
+                $file = $it->current();
112
+                if (in_array($file->getBasename(), ['.', '..'])) {
113
+                    $it->next();
114
+                    continue;
115
+                } elseif ($file->isDir()) {
116
+                    rmdir($file->getPathname());
117
+                } elseif ($file->isFile() || $file->isLink()) {
118
+                    unlink($file->getPathname());
119
+                }
120
+                $it->next();
121
+            }
122
+            return rmdir($this->getSourcePath($path));
123
+        } catch (\UnexpectedValueException $e) {
124
+            return false;
125
+        }
126
+    }
127
+
128
+    public function opendir($path) {
129
+        return opendir($this->getSourcePath($path));
130
+    }
131
+
132
+    public function is_dir($path) {
133
+        if (substr($path, -1) == '/') {
134
+            $path = substr($path, 0, -1);
135
+        }
136
+        return is_dir($this->getSourcePath($path));
137
+    }
138
+
139
+    public function is_file($path) {
140
+        return is_file($this->getSourcePath($path));
141
+    }
142
+
143
+    public function stat($path) {
144
+        clearstatcache();
145
+        $fullPath = $this->getSourcePath($path);
146
+        $statResult = stat($fullPath);
147
+        if (PHP_INT_SIZE === 4 && !$this->is_dir($path)) {
148
+            $filesize = $this->filesize($path);
149
+            $statResult['size'] = $filesize;
150
+            $statResult[7] = $filesize;
151
+        }
152
+        return $statResult;
153
+    }
154
+
155
+    /**
156
+     * @inheritdoc
157
+     */
158
+    public function getMetaData($path) {
159
+        $fullPath = $this->getSourcePath($path);
160
+        clearstatcache();
161
+        $stat = @stat($fullPath);
162
+        if (!$stat) {
163
+            return null;
164
+        }
165
+
166
+        $permissions = Constants::PERMISSION_SHARE;
167
+        $statPermissions = $stat['mode'];
168
+        $isDir = ($statPermissions & 0x4000) === 0x4000;
169
+        if ($statPermissions & 0x0100) {
170
+            $permissions += Constants::PERMISSION_READ;
171
+        }
172
+        if ($statPermissions & 0x0080) {
173
+            $permissions += Constants::PERMISSION_UPDATE;
174
+            if ($isDir) {
175
+                $permissions += Constants::PERMISSION_CREATE;
176
+            }
177
+        }
178
+
179
+        if (!($path === '' || $path === '/')) { // deletable depends on the parents unix permissions
180
+            $parent = dirname($fullPath);
181
+            if (is_writable($parent)) {
182
+                $permissions += Constants::PERMISSION_DELETE;
183
+            }
184
+        }
185
+
186
+        $data = [];
187
+        $data['mimetype'] = $isDir ? 'httpd/unix-directory' : \OC::$server->getMimeTypeDetector()->detectPath($path);
188
+        $data['mtime'] = $stat['mtime'];
189
+        if ($data['mtime'] === false) {
190
+            $data['mtime'] = time();
191
+        }
192
+        if ($isDir) {
193
+            $data['size'] = -1; //unknown
194
+        } else {
195
+            $data['size'] = $stat['size'];
196
+        }
197
+        $data['etag'] = $this->calculateEtag($path, $stat);
198
+        $data['storage_mtime'] = $data['mtime'];
199
+        $data['permissions'] = $permissions;
200
+        $data['name'] = basename($path);
201
+
202
+        return $data;
203
+    }
204
+
205
+    public function filetype($path) {
206
+        $filetype = filetype($this->getSourcePath($path));
207
+        if ($filetype == 'link') {
208
+            $filetype = filetype(realpath($this->getSourcePath($path)));
209
+        }
210
+        return $filetype;
211
+    }
212
+
213
+    public function filesize($path) {
214
+        if ($this->is_dir($path)) {
215
+            return 0;
216
+        }
217
+        $fullPath = $this->getSourcePath($path);
218
+        if (PHP_INT_SIZE === 4) {
219
+            $helper = new \OC\LargeFileHelper;
220
+            return $helper->getFileSize($fullPath);
221
+        }
222
+        return filesize($fullPath);
223
+    }
224
+
225
+    public function isReadable($path) {
226
+        return is_readable($this->getSourcePath($path));
227
+    }
228
+
229
+    public function isUpdatable($path) {
230
+        return is_writable($this->getSourcePath($path));
231
+    }
232
+
233
+    public function file_exists($path) {
234
+        return file_exists($this->getSourcePath($path));
235
+    }
236
+
237
+    public function filemtime($path) {
238
+        $fullPath = $this->getSourcePath($path);
239
+        clearstatcache(true, $fullPath);
240
+        if (!$this->file_exists($path)) {
241
+            return false;
242
+        }
243
+        if (PHP_INT_SIZE === 4) {
244
+            $helper = new \OC\LargeFileHelper();
245
+            return $helper->getFileMtime($fullPath);
246
+        }
247
+        return filemtime($fullPath);
248
+    }
249
+
250
+    public function touch($path, $mtime = null) {
251
+        // sets the modification time of the file to the given value.
252
+        // If mtime is nil the current time is set.
253
+        // note that the access time of the file always changes to the current time.
254
+        if ($this->file_exists($path) and !$this->isUpdatable($path)) {
255
+            return false;
256
+        }
257
+        if (!is_null($mtime)) {
258
+            $result = @touch($this->getSourcePath($path), $mtime);
259
+        } else {
260
+            $result = @touch($this->getSourcePath($path));
261
+        }
262
+        if ($result) {
263
+            clearstatcache(true, $this->getSourcePath($path));
264
+        }
265
+
266
+        return $result;
267
+    }
268
+
269
+    public function file_get_contents($path) {
270
+        return file_get_contents($this->getSourcePath($path));
271
+    }
272
+
273
+    public function file_put_contents($path, $data) {
274
+        return file_put_contents($this->getSourcePath($path), $data);
275
+    }
276
+
277
+    public function unlink($path) {
278
+        if ($this->is_dir($path)) {
279
+            return $this->rmdir($path);
280
+        } elseif ($this->is_file($path)) {
281
+            return unlink($this->getSourcePath($path));
282
+        } else {
283
+            return false;
284
+        }
285
+    }
286
+
287
+    private function treeContainsBlacklistedFile(string $path): bool {
288
+        $iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($path));
289
+        foreach ($iterator as $file) {
290
+            /** @var \SplFileInfo $file */
291
+            if (Filesystem::isFileBlacklisted($file->getBasename())) {
292
+                return true;
293
+            }
294
+        }
295
+
296
+        return false;
297
+    }
298
+
299
+    public function rename($path1, $path2) {
300
+        $srcParent = dirname($path1);
301
+        $dstParent = dirname($path2);
302
+
303
+        if (!$this->isUpdatable($srcParent)) {
304
+            \OCP\Util::writeLog('core', 'unable to rename, source directory is not writable : ' . $srcParent, ILogger::ERROR);
305
+            return false;
306
+        }
307
+
308
+        if (!$this->isUpdatable($dstParent)) {
309
+            \OCP\Util::writeLog('core', 'unable to rename, destination directory is not writable : ' . $dstParent, ILogger::ERROR);
310
+            return false;
311
+        }
312
+
313
+        if (!$this->file_exists($path1)) {
314
+            \OCP\Util::writeLog('core', 'unable to rename, file does not exists : ' . $path1, ILogger::ERROR);
315
+            return false;
316
+        }
317
+
318
+        if ($this->is_dir($path2)) {
319
+            $this->rmdir($path2);
320
+        } elseif ($this->is_file($path2)) {
321
+            $this->unlink($path2);
322
+        }
323
+
324
+        if ($this->is_dir($path1)) {
325
+            // we can't move folders across devices, use copy instead
326
+            $stat1 = stat(dirname($this->getSourcePath($path1)));
327
+            $stat2 = stat(dirname($this->getSourcePath($path2)));
328
+            if ($stat1['dev'] !== $stat2['dev']) {
329
+                $result = $this->copy($path1, $path2);
330
+                if ($result) {
331
+                    $result &= $this->rmdir($path1);
332
+                }
333
+                return $result;
334
+            }
335
+
336
+            if ($this->treeContainsBlacklistedFile($this->getSourcePath($path1))) {
337
+                throw new ForbiddenException('Invalid path', false);
338
+            }
339
+        }
340
+
341
+        return rename($this->getSourcePath($path1), $this->getSourcePath($path2));
342
+    }
343
+
344
+    public function copy($path1, $path2) {
345
+        if ($this->is_dir($path1)) {
346
+            return parent::copy($path1, $path2);
347
+        } else {
348
+            return copy($this->getSourcePath($path1), $this->getSourcePath($path2));
349
+        }
350
+    }
351
+
352
+    public function fopen($path, $mode) {
353
+        return fopen($this->getSourcePath($path), $mode);
354
+    }
355
+
356
+    public function hash($type, $path, $raw = false) {
357
+        return hash_file($type, $this->getSourcePath($path), $raw);
358
+    }
359
+
360
+    public function free_space($path) {
361
+        $sourcePath = $this->getSourcePath($path);
362
+        // using !is_dir because $sourcePath might be a part file or
363
+        // non-existing file, so we'd still want to use the parent dir
364
+        // in such cases
365
+        if (!is_dir($sourcePath)) {
366
+            // disk_free_space doesn't work on files
367
+            $sourcePath = dirname($sourcePath);
368
+        }
369
+        $space = @disk_free_space($sourcePath);
370
+        if ($space === false || is_null($space)) {
371
+            return \OCP\Files\FileInfo::SPACE_UNKNOWN;
372
+        }
373
+        return $space;
374
+    }
375
+
376
+    public function search($query) {
377
+        return $this->searchInDir($query);
378
+    }
379
+
380
+    public function getLocalFile($path) {
381
+        return $this->getSourcePath($path);
382
+    }
383
+
384
+    public function getLocalFolder($path) {
385
+        return $this->getSourcePath($path);
386
+    }
387
+
388
+    /**
389
+     * @param string $query
390
+     * @param string $dir
391
+     * @return array
392
+     */
393
+    protected function searchInDir($query, $dir = '') {
394
+        $files = [];
395
+        $physicalDir = $this->getSourcePath($dir);
396
+        foreach (scandir($physicalDir) as $item) {
397
+            if (\OC\Files\Filesystem::isIgnoredDir($item)) {
398
+                continue;
399
+            }
400
+            $physicalItem = $physicalDir . '/' . $item;
401
+
402
+            if (strstr(strtolower($item), strtolower($query)) !== false) {
403
+                $files[] = $dir . '/' . $item;
404
+            }
405
+            if (is_dir($physicalItem)) {
406
+                $files = array_merge($files, $this->searchInDir($query, $dir . '/' . $item));
407
+            }
408
+        }
409
+        return $files;
410
+    }
411
+
412
+    /**
413
+     * check if a file or folder has been updated since $time
414
+     *
415
+     * @param string $path
416
+     * @param int $time
417
+     * @return bool
418
+     */
419
+    public function hasUpdated($path, $time) {
420
+        if ($this->file_exists($path)) {
421
+            return $this->filemtime($path) > $time;
422
+        } else {
423
+            return true;
424
+        }
425
+    }
426
+
427
+    /**
428
+     * Get the source path (on disk) of a given path
429
+     *
430
+     * @param string $path
431
+     * @return string
432
+     * @throws ForbiddenException
433
+     */
434
+    public function getSourcePath($path) {
435
+        if (Filesystem::isFileBlacklisted($path)) {
436
+            throw new ForbiddenException('Invalid path', false);
437
+        }
438
+
439
+        $fullPath = $this->datadir . $path;
440
+        $currentPath = $path;
441
+        if ($this->allowSymlinks || $currentPath === '') {
442
+            return $fullPath;
443
+        }
444
+        $pathToResolve = $fullPath;
445
+        $realPath = realpath($pathToResolve);
446
+        while ($realPath === false) { // for non existing files check the parent directory
447
+            $currentPath = dirname($currentPath);
448
+            if ($currentPath === '' || $currentPath === '.') {
449
+                return $fullPath;
450
+            }
451
+            $realPath = realpath($this->datadir . $currentPath);
452
+        }
453
+        if ($realPath) {
454
+            $realPath = $realPath . '/';
455
+        }
456
+        if (substr($realPath, 0, $this->dataDirLength) === $this->realDataDir) {
457
+            return $fullPath;
458
+        }
459
+
460
+        \OCP\Util::writeLog('core', "Following symlinks is not allowed ('$fullPath' -> '$realPath' not inside '{$this->realDataDir}')", ILogger::ERROR);
461
+        throw new ForbiddenException('Following symlinks is not allowed', false);
462
+    }
463
+
464
+    /**
465
+     * {@inheritdoc}
466
+     */
467
+    public function isLocal() {
468
+        return true;
469
+    }
470
+
471
+    /**
472
+     * get the ETag for a file or folder
473
+     *
474
+     * @param string $path
475
+     * @return string
476
+     */
477
+    public function getETag($path) {
478
+        return $this->calculateEtag($path, $this->stat($path));
479
+    }
480
+
481
+    private function calculateEtag(string $path, array $stat): string {
482
+        if ($stat['mode'] & 0x4000) { // is_dir
483
+            return parent::getETag($path);
484
+        } else {
485
+            if ($stat === false) {
486
+                return md5('');
487
+            }
488
+
489
+            $toHash = '';
490
+            if (isset($stat['mtime'])) {
491
+                $toHash .= $stat['mtime'];
492
+            }
493
+            if (isset($stat['ino'])) {
494
+                $toHash .= $stat['ino'];
495
+            }
496
+            if (isset($stat['dev'])) {
497
+                $toHash .= $stat['dev'];
498
+            }
499
+            if (isset($stat['size'])) {
500
+                $toHash .= $stat['size'];
501
+            }
502
+
503
+            return md5($toHash);
504
+        }
505
+    }
506
+
507
+    /**
508
+     * @param IStorage $sourceStorage
509
+     * @param string $sourceInternalPath
510
+     * @param string $targetInternalPath
511
+     * @param bool $preserveMtime
512
+     * @return bool
513
+     */
514
+    public function copyFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath, $preserveMtime = false) {
515
+        if ($sourceStorage->instanceOfStorage(Local::class)) {
516
+            if ($sourceStorage->instanceOfStorage(Jail::class)) {
517
+                /**
518
+                 * @var \OC\Files\Storage\Wrapper\Jail $sourceStorage
519
+                 */
520
+                $sourceInternalPath = $sourceStorage->getUnjailedPath($sourceInternalPath);
521
+            }
522
+            /**
523
+             * @var \OC\Files\Storage\Local $sourceStorage
524
+             */
525
+            $rootStorage = new Local(['datadir' => '/']);
526
+            return $rootStorage->copy($sourceStorage->getSourcePath($sourceInternalPath), $this->getSourcePath($targetInternalPath));
527
+        } else {
528
+            return parent::copyFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
529
+        }
530
+    }
531
+
532
+    /**
533
+     * @param IStorage $sourceStorage
534
+     * @param string $sourceInternalPath
535
+     * @param string $targetInternalPath
536
+     * @return bool
537
+     */
538
+    public function moveFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) {
539
+        if ($sourceStorage->instanceOfStorage(Local::class)) {
540
+            if ($sourceStorage->instanceOfStorage(Jail::class)) {
541
+                /**
542
+                 * @var \OC\Files\Storage\Wrapper\Jail $sourceStorage
543
+                 */
544
+                $sourceInternalPath = $sourceStorage->getUnjailedPath($sourceInternalPath);
545
+            }
546
+            /**
547
+             * @var \OC\Files\Storage\Local $sourceStorage
548
+             */
549
+            $rootStorage = new Local(['datadir' => '/']);
550
+            return $rootStorage->rename($sourceStorage->getSourcePath($sourceInternalPath), $this->getSourcePath($targetInternalPath));
551
+        } else {
552
+            return parent::moveFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
553
+        }
554
+    }
555
+
556
+    public function writeStream(string $path, $stream, int $size = null): int {
557
+        $result = file_put_contents($this->getSourcePath($path), $stream);
558
+        if ($result === false) {
559
+            throw new GenericFileException("Failed write steam to $path");
560
+        } else {
561
+            return $result;
562
+        }
563
+    }
564 564
 }
Please login to merge, or discard this patch.
Spacing   +11 added lines, -11 removed lines patch added patch discarded remove patch
@@ -70,7 +70,7 @@  discard block
 block discarded – undo
70 70
 			$this->realDataDir = $this->datadir;
71 71
 		} else {
72 72
 			$realPath = realpath($this->datadir) ?: $this->datadir;
73
-			$this->realDataDir = rtrim($realPath, '/') . '/';
73
+			$this->realDataDir = rtrim($realPath, '/').'/';
74 74
 		}
75 75
 		if (substr($this->datadir, -1) !== '/') {
76 76
 			$this->datadir .= '/';
@@ -82,7 +82,7 @@  discard block
 block discarded – undo
82 82
 	}
83 83
 
84 84
 	public function getId() {
85
-		return 'local::' . $this->datadir;
85
+		return 'local::'.$this->datadir;
86 86
 	}
87 87
 
88 88
 	public function mkdir($path) {
@@ -301,17 +301,17 @@  discard block
 block discarded – undo
301 301
 		$dstParent = dirname($path2);
302 302
 
303 303
 		if (!$this->isUpdatable($srcParent)) {
304
-			\OCP\Util::writeLog('core', 'unable to rename, source directory is not writable : ' . $srcParent, ILogger::ERROR);
304
+			\OCP\Util::writeLog('core', 'unable to rename, source directory is not writable : '.$srcParent, ILogger::ERROR);
305 305
 			return false;
306 306
 		}
307 307
 
308 308
 		if (!$this->isUpdatable($dstParent)) {
309
-			\OCP\Util::writeLog('core', 'unable to rename, destination directory is not writable : ' . $dstParent, ILogger::ERROR);
309
+			\OCP\Util::writeLog('core', 'unable to rename, destination directory is not writable : '.$dstParent, ILogger::ERROR);
310 310
 			return false;
311 311
 		}
312 312
 
313 313
 		if (!$this->file_exists($path1)) {
314
-			\OCP\Util::writeLog('core', 'unable to rename, file does not exists : ' . $path1, ILogger::ERROR);
314
+			\OCP\Util::writeLog('core', 'unable to rename, file does not exists : '.$path1, ILogger::ERROR);
315 315
 			return false;
316 316
 		}
317 317
 
@@ -397,13 +397,13 @@  discard block
 block discarded – undo
397 397
 			if (\OC\Files\Filesystem::isIgnoredDir($item)) {
398 398
 				continue;
399 399
 			}
400
-			$physicalItem = $physicalDir . '/' . $item;
400
+			$physicalItem = $physicalDir.'/'.$item;
401 401
 
402 402
 			if (strstr(strtolower($item), strtolower($query)) !== false) {
403
-				$files[] = $dir . '/' . $item;
403
+				$files[] = $dir.'/'.$item;
404 404
 			}
405 405
 			if (is_dir($physicalItem)) {
406
-				$files = array_merge($files, $this->searchInDir($query, $dir . '/' . $item));
406
+				$files = array_merge($files, $this->searchInDir($query, $dir.'/'.$item));
407 407
 			}
408 408
 		}
409 409
 		return $files;
@@ -436,7 +436,7 @@  discard block
 block discarded – undo
436 436
 			throw new ForbiddenException('Invalid path', false);
437 437
 		}
438 438
 
439
-		$fullPath = $this->datadir . $path;
439
+		$fullPath = $this->datadir.$path;
440 440
 		$currentPath = $path;
441 441
 		if ($this->allowSymlinks || $currentPath === '') {
442 442
 			return $fullPath;
@@ -448,10 +448,10 @@  discard block
 block discarded – undo
448 448
 			if ($currentPath === '' || $currentPath === '.') {
449 449
 				return $fullPath;
450 450
 			}
451
-			$realPath = realpath($this->datadir . $currentPath);
451
+			$realPath = realpath($this->datadir.$currentPath);
452 452
 		}
453 453
 		if ($realPath) {
454
-			$realPath = $realPath . '/';
454
+			$realPath = $realPath.'/';
455 455
 		}
456 456
 		if (substr($realPath, 0, $this->dataDirLength) === $this->realDataDir) {
457 457
 			return $fullPath;
Please login to merge, or discard this patch.
lib/private/Files/Cache/Updater.php 1 patch
Indentation   +190 added lines, -190 removed lines patch added patch discarded remove patch
@@ -38,220 +38,220 @@
 block discarded – undo
38 38
  *
39 39
  */
40 40
 class Updater implements IUpdater {
41
-	/**
42
-	 * @var bool
43
-	 */
44
-	protected $enabled = true;
41
+    /**
42
+     * @var bool
43
+     */
44
+    protected $enabled = true;
45 45
 
46
-	/**
47
-	 * @var \OC\Files\Storage\Storage
48
-	 */
49
-	protected $storage;
46
+    /**
47
+     * @var \OC\Files\Storage\Storage
48
+     */
49
+    protected $storage;
50 50
 
51
-	/**
52
-	 * @var \OC\Files\Cache\Propagator
53
-	 */
54
-	protected $propagator;
51
+    /**
52
+     * @var \OC\Files\Cache\Propagator
53
+     */
54
+    protected $propagator;
55 55
 
56
-	/**
57
-	 * @var Scanner
58
-	 */
59
-	protected $scanner;
56
+    /**
57
+     * @var Scanner
58
+     */
59
+    protected $scanner;
60 60
 
61
-	/**
62
-	 * @var Cache
63
-	 */
64
-	protected $cache;
61
+    /**
62
+     * @var Cache
63
+     */
64
+    protected $cache;
65 65
 
66
-	/**
67
-	 * @param \OC\Files\Storage\Storage $storage
68
-	 */
69
-	public function __construct(\OC\Files\Storage\Storage $storage) {
70
-		$this->storage = $storage;
71
-		$this->propagator = $storage->getPropagator();
72
-		$this->scanner = $storage->getScanner();
73
-		$this->cache = $storage->getCache();
74
-	}
66
+    /**
67
+     * @param \OC\Files\Storage\Storage $storage
68
+     */
69
+    public function __construct(\OC\Files\Storage\Storage $storage) {
70
+        $this->storage = $storage;
71
+        $this->propagator = $storage->getPropagator();
72
+        $this->scanner = $storage->getScanner();
73
+        $this->cache = $storage->getCache();
74
+    }
75 75
 
76
-	/**
77
-	 * Disable updating the cache trough this updater
78
-	 */
79
-	public function disable() {
80
-		$this->enabled = false;
81
-	}
76
+    /**
77
+     * Disable updating the cache trough this updater
78
+     */
79
+    public function disable() {
80
+        $this->enabled = false;
81
+    }
82 82
 
83
-	/**
84
-	 * Re-enable the updating of the cache trough this updater
85
-	 */
86
-	public function enable() {
87
-		$this->enabled = true;
88
-	}
83
+    /**
84
+     * Re-enable the updating of the cache trough this updater
85
+     */
86
+    public function enable() {
87
+        $this->enabled = true;
88
+    }
89 89
 
90
-	/**
91
-	 * Get the propagator for etags and mtime for the view the updater works on
92
-	 *
93
-	 * @return Propagator
94
-	 */
95
-	public function getPropagator() {
96
-		return $this->propagator;
97
-	}
90
+    /**
91
+     * Get the propagator for etags and mtime for the view the updater works on
92
+     *
93
+     * @return Propagator
94
+     */
95
+    public function getPropagator() {
96
+        return $this->propagator;
97
+    }
98 98
 
99
-	/**
100
-	 * Propagate etag and mtime changes for the parent folders of $path up to the root of the filesystem
101
-	 *
102
-	 * @param string $path the path of the file to propagate the changes for
103
-	 * @param int|null $time the timestamp to set as mtime for the parent folders, if left out the current time is used
104
-	 */
105
-	public function propagate($path, $time = null) {
106
-		if (Scanner::isPartialFile($path)) {
107
-			return;
108
-		}
109
-		$this->propagator->propagateChange($path, $time);
110
-	}
99
+    /**
100
+     * Propagate etag and mtime changes for the parent folders of $path up to the root of the filesystem
101
+     *
102
+     * @param string $path the path of the file to propagate the changes for
103
+     * @param int|null $time the timestamp to set as mtime for the parent folders, if left out the current time is used
104
+     */
105
+    public function propagate($path, $time = null) {
106
+        if (Scanner::isPartialFile($path)) {
107
+            return;
108
+        }
109
+        $this->propagator->propagateChange($path, $time);
110
+    }
111 111
 
112
-	/**
113
-	 * Update the cache for $path and update the size, etag and mtime of the parent folders
114
-	 *
115
-	 * @param string $path
116
-	 * @param int $time
117
-	 */
118
-	public function update($path, $time = null) {
119
-		if (!$this->enabled or Scanner::isPartialFile($path)) {
120
-			return;
121
-		}
122
-		if (is_null($time)) {
123
-			$time = time();
124
-		}
112
+    /**
113
+     * Update the cache for $path and update the size, etag and mtime of the parent folders
114
+     *
115
+     * @param string $path
116
+     * @param int $time
117
+     */
118
+    public function update($path, $time = null) {
119
+        if (!$this->enabled or Scanner::isPartialFile($path)) {
120
+            return;
121
+        }
122
+        if (is_null($time)) {
123
+            $time = time();
124
+        }
125 125
 
126
-		$data = $this->scanner->scan($path, Scanner::SCAN_SHALLOW, -1, false);
127
-		if (
128
-			isset($data['oldSize']) && isset($data['size']) &&
129
-			!$data['encrypted'] // encryption is a pita and touches the cache itself
130
-		) {
131
-			$sizeDifference = $data['size'] - $data['oldSize'];
132
-		} else {
133
-			// scanner didn't provide size info, fallback to full size calculation
134
-			$sizeDifference = 0;
135
-			if ($this->cache instanceof Cache) {
136
-				$this->cache->correctFolderSize($path, $data);
137
-			}
138
-		}
139
-		$this->correctParentStorageMtime($path);
140
-		$this->propagator->propagateChange($path, $time, $sizeDifference);
141
-	}
126
+        $data = $this->scanner->scan($path, Scanner::SCAN_SHALLOW, -1, false);
127
+        if (
128
+            isset($data['oldSize']) && isset($data['size']) &&
129
+            !$data['encrypted'] // encryption is a pita and touches the cache itself
130
+        ) {
131
+            $sizeDifference = $data['size'] - $data['oldSize'];
132
+        } else {
133
+            // scanner didn't provide size info, fallback to full size calculation
134
+            $sizeDifference = 0;
135
+            if ($this->cache instanceof Cache) {
136
+                $this->cache->correctFolderSize($path, $data);
137
+            }
138
+        }
139
+        $this->correctParentStorageMtime($path);
140
+        $this->propagator->propagateChange($path, $time, $sizeDifference);
141
+    }
142 142
 
143
-	/**
144
-	 * Remove $path from the cache and update the size, etag and mtime of the parent folders
145
-	 *
146
-	 * @param string $path
147
-	 */
148
-	public function remove($path) {
149
-		if (!$this->enabled or Scanner::isPartialFile($path)) {
150
-			return;
151
-		}
143
+    /**
144
+     * Remove $path from the cache and update the size, etag and mtime of the parent folders
145
+     *
146
+     * @param string $path
147
+     */
148
+    public function remove($path) {
149
+        if (!$this->enabled or Scanner::isPartialFile($path)) {
150
+            return;
151
+        }
152 152
 
153
-		$parent = dirname($path);
154
-		if ($parent === '.') {
155
-			$parent = '';
156
-		}
153
+        $parent = dirname($path);
154
+        if ($parent === '.') {
155
+            $parent = '';
156
+        }
157 157
 
158
-		$entry = $this->cache->get($path);
158
+        $entry = $this->cache->get($path);
159 159
 
160
-		$this->cache->remove($path);
160
+        $this->cache->remove($path);
161 161
 
162
-		$this->correctParentStorageMtime($path);
163
-		if ($entry instanceof ICacheEntry) {
164
-			$this->propagator->propagateChange($path, time(), -$entry->getSize());
165
-		} else {
166
-			$this->propagator->propagateChange($path, time());
167
-			if ($this->cache instanceof Cache) {
168
-				$this->cache->correctFolderSize($parent);
169
-			}
170
-		}
171
-	}
162
+        $this->correctParentStorageMtime($path);
163
+        if ($entry instanceof ICacheEntry) {
164
+            $this->propagator->propagateChange($path, time(), -$entry->getSize());
165
+        } else {
166
+            $this->propagator->propagateChange($path, time());
167
+            if ($this->cache instanceof Cache) {
168
+                $this->cache->correctFolderSize($parent);
169
+            }
170
+        }
171
+    }
172 172
 
173
-	/**
174
-	 * Rename a file or folder in the cache and update the size, etag and mtime of the parent folders
175
-	 *
176
-	 * @param IStorage $sourceStorage
177
-	 * @param string $source
178
-	 * @param string $target
179
-	 */
180
-	public function renameFromStorage(IStorage $sourceStorage, $source, $target) {
181
-		if (!$this->enabled or Scanner::isPartialFile($source) or Scanner::isPartialFile($target)) {
182
-			return;
183
-		}
173
+    /**
174
+     * Rename a file or folder in the cache and update the size, etag and mtime of the parent folders
175
+     *
176
+     * @param IStorage $sourceStorage
177
+     * @param string $source
178
+     * @param string $target
179
+     */
180
+    public function renameFromStorage(IStorage $sourceStorage, $source, $target) {
181
+        if (!$this->enabled or Scanner::isPartialFile($source) or Scanner::isPartialFile($target)) {
182
+            return;
183
+        }
184 184
 
185
-		$time = time();
185
+        $time = time();
186 186
 
187
-		$sourceCache = $sourceStorage->getCache();
188
-		$sourceUpdater = $sourceStorage->getUpdater();
189
-		$sourcePropagator = $sourceStorage->getPropagator();
187
+        $sourceCache = $sourceStorage->getCache();
188
+        $sourceUpdater = $sourceStorage->getUpdater();
189
+        $sourcePropagator = $sourceStorage->getPropagator();
190 190
 
191
-		$sourceInfo = $sourceCache->get($source);
191
+        $sourceInfo = $sourceCache->get($source);
192 192
 
193
-		if ($sourceInfo !== false) {
194
-			if ($this->cache->inCache($target)) {
195
-				$this->cache->remove($target);
196
-			}
193
+        if ($sourceInfo !== false) {
194
+            if ($this->cache->inCache($target)) {
195
+                $this->cache->remove($target);
196
+            }
197 197
 
198
-			if ($sourceStorage === $this->storage) {
199
-				$this->cache->move($source, $target);
200
-			} else {
201
-				$this->cache->moveFromCache($sourceCache, $source, $target);
202
-			}
198
+            if ($sourceStorage === $this->storage) {
199
+                $this->cache->move($source, $target);
200
+            } else {
201
+                $this->cache->moveFromCache($sourceCache, $source, $target);
202
+            }
203 203
 
204
-			if (pathinfo($source, PATHINFO_EXTENSION) !== pathinfo($target, PATHINFO_EXTENSION) && $sourceInfo->getMimeType() !== FileInfo::MIMETYPE_FOLDER) {
205
-				// handle mime type change
206
-				$mimeType = $this->storage->getMimeType($target);
207
-				$fileId = $this->cache->getId($target);
208
-				$this->cache->update($fileId, ['mimetype' => $mimeType]);
209
-			}
210
-		}
204
+            if (pathinfo($source, PATHINFO_EXTENSION) !== pathinfo($target, PATHINFO_EXTENSION) && $sourceInfo->getMimeType() !== FileInfo::MIMETYPE_FOLDER) {
205
+                // handle mime type change
206
+                $mimeType = $this->storage->getMimeType($target);
207
+                $fileId = $this->cache->getId($target);
208
+                $this->cache->update($fileId, ['mimetype' => $mimeType]);
209
+            }
210
+        }
211 211
 
212
-		if ($sourceCache instanceof Cache) {
213
-			$sourceCache->correctFolderSize($source);
214
-		}
215
-		if ($this->cache instanceof Cache) {
216
-			$this->cache->correctFolderSize($target);
217
-		}
218
-		if ($sourceUpdater instanceof Updater) {
219
-			$sourceUpdater->correctParentStorageMtime($source);
220
-		}
221
-		$this->correctParentStorageMtime($target);
222
-		$this->updateStorageMTimeOnly($target);
223
-		$sourcePropagator->propagateChange($source, $time);
224
-		$this->propagator->propagateChange($target, $time);
225
-	}
212
+        if ($sourceCache instanceof Cache) {
213
+            $sourceCache->correctFolderSize($source);
214
+        }
215
+        if ($this->cache instanceof Cache) {
216
+            $this->cache->correctFolderSize($target);
217
+        }
218
+        if ($sourceUpdater instanceof Updater) {
219
+            $sourceUpdater->correctParentStorageMtime($source);
220
+        }
221
+        $this->correctParentStorageMtime($target);
222
+        $this->updateStorageMTimeOnly($target);
223
+        $sourcePropagator->propagateChange($source, $time);
224
+        $this->propagator->propagateChange($target, $time);
225
+    }
226 226
 
227
-	private function updateStorageMTimeOnly($internalPath) {
228
-		$fileId = $this->cache->getId($internalPath);
229
-		if ($fileId !== -1) {
230
-			$mtime = $this->storage->filemtime($internalPath);
231
-			if ($mtime !== false) {
232
-				$this->cache->update(
233
-					$fileId, [
234
-						'mtime' => null, // this magic tells it to not overwrite mtime
235
-						'storage_mtime' => $mtime
236
-					]
237
-				);
238
-			}
239
-		}
240
-	}
227
+    private function updateStorageMTimeOnly($internalPath) {
228
+        $fileId = $this->cache->getId($internalPath);
229
+        if ($fileId !== -1) {
230
+            $mtime = $this->storage->filemtime($internalPath);
231
+            if ($mtime !== false) {
232
+                $this->cache->update(
233
+                    $fileId, [
234
+                        'mtime' => null, // this magic tells it to not overwrite mtime
235
+                        'storage_mtime' => $mtime
236
+                    ]
237
+                );
238
+            }
239
+        }
240
+    }
241 241
 
242
-	/**
243
-	 * update the storage_mtime of the direct parent in the cache to the mtime from the storage
244
-	 *
245
-	 * @param string $internalPath
246
-	 */
247
-	private function correctParentStorageMtime($internalPath) {
248
-		$parentId = $this->cache->getParentId($internalPath);
249
-		$parent = dirname($internalPath);
250
-		if ($parentId != -1) {
251
-			$mtime = $this->storage->filemtime($parent);
252
-			if ($mtime !== false) {
253
-				$this->cache->update($parentId, ['storage_mtime' => $mtime]);
254
-			}
255
-		}
256
-	}
242
+    /**
243
+     * update the storage_mtime of the direct parent in the cache to the mtime from the storage
244
+     *
245
+     * @param string $internalPath
246
+     */
247
+    private function correctParentStorageMtime($internalPath) {
248
+        $parentId = $this->cache->getParentId($internalPath);
249
+        $parent = dirname($internalPath);
250
+        if ($parentId != -1) {
251
+            $mtime = $this->storage->filemtime($parent);
252
+            if ($mtime !== false) {
253
+                $this->cache->update($parentId, ['storage_mtime' => $mtime]);
254
+            }
255
+        }
256
+    }
257 257
 }
Please login to merge, or discard this patch.