Passed
Push — master ( aaa226...2be74e )
by Blizzz
16:51 queued 14s
created
apps/files_trashbin/lib/Command/RestoreAllFiles.php 1 patch
Indentation   +117 added lines, -117 removed lines patch added patch discarded remove patch
@@ -35,131 +35,131 @@
 block discarded – undo
35 35
 
36 36
 class RestoreAllFiles extends Base {
37 37
 
38
-	/** @var IUserManager */
39
-	protected $userManager;
38
+    /** @var IUserManager */
39
+    protected $userManager;
40 40
 
41
-	/** @var IRootFolder */
42
-	protected $rootFolder;
41
+    /** @var IRootFolder */
42
+    protected $rootFolder;
43 43
 
44
-	/** @var \OCP\IDBConnection */
45
-	protected $dbConnection;
44
+    /** @var \OCP\IDBConnection */
45
+    protected $dbConnection;
46 46
 
47
-	/** @var IL10N */
48
-	protected $l10n;
47
+    /** @var IL10N */
48
+    protected $l10n;
49 49
 
50
-	/**
51
-	 * @param IRootFolder $rootFolder
52
-	 * @param IUserManager $userManager
53
-	 * @param IDBConnection $dbConnection
54
-	 */
55
-	public function __construct(IRootFolder $rootFolder, IUserManager $userManager, IDBConnection $dbConnection, IFactory $l10nFactory) {
56
-		parent::__construct();
57
-		$this->userManager = $userManager;
58
-		$this->rootFolder = $rootFolder;
59
-		$this->dbConnection = $dbConnection;
60
-		$this->l10n = $l10nFactory->get('files_trashbin');
61
-	}
50
+    /**
51
+     * @param IRootFolder $rootFolder
52
+     * @param IUserManager $userManager
53
+     * @param IDBConnection $dbConnection
54
+     */
55
+    public function __construct(IRootFolder $rootFolder, IUserManager $userManager, IDBConnection $dbConnection, IFactory $l10nFactory) {
56
+        parent::__construct();
57
+        $this->userManager = $userManager;
58
+        $this->rootFolder = $rootFolder;
59
+        $this->dbConnection = $dbConnection;
60
+        $this->l10n = $l10nFactory->get('files_trashbin');
61
+    }
62 62
 
63
-	protected function configure(): void {
64
-		parent::configure();
65
-		$this
66
-			->setName('trashbin:restore')
67
-			->setDescription('Restore all deleted files')
68
-			->addArgument(
69
-				'user_id',
70
-				InputArgument::OPTIONAL | InputArgument::IS_ARRAY,
71
-				'restore all deleted files of the given user(s)'
72
-			)
73
-			->addOption(
74
-				'all-users',
75
-				null,
76
-				InputOption::VALUE_NONE,
77
-				'run action on all users'
78
-			);
79
-	}
63
+    protected function configure(): void {
64
+        parent::configure();
65
+        $this
66
+            ->setName('trashbin:restore')
67
+            ->setDescription('Restore all deleted files')
68
+            ->addArgument(
69
+                'user_id',
70
+                InputArgument::OPTIONAL | InputArgument::IS_ARRAY,
71
+                'restore all deleted files of the given user(s)'
72
+            )
73
+            ->addOption(
74
+                'all-users',
75
+                null,
76
+                InputOption::VALUE_NONE,
77
+                'run action on all users'
78
+            );
79
+    }
80 80
 
81
-	protected function execute(InputInterface $input, OutputInterface $output): int {
82
-		/** @var string[] $users */
83
-		$users = $input->getArgument('user_id');
84
-		if ((!empty($users)) and ($input->getOption('all-users'))) {
85
-			throw new InvalidOptionException('Either specify a user_id or --all-users');
86
-		} elseif (!empty($users)) {
87
-			foreach ($users as $user) {
88
-				if ($this->userManager->userExists($user)) {
89
-					$output->writeln("Restoring deleted files for user <info>$user</info>");
90
-					$this->restoreDeletedFiles($user, $output);
91
-				} else {
92
-					$output->writeln("<error>Unknown user $user</error>");
93
-					return 1;
94
-				}
95
-			}
96
-		} elseif ($input->getOption('all-users')) {
97
-			$output->writeln('Restoring deleted files for all users');
98
-			foreach ($this->userManager->getBackends() as $backend) {
99
-				$name = get_class($backend);
100
-				if ($backend instanceof IUserBackend) {
101
-					$name = $backend->getBackendName();
102
-				}
103
-				$output->writeln("Restoring deleted files for users on backend <info>$name</info>");
104
-				$limit = 500;
105
-				$offset = 0;
106
-				do {
107
-					$users = $backend->getUsers('', $limit, $offset);
108
-					foreach ($users as $user) {
109
-						$output->writeln("<info>$user</info>");
110
-						$this->restoreDeletedFiles($user, $output);
111
-					}
112
-					$offset += $limit;
113
-				} while (count($users) >= $limit);
114
-			}
115
-		} else {
116
-			throw new InvalidOptionException('Either specify a user_id or --all-users');
117
-		}
118
-		return 0;
119
-	}
81
+    protected function execute(InputInterface $input, OutputInterface $output): int {
82
+        /** @var string[] $users */
83
+        $users = $input->getArgument('user_id');
84
+        if ((!empty($users)) and ($input->getOption('all-users'))) {
85
+            throw new InvalidOptionException('Either specify a user_id or --all-users');
86
+        } elseif (!empty($users)) {
87
+            foreach ($users as $user) {
88
+                if ($this->userManager->userExists($user)) {
89
+                    $output->writeln("Restoring deleted files for user <info>$user</info>");
90
+                    $this->restoreDeletedFiles($user, $output);
91
+                } else {
92
+                    $output->writeln("<error>Unknown user $user</error>");
93
+                    return 1;
94
+                }
95
+            }
96
+        } elseif ($input->getOption('all-users')) {
97
+            $output->writeln('Restoring deleted files for all users');
98
+            foreach ($this->userManager->getBackends() as $backend) {
99
+                $name = get_class($backend);
100
+                if ($backend instanceof IUserBackend) {
101
+                    $name = $backend->getBackendName();
102
+                }
103
+                $output->writeln("Restoring deleted files for users on backend <info>$name</info>");
104
+                $limit = 500;
105
+                $offset = 0;
106
+                do {
107
+                    $users = $backend->getUsers('', $limit, $offset);
108
+                    foreach ($users as $user) {
109
+                        $output->writeln("<info>$user</info>");
110
+                        $this->restoreDeletedFiles($user, $output);
111
+                    }
112
+                    $offset += $limit;
113
+                } while (count($users) >= $limit);
114
+            }
115
+        } else {
116
+            throw new InvalidOptionException('Either specify a user_id or --all-users');
117
+        }
118
+        return 0;
119
+    }
120 120
 
121
-	/**
122
-	 * Restore deleted files for the given user
123
-	 *
124
-	 * @param string $uid
125
-	 * @param OutputInterface $output
126
-	 */
127
-	protected function restoreDeletedFiles(string $uid, OutputInterface $output): void {
128
-		\OC_Util::tearDownFS();
129
-		\OC_Util::setupFS($uid);
130
-		\OC_User::setUserId($uid);
121
+    /**
122
+     * Restore deleted files for the given user
123
+     *
124
+     * @param string $uid
125
+     * @param OutputInterface $output
126
+     */
127
+    protected function restoreDeletedFiles(string $uid, OutputInterface $output): void {
128
+        \OC_Util::tearDownFS();
129
+        \OC_Util::setupFS($uid);
130
+        \OC_User::setUserId($uid);
131 131
 
132
-		// Sort by most recently deleted first
133
-		// (Restoring in order of most recently deleted preserves nested file paths.
134
-		// See https://github.com/nextcloud/server/issues/31200#issuecomment-1130358549)
135
-		$filesInTrash = Helper::getTrashFiles('/', $uid, 'mtime', true);
132
+        // Sort by most recently deleted first
133
+        // (Restoring in order of most recently deleted preserves nested file paths.
134
+        // See https://github.com/nextcloud/server/issues/31200#issuecomment-1130358549)
135
+        $filesInTrash = Helper::getTrashFiles('/', $uid, 'mtime', true);
136 136
 
137
-		$trashCount = count($filesInTrash);
138
-		if ($trashCount == 0) {
139
-			$output->writeln("User has no deleted files in the trashbin");
140
-			return;
141
-		}
142
-		$output->writeln("Preparing to restore <info>$trashCount</info> files...");
143
-		$count = 0;
144
-		foreach ($filesInTrash as $trashFile) {
145
-			$filename = $trashFile->getName();
146
-			$timestamp = $trashFile->getMtime();
147
-			$humanTime = $this->l10n->l('datetime', $timestamp);
148
-			$output->write("File <info>$filename</info> originally deleted at <info>$humanTime</info> ");
149
-			$file = Trashbin::getTrashFilename($filename, $timestamp);
150
-			$location = Trashbin::getLocation($uid, $filename, (string) $timestamp);
151
-			if ($location === '.') {
152
-				$location = '';
153
-			}
154
-			$output->write("restoring to <info>/$location</info>:");
155
-			if (Trashbin::restore($file, $filename, $timestamp)) {
156
-				$count = $count + 1;
157
-				$output->writeln(" <info>success</info>");
158
-			} else {
159
-				$output->writeln(" <error>failed</error>");
160
-			}
161
-		}
137
+        $trashCount = count($filesInTrash);
138
+        if ($trashCount == 0) {
139
+            $output->writeln("User has no deleted files in the trashbin");
140
+            return;
141
+        }
142
+        $output->writeln("Preparing to restore <info>$trashCount</info> files...");
143
+        $count = 0;
144
+        foreach ($filesInTrash as $trashFile) {
145
+            $filename = $trashFile->getName();
146
+            $timestamp = $trashFile->getMtime();
147
+            $humanTime = $this->l10n->l('datetime', $timestamp);
148
+            $output->write("File <info>$filename</info> originally deleted at <info>$humanTime</info> ");
149
+            $file = Trashbin::getTrashFilename($filename, $timestamp);
150
+            $location = Trashbin::getLocation($uid, $filename, (string) $timestamp);
151
+            if ($location === '.') {
152
+                $location = '';
153
+            }
154
+            $output->write("restoring to <info>/$location</info>:");
155
+            if (Trashbin::restore($file, $filename, $timestamp)) {
156
+                $count = $count + 1;
157
+                $output->writeln(" <info>success</info>");
158
+            } else {
159
+                $output->writeln(" <error>failed</error>");
160
+            }
161
+        }
162 162
 
163
-		$output->writeln("Successfully restored <info>$count</info> out of <info>$trashCount</info> files.");
164
-	}
163
+        $output->writeln("Successfully restored <info>$count</info> out of <info>$trashCount</info> files.");
164
+    }
165 165
 }
Please login to merge, or discard this patch.
apps/files_trashbin/lib/Trashbin.php 2 patches
Indentation   +1079 added lines, -1079 removed lines patch added patch discarded remove patch
@@ -65,1083 +65,1083 @@
 block discarded – undo
65 65
 
66 66
 class Trashbin {
67 67
 
68
-	// unit: percentage; 50% of available disk space/quota
69
-	public const DEFAULTMAXSIZE = 50;
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 = OC_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 !== OC_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::$server->getDatabaseConnection()->getQueryBuilder();
127
-		$query->select('id', 'timestamp', 'location')
128
-			->from('files_trash')
129
-			->where($query->expr()->eq('user', $query->createNamedParameter($user)));
130
-		$result = $query->executeQuery();
131
-		$array = [];
132
-		while ($row = $result->fetch()) {
133
-			if (isset($array[$row['id']])) {
134
-				$array[$row['id']][$row['timestamp']] = $row['location'];
135
-			} else {
136
-				$array[$row['id']] = [$row['timestamp'] => $row['location']];
137
-			}
138
-		}
139
-		$result->closeCursor();
140
-		return $array;
141
-	}
142
-
143
-	/**
144
-	 * get original location of file
145
-	 *
146
-	 * @param string $user
147
-	 * @param string $filename
148
-	 * @param string $timestamp
149
-	 * @return string original location
150
-	 */
151
-	public static function getLocation($user, $filename, $timestamp) {
152
-		$query = \OC::$server->getDatabaseConnection()->getQueryBuilder();
153
-		$query->select('location')
154
-			->from('files_trash')
155
-			->where($query->expr()->eq('user', $query->createNamedParameter($user)))
156
-			->andWhere($query->expr()->eq('id', $query->createNamedParameter($filename)))
157
-			->andWhere($query->expr()->eq('timestamp', $query->createNamedParameter($timestamp)));
158
-
159
-		$result = $query->executeQuery();
160
-		$row = $result->fetch();
161
-		$result->closeCursor();
162
-
163
-		if (isset($row['location'])) {
164
-			return $row['location'];
165
-		} else {
166
-			return false;
167
-		}
168
-	}
169
-
170
-	private static function setUpTrash($user) {
171
-		$view = new View('/' . $user);
172
-		if (!$view->is_dir('files_trashbin')) {
173
-			$view->mkdir('files_trashbin');
174
-		}
175
-		if (!$view->is_dir('files_trashbin/files')) {
176
-			$view->mkdir('files_trashbin/files');
177
-		}
178
-		if (!$view->is_dir('files_trashbin/versions')) {
179
-			$view->mkdir('files_trashbin/versions');
180
-		}
181
-		if (!$view->is_dir('files_trashbin/keys')) {
182
-			$view->mkdir('files_trashbin/keys');
183
-		}
184
-	}
185
-
186
-
187
-	/**
188
-	 * copy file to owners trash
189
-	 *
190
-	 * @param string $sourcePath
191
-	 * @param string $owner
192
-	 * @param string $targetPath
193
-	 * @param $user
194
-	 * @param integer $timestamp
195
-	 */
196
-	private static function copyFilesToUser($sourcePath, $owner, $targetPath, $user, $timestamp) {
197
-		self::setUpTrash($owner);
198
-
199
-		$targetFilename = basename($targetPath);
200
-		$targetLocation = dirname($targetPath);
201
-
202
-		$sourceFilename = basename($sourcePath);
203
-
204
-		$view = new View('/');
205
-
206
-		$target = $user . '/files_trashbin/files/' . static::getTrashFilename($targetFilename, $timestamp);
207
-		$source = $owner . '/files_trashbin/files/' . static::getTrashFilename($sourceFilename, $timestamp);
208
-		$free = $view->free_space($target);
209
-		$isUnknownOrUnlimitedFreeSpace = $free < 0;
210
-		$isEnoughFreeSpaceLeft = $view->filesize($source) < $free;
211
-		if ($isUnknownOrUnlimitedFreeSpace || $isEnoughFreeSpaceLeft) {
212
-			self::copy_recursive($source, $target, $view);
213
-		}
214
-
215
-
216
-		if ($view->file_exists($target)) {
217
-			$query = \OC::$server->getDatabaseConnection()->getQueryBuilder();
218
-			$query->insert('files_trash')
219
-				->setValue('id', $query->createNamedParameter($targetFilename))
220
-				->setValue('timestamp', $query->createNamedParameter($timestamp))
221
-				->setValue('location', $query->createNamedParameter($targetLocation))
222
-				->setValue('user', $query->createNamedParameter($user));
223
-			$result = $query->executeStatement();
224
-			if (!$result) {
225
-				\OC::$server->get(LoggerInterface::class)->error('trash bin database couldn\'t be updated for the files owner', ['app' => 'files_trashbin']);
226
-			}
227
-		}
228
-	}
229
-
230
-
231
-	/**
232
-	 * move file to the trash bin
233
-	 *
234
-	 * @param string $file_path path to the deleted file/directory relative to the files root directory
235
-	 * @param bool $ownerOnly delete for owner only (if file gets moved out of a shared folder)
236
-	 *
237
-	 * @return bool
238
-	 */
239
-	public static function move2trash($file_path, $ownerOnly = false) {
240
-		// get the user for which the filesystem is setup
241
-		$root = Filesystem::getRoot();
242
-		[, $user] = explode('/', $root);
243
-		[$owner, $ownerPath] = self::getUidAndFilename($file_path);
244
-
245
-		// if no owner found (ex: ext storage + share link), will use the current user's trashbin then
246
-		if (is_null($owner)) {
247
-			$owner = $user;
248
-			$ownerPath = $file_path;
249
-		}
250
-
251
-		$ownerView = new View('/' . $owner);
252
-
253
-		// file has been deleted in between
254
-		if (is_null($ownerPath) || $ownerPath === '') {
255
-			return true;
256
-		}
257
-
258
-		$sourceInfo = $ownerView->getFileInfo('/files/' . $ownerPath);
259
-
260
-		if ($sourceInfo === false) {
261
-			return true;
262
-		}
263
-
264
-		self::setUpTrash($user);
265
-		if ($owner !== $user) {
266
-			// also setup for owner
267
-			self::setUpTrash($owner);
268
-		}
269
-
270
-		$path_parts = pathinfo($ownerPath);
271
-
272
-		$filename = $path_parts['basename'];
273
-		$location = $path_parts['dirname'];
274
-		/** @var ITimeFactory $timeFactory */
275
-		$timeFactory = \OC::$server->query(ITimeFactory::class);
276
-		$timestamp = $timeFactory->getTime();
277
-
278
-		$lockingProvider = \OC::$server->getLockingProvider();
279
-
280
-		// disable proxy to prevent recursive calls
281
-		$trashPath = '/files_trashbin/files/' . static::getTrashFilename($filename, $timestamp);
282
-		$gotLock = false;
283
-
284
-		while (!$gotLock) {
285
-			try {
286
-				/** @var \OC\Files\Storage\Storage $trashStorage */
287
-				[$trashStorage, $trashInternalPath] = $ownerView->resolvePath($trashPath);
288
-
289
-				$trashStorage->acquireLock($trashInternalPath, ILockingProvider::LOCK_EXCLUSIVE, $lockingProvider);
290
-				$gotLock = true;
291
-			} catch (LockedException $e) {
292
-				// a file with the same name is being deleted concurrently
293
-				// nudge the timestamp a bit to resolve the conflict
294
-
295
-				$timestamp = $timestamp + 1;
296
-
297
-				$trashPath = '/files_trashbin/files/' . static::getTrashFilename($filename, $timestamp);
298
-			}
299
-		}
300
-
301
-		$sourceStorage = $sourceInfo->getStorage();
302
-		$sourceInternalPath = $sourceInfo->getInternalPath();
303
-
304
-		if ($trashStorage->file_exists($trashInternalPath)) {
305
-			$trashStorage->unlink($trashInternalPath);
306
-		}
307
-
308
-		$config = \OC::$server->getConfig();
309
-		$systemTrashbinSize = (int)$config->getAppValue('files_trashbin', 'trashbin_size', '-1');
310
-		$userTrashbinSize = (int)$config->getUserValue($owner, 'files_trashbin', 'trashbin_size', '-1');
311
-		$configuredTrashbinSize = ($userTrashbinSize < 0) ? $systemTrashbinSize : $userTrashbinSize;
312
-		if ($configuredTrashbinSize >= 0 && $sourceInfo->getSize() >= $configuredTrashbinSize) {
313
-			return false;
314
-		}
315
-
316
-		$trashStorage->getUpdater()->renameFromStorage($sourceStorage, $sourceInternalPath, $trashInternalPath);
317
-
318
-		try {
319
-			$moveSuccessful = true;
320
-
321
-			// when moving within the same object store, the cache update done above is enough to move the file
322
-			if (!($trashStorage->instanceOfStorage(ObjectStoreStorage::class) && $trashStorage->getId() === $sourceStorage->getId())) {
323
-				$trashStorage->moveFromStorage($sourceStorage, $sourceInternalPath, $trashInternalPath);
324
-			}
325
-		} catch (\OCA\Files_Trashbin\Exceptions\CopyRecursiveException $e) {
326
-			$moveSuccessful = false;
327
-			if ($trashStorage->file_exists($trashInternalPath)) {
328
-				$trashStorage->unlink($trashInternalPath);
329
-			}
330
-			\OC::$server->get(LoggerInterface::class)->error('Couldn\'t move ' . $file_path . ' to the trash bin', ['app' => 'files_trashbin']);
331
-		}
332
-
333
-		if ($sourceStorage->file_exists($sourceInternalPath)) { // failed to delete the original file, abort
334
-			if ($sourceStorage->is_dir($sourceInternalPath)) {
335
-				$sourceStorage->rmdir($sourceInternalPath);
336
-			} else {
337
-				$sourceStorage->unlink($sourceInternalPath);
338
-			}
339
-
340
-			if ($sourceStorage->file_exists($sourceInternalPath)) {
341
-				// undo the cache move
342
-				$sourceStorage->getUpdater()->renameFromStorage($trashStorage, $trashInternalPath, $sourceInternalPath);
343
-			} else {
344
-				$trashStorage->getUpdater()->remove($trashInternalPath);
345
-			}
346
-			return false;
347
-		}
348
-
349
-		if ($moveSuccessful) {
350
-			$query = \OC::$server->getDatabaseConnection()->getQueryBuilder();
351
-			$query->insert('files_trash')
352
-				->setValue('id', $query->createNamedParameter($filename))
353
-				->setValue('timestamp', $query->createNamedParameter($timestamp))
354
-				->setValue('location', $query->createNamedParameter($location))
355
-				->setValue('user', $query->createNamedParameter($owner));
356
-			$result = $query->executeStatement();
357
-			if (!$result) {
358
-				\OC::$server->get(LoggerInterface::class)->error('trash bin database couldn\'t be updated', ['app' => 'files_trashbin']);
359
-			}
360
-			\OCP\Util::emitHook('\OCA\Files_Trashbin\Trashbin', 'post_moveToTrash', ['filePath' => Filesystem::normalizePath($file_path),
361
-				'trashPath' => Filesystem::normalizePath(static::getTrashFilename($filename, $timestamp))]);
362
-
363
-			self::retainVersions($filename, $owner, $ownerPath, $timestamp);
364
-
365
-			// if owner !== user we need to also add a copy to the users trash
366
-			if ($user !== $owner && $ownerOnly === false) {
367
-				self::copyFilesToUser($ownerPath, $owner, $file_path, $user, $timestamp);
368
-			}
369
-		}
370
-
371
-		$trashStorage->releaseLock($trashInternalPath, ILockingProvider::LOCK_EXCLUSIVE, $lockingProvider);
372
-
373
-		self::scheduleExpire($user);
374
-
375
-		// if owner !== user we also need to update the owners trash size
376
-		if ($owner !== $user) {
377
-			self::scheduleExpire($owner);
378
-		}
379
-
380
-		return $moveSuccessful;
381
-	}
382
-
383
-	/**
384
-	 * Move file versions to trash so that they can be restored later
385
-	 *
386
-	 * @param string $filename of deleted file
387
-	 * @param string $owner owner user id
388
-	 * @param string $ownerPath path relative to the owner's home storage
389
-	 * @param integer $timestamp when the file was deleted
390
-	 */
391
-	private static function retainVersions($filename, $owner, $ownerPath, $timestamp) {
392
-		if (\OCP\Server::get(IAppManager::class)->isEnabledForUser('files_versions') && !empty($ownerPath)) {
393
-			$user = OC_User::getUser();
394
-			$rootView = new View('/');
395
-
396
-			if ($rootView->is_dir($owner . '/files_versions/' . $ownerPath)) {
397
-				if ($owner !== $user) {
398
-					self::copy_recursive($owner . '/files_versions/' . $ownerPath, $owner . '/files_trashbin/versions/' . static::getTrashFilename(basename($ownerPath), $timestamp), $rootView);
399
-				}
400
-				self::move($rootView, $owner . '/files_versions/' . $ownerPath, $user . '/files_trashbin/versions/' . static::getTrashFilename($filename, $timestamp));
401
-			} elseif ($versions = \OCA\Files_Versions\Storage::getVersions($owner, $ownerPath)) {
402
-				foreach ($versions as $v) {
403
-					if ($owner !== $user) {
404
-						self::copy($rootView, $owner . '/files_versions' . $v['path'] . '.v' . $v['version'], $owner . '/files_trashbin/versions/' . static::getTrashFilename($v['name'] . '.v' . $v['version'], $timestamp));
405
-					}
406
-					self::move($rootView, $owner . '/files_versions' . $v['path'] . '.v' . $v['version'], $user . '/files_trashbin/versions/' . static::getTrashFilename($filename . '.v' . $v['version'], $timestamp));
407
-				}
408
-			}
409
-		}
410
-	}
411
-
412
-	/**
413
-	 * Move a file or folder on storage level
414
-	 *
415
-	 * @param View $view
416
-	 * @param string $source
417
-	 * @param string $target
418
-	 * @return bool
419
-	 */
420
-	private static function move(View $view, $source, $target) {
421
-		/** @var \OC\Files\Storage\Storage $sourceStorage */
422
-		[$sourceStorage, $sourceInternalPath] = $view->resolvePath($source);
423
-		/** @var \OC\Files\Storage\Storage $targetStorage */
424
-		[$targetStorage, $targetInternalPath] = $view->resolvePath($target);
425
-		/** @var \OC\Files\Storage\Storage $ownerTrashStorage */
426
-
427
-		$result = $targetStorage->moveFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
428
-		if ($result) {
429
-			$targetStorage->getUpdater()->renameFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
430
-		}
431
-		return $result;
432
-	}
433
-
434
-	/**
435
-	 * Copy a file or folder on storage level
436
-	 *
437
-	 * @param View $view
438
-	 * @param string $source
439
-	 * @param string $target
440
-	 * @return bool
441
-	 */
442
-	private static function copy(View $view, $source, $target) {
443
-		/** @var \OC\Files\Storage\Storage $sourceStorage */
444
-		[$sourceStorage, $sourceInternalPath] = $view->resolvePath($source);
445
-		/** @var \OC\Files\Storage\Storage $targetStorage */
446
-		[$targetStorage, $targetInternalPath] = $view->resolvePath($target);
447
-		/** @var \OC\Files\Storage\Storage $ownerTrashStorage */
448
-
449
-		$result = $targetStorage->copyFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
450
-		if ($result) {
451
-			$targetStorage->getUpdater()->update($targetInternalPath);
452
-		}
453
-		return $result;
454
-	}
455
-
456
-	/**
457
-	 * Restore a file or folder from trash bin
458
-	 *
459
-	 * @param string $file path to the deleted file/folder relative to "files_trashbin/files/",
460
-	 * including the timestamp suffix ".d12345678"
461
-	 * @param string $filename name of the file/folder
462
-	 * @param int $timestamp time when the file/folder was deleted
463
-	 *
464
-	 * @return bool true on success, false otherwise
465
-	 */
466
-	public static function restore($file, $filename, $timestamp) {
467
-		$user = OC_User::getUser();
468
-		$view = new View('/' . $user);
469
-
470
-		$location = '';
471
-		if ($timestamp) {
472
-			$location = self::getLocation($user, $filename, $timestamp);
473
-			if ($location === false) {
474
-				\OC::$server->get(LoggerInterface::class)->error('trash bin database inconsistent! ($user: ' . $user . ' $filename: ' . $filename . ', $timestamp: ' . $timestamp . ')', ['app' => 'files_trashbin']);
475
-			} else {
476
-				// if location no longer exists, restore file in the root directory
477
-				if ($location !== '/' &&
478
-					(!$view->is_dir('files/' . $location) ||
479
-						!$view->isCreatable('files/' . $location))
480
-				) {
481
-					$location = '';
482
-				}
483
-			}
484
-		}
485
-
486
-		// we need a  extension in case a file/dir with the same name already exists
487
-		$uniqueFilename = self::getUniqueFilename($location, $filename, $view);
488
-
489
-		$source = Filesystem::normalizePath('files_trashbin/files/' . $file);
490
-		$target = Filesystem::normalizePath('files/' . $location . '/' . $uniqueFilename);
491
-		if (!$view->file_exists($source)) {
492
-			return false;
493
-		}
494
-		$mtime = $view->filemtime($source);
495
-
496
-		// restore file
497
-		if (!$view->isCreatable(dirname($target))) {
498
-			throw new NotPermittedException("Can't restore trash item because the target folder is not writable");
499
-		}
500
-		$restoreResult = $view->rename($source, $target);
501
-
502
-		// handle the restore result
503
-		if ($restoreResult) {
504
-			$fakeRoot = $view->getRoot();
505
-			$view->chroot('/' . $user . '/files');
506
-			$view->touch('/' . $location . '/' . $uniqueFilename, $mtime);
507
-			$view->chroot($fakeRoot);
508
-			\OCP\Util::emitHook('\OCA\Files_Trashbin\Trashbin', 'post_restore', ['filePath' => Filesystem::normalizePath('/' . $location . '/' . $uniqueFilename),
509
-				'trashPath' => Filesystem::normalizePath($file)]);
510
-
511
-			self::restoreVersions($view, $file, $filename, $uniqueFilename, $location, $timestamp);
512
-
513
-			if ($timestamp) {
514
-				$query = \OC::$server->getDatabaseConnection()->getQueryBuilder();
515
-				$query->delete('files_trash')
516
-					->where($query->expr()->eq('user', $query->createNamedParameter($user)))
517
-					->andWhere($query->expr()->eq('id', $query->createNamedParameter($filename)))
518
-					->andWhere($query->expr()->eq('timestamp', $query->createNamedParameter($timestamp)));
519
-				$query->executeStatement();
520
-			}
521
-
522
-			return true;
523
-		}
524
-
525
-		return false;
526
-	}
527
-
528
-	/**
529
-	 * restore versions from trash bin
530
-	 *
531
-	 * @param View $view file view
532
-	 * @param string $file complete path to file
533
-	 * @param string $filename name of file once it was deleted
534
-	 * @param string $uniqueFilename new file name to restore the file without overwriting existing files
535
-	 * @param string $location location if file
536
-	 * @param int $timestamp deletion time
537
-	 * @return false|null
538
-	 */
539
-	private static function restoreVersions(View $view, $file, $filename, $uniqueFilename, $location, $timestamp) {
540
-		if (\OCP\Server::get(IAppManager::class)->isEnabledForUser('files_versions')) {
541
-			$user = OC_User::getUser();
542
-			$rootView = new View('/');
543
-
544
-			$target = Filesystem::normalizePath('/' . $location . '/' . $uniqueFilename);
545
-
546
-			[$owner, $ownerPath] = self::getUidAndFilename($target);
547
-
548
-			// file has been deleted in between
549
-			if (empty($ownerPath)) {
550
-				return false;
551
-			}
552
-
553
-			if ($timestamp) {
554
-				$versionedFile = $filename;
555
-			} else {
556
-				$versionedFile = $file;
557
-			}
558
-
559
-			if ($view->is_dir('/files_trashbin/versions/' . $file)) {
560
-				$rootView->rename(Filesystem::normalizePath($user . '/files_trashbin/versions/' . $file), Filesystem::normalizePath($owner . '/files_versions/' . $ownerPath));
561
-			} elseif ($versions = self::getVersionsFromTrash($versionedFile, $timestamp, $user)) {
562
-				foreach ($versions as $v) {
563
-					if ($timestamp) {
564
-						$rootView->rename($user . '/files_trashbin/versions/' . static::getTrashFilename($versionedFile . '.v' . $v, $timestamp), $owner . '/files_versions/' . $ownerPath . '.v' . $v);
565
-					} else {
566
-						$rootView->rename($user . '/files_trashbin/versions/' . $versionedFile . '.v' . $v, $owner . '/files_versions/' . $ownerPath . '.v' . $v);
567
-					}
568
-				}
569
-			}
570
-		}
571
-	}
572
-
573
-	/**
574
-	 * delete all files from the trash
575
-	 */
576
-	public static function deleteAll() {
577
-		$user = OC_User::getUser();
578
-		$userRoot = \OC::$server->getUserFolder($user)->getParent();
579
-		$view = new View('/' . $user);
580
-		$fileInfos = $view->getDirectoryContent('files_trashbin/files');
581
-
582
-		try {
583
-			$trash = $userRoot->get('files_trashbin');
584
-		} catch (NotFoundException $e) {
585
-			return false;
586
-		}
587
-
588
-		// Array to store the relative path in (after the file is deleted, the view won't be able to relativise the path anymore)
589
-		$filePaths = [];
590
-		foreach ($fileInfos as $fileInfo) {
591
-			$filePaths[] = $view->getRelativePath($fileInfo->getPath());
592
-		}
593
-		unset($fileInfos); // save memory
594
-
595
-		// Bulk PreDelete-Hook
596
-		\OC_Hook::emit('\OCP\Trashbin', 'preDeleteAll', ['paths' => $filePaths]);
597
-
598
-		// Single-File Hooks
599
-		foreach ($filePaths as $path) {
600
-			self::emitTrashbinPreDelete($path);
601
-		}
602
-
603
-		// actual file deletion
604
-		$trash->delete();
605
-
606
-		$query = \OC::$server->getDatabaseConnection()->getQueryBuilder();
607
-		$query->delete('files_trash')
608
-			->where($query->expr()->eq('user', $query->createNamedParameter($user)));
609
-		$query->executeStatement();
610
-
611
-		// Bulk PostDelete-Hook
612
-		\OC_Hook::emit('\OCP\Trashbin', 'deleteAll', ['paths' => $filePaths]);
613
-
614
-		// Single-File Hooks
615
-		foreach ($filePaths as $path) {
616
-			self::emitTrashbinPostDelete($path);
617
-		}
618
-
619
-		$trash = $userRoot->newFolder('files_trashbin');
620
-		$trash->newFolder('files');
621
-
622
-		return true;
623
-	}
624
-
625
-	/**
626
-	 * wrapper function to emit the 'preDelete' hook of \OCP\Trashbin before a file is deleted
627
-	 *
628
-	 * @param string $path
629
-	 */
630
-	protected static function emitTrashbinPreDelete($path) {
631
-		\OC_Hook::emit('\OCP\Trashbin', 'preDelete', ['path' => $path]);
632
-	}
633
-
634
-	/**
635
-	 * wrapper function to emit the 'delete' hook of \OCP\Trashbin after a file has been deleted
636
-	 *
637
-	 * @param string $path
638
-	 */
639
-	protected static function emitTrashbinPostDelete($path) {
640
-		\OC_Hook::emit('\OCP\Trashbin', 'delete', ['path' => $path]);
641
-	}
642
-
643
-	/**
644
-	 * delete file from trash bin permanently
645
-	 *
646
-	 * @param string $filename path to the file
647
-	 * @param string $user
648
-	 * @param int $timestamp of deletion time
649
-	 *
650
-	 * @return int size of deleted files
651
-	 */
652
-	public static function delete($filename, $user, $timestamp = null) {
653
-		$userRoot = \OC::$server->getUserFolder($user)->getParent();
654
-		$view = new View('/' . $user);
655
-		$size = 0;
656
-
657
-		if ($timestamp) {
658
-			$query = \OC::$server->getDatabaseConnection()->getQueryBuilder();
659
-			$query->delete('files_trash')
660
-				->where($query->expr()->eq('user', $query->createNamedParameter($user)))
661
-				->andWhere($query->expr()->eq('id', $query->createNamedParameter($filename)))
662
-				->andWhere($query->expr()->eq('timestamp', $query->createNamedParameter($timestamp)));
663
-			$query->executeStatement();
664
-
665
-			$file = static::getTrashFilename($filename, $timestamp);
666
-		} else {
667
-			$file = $filename;
668
-		}
669
-
670
-		$size += self::deleteVersions($view, $file, $filename, $timestamp, $user);
671
-
672
-		try {
673
-			$node = $userRoot->get('/files_trashbin/files/' . $file);
674
-		} catch (NotFoundException $e) {
675
-			return $size;
676
-		}
677
-
678
-		if ($node instanceof Folder) {
679
-			$size += self::calculateSize(new View('/' . $user . '/files_trashbin/files/' . $file));
680
-		} elseif ($node instanceof File) {
681
-			$size += $view->filesize('/files_trashbin/files/' . $file);
682
-		}
683
-
684
-		self::emitTrashbinPreDelete('/files_trashbin/files/' . $file);
685
-		$node->delete();
686
-		self::emitTrashbinPostDelete('/files_trashbin/files/' . $file);
687
-
688
-		return $size;
689
-	}
690
-
691
-	/**
692
-	 * @param View $view
693
-	 * @param string $file
694
-	 * @param string $filename
695
-	 * @param integer|null $timestamp
696
-	 * @param string $user
697
-	 * @return int
698
-	 */
699
-	private static function deleteVersions(View $view, $file, $filename, $timestamp, $user) {
700
-		$size = 0;
701
-		if (\OCP\Server::get(IAppManager::class)->isEnabledForUser('files_versions')) {
702
-			if ($view->is_dir('files_trashbin/versions/' . $file)) {
703
-				$size += self::calculateSize(new View('/' . $user . '/files_trashbin/versions/' . $file));
704
-				$view->unlink('files_trashbin/versions/' . $file);
705
-			} elseif ($versions = self::getVersionsFromTrash($filename, $timestamp, $user)) {
706
-				foreach ($versions as $v) {
707
-					if ($timestamp) {
708
-						$size += $view->filesize('/files_trashbin/versions/' . static::getTrashFilename($filename . '.v' . $v, $timestamp));
709
-						$view->unlink('/files_trashbin/versions/' . static::getTrashFilename($filename . '.v' . $v, $timestamp));
710
-					} else {
711
-						$size += $view->filesize('/files_trashbin/versions/' . $filename . '.v' . $v);
712
-						$view->unlink('/files_trashbin/versions/' . $filename . '.v' . $v);
713
-					}
714
-				}
715
-			}
716
-		}
717
-		return $size;
718
-	}
719
-
720
-	/**
721
-	 * check to see whether a file exists in trashbin
722
-	 *
723
-	 * @param string $filename path to the file
724
-	 * @param int $timestamp of deletion time
725
-	 * @return bool true if file exists, otherwise false
726
-	 */
727
-	public static function file_exists($filename, $timestamp = null) {
728
-		$user = OC_User::getUser();
729
-		$view = new View('/' . $user);
730
-
731
-		if ($timestamp) {
732
-			$filename = static::getTrashFilename($filename, $timestamp);
733
-		}
734
-
735
-		$target = Filesystem::normalizePath('files_trashbin/files/' . $filename);
736
-		return $view->file_exists($target);
737
-	}
738
-
739
-	/**
740
-	 * deletes used space for trash bin in db if user was deleted
741
-	 *
742
-	 * @param string $uid id of deleted user
743
-	 * @return bool result of db delete operation
744
-	 */
745
-	public static function deleteUser($uid) {
746
-		$query = \OC::$server->getDatabaseConnection()->getQueryBuilder();
747
-		$query->delete('files_trash')
748
-			->where($query->expr()->eq('user', $query->createNamedParameter($uid)));
749
-		return (bool) $query->executeStatement();
750
-	}
751
-
752
-	/**
753
-	 * calculate remaining free space for trash bin
754
-	 *
755
-	 * @param integer $trashbinSize current size of the trash bin
756
-	 * @param string $user
757
-	 * @return int available free space for trash bin
758
-	 */
759
-	private static function calculateFreeSpace($trashbinSize, $user) {
760
-		$config = \OC::$server->getConfig();
761
-		$userTrashbinSize = (int)$config->getUserValue($user, 'files_trashbin', 'trashbin_size', '-1');
762
-		if ($userTrashbinSize > -1) {
763
-			return $userTrashbinSize - $trashbinSize;
764
-		}
765
-		$systemTrashbinSize = (int)$config->getAppValue('files_trashbin', 'trashbin_size', '-1');
766
-		if ($systemTrashbinSize > -1) {
767
-			return $systemTrashbinSize - $trashbinSize;
768
-		}
769
-
770
-		$softQuota = true;
771
-		$userObject = \OC::$server->getUserManager()->get($user);
772
-		if (is_null($userObject)) {
773
-			return 0;
774
-		}
775
-		$quota = $userObject->getQuota();
776
-		if ($quota === null || $quota === 'none') {
777
-			$quota = Filesystem::free_space('/');
778
-			$softQuota = false;
779
-			// inf or unknown free space
780
-			if ($quota < 0) {
781
-				$quota = PHP_INT_MAX;
782
-			}
783
-		} else {
784
-			$quota = \OCP\Util::computerFileSize($quota);
785
-		}
786
-
787
-		// calculate available space for trash bin
788
-		// subtract size of files and current trash bin size from quota
789
-		if ($softQuota) {
790
-			$userFolder = \OC::$server->getUserFolder($user);
791
-			if (is_null($userFolder)) {
792
-				return 0;
793
-			}
794
-			$free = $quota - $userFolder->getSize(false); // remaining free space for user
795
-			if ($free > 0) {
796
-				$availableSpace = ($free * self::DEFAULTMAXSIZE / 100) - $trashbinSize; // how much space can be used for versions
797
-			} else {
798
-				$availableSpace = $free - $trashbinSize;
799
-			}
800
-		} else {
801
-			$availableSpace = $quota;
802
-		}
803
-
804
-		return (int)$availableSpace;
805
-	}
806
-
807
-	/**
808
-	 * resize trash bin if necessary after a new file was added to Nextcloud
809
-	 *
810
-	 * @param string $user user id
811
-	 */
812
-	public static function resizeTrash($user) {
813
-		$size = self::getTrashbinSize($user);
814
-
815
-		$freeSpace = self::calculateFreeSpace($size, $user);
816
-
817
-		if ($freeSpace < 0) {
818
-			self::scheduleExpire($user);
819
-		}
820
-	}
821
-
822
-	/**
823
-	 * clean up the trash bin
824
-	 *
825
-	 * @param string $user
826
-	 */
827
-	public static function expire($user) {
828
-		$trashBinSize = self::getTrashbinSize($user);
829
-		$availableSpace = self::calculateFreeSpace($trashBinSize, $user);
830
-
831
-		$dirContent = Helper::getTrashFiles('/', $user, 'mtime');
832
-
833
-		// delete all files older then $retention_obligation
834
-		[$delSize, $count] = self::deleteExpiredFiles($dirContent, $user);
835
-
836
-		$availableSpace += $delSize;
837
-
838
-		// delete files from trash until we meet the trash bin size limit again
839
-		self::deleteFiles(array_slice($dirContent, $count), $user, $availableSpace);
840
-	}
841
-
842
-	/**
843
-	 * @param string $user
844
-	 */
845
-	private static function scheduleExpire($user) {
846
-		// let the admin disable auto expire
847
-		/** @var Application $application */
848
-		$application = \OC::$server->query(Application::class);
849
-		$expiration = $application->getContainer()->query('Expiration');
850
-		if ($expiration->isEnabled()) {
851
-			\OC::$server->getCommandBus()->push(new Expire($user));
852
-		}
853
-	}
854
-
855
-	/**
856
-	 * if the size limit for the trash bin is reached, we delete the oldest
857
-	 * files in the trash bin until we meet the limit again
858
-	 *
859
-	 * @param array $files
860
-	 * @param string $user
861
-	 * @param int $availableSpace available disc space
862
-	 * @return int size of deleted files
863
-	 */
864
-	protected static function deleteFiles($files, $user, $availableSpace) {
865
-		/** @var Application $application */
866
-		$application = \OC::$server->query(Application::class);
867
-		$expiration = $application->getContainer()->query('Expiration');
868
-		$size = 0;
869
-
870
-		if ($availableSpace < 0) {
871
-			foreach ($files as $file) {
872
-				if ($availableSpace < 0 && $expiration->isExpired($file['mtime'], true)) {
873
-					$tmp = self::delete($file['name'], $user, $file['mtime']);
874
-					\OC::$server->get(LoggerInterface::class)->info('remove "' . $file['name'] . '" (' . $tmp . 'B) to meet the limit of trash bin size (50% of available quota)', ['app' => 'files_trashbin']);
875
-					$availableSpace += $tmp;
876
-					$size += $tmp;
877
-				} else {
878
-					break;
879
-				}
880
-			}
881
-		}
882
-		return $size;
883
-	}
884
-
885
-	/**
886
-	 * delete files older then max storage time
887
-	 *
888
-	 * @param array $files list of files sorted by mtime
889
-	 * @param string $user
890
-	 * @return integer[] size of deleted files and number of deleted files
891
-	 */
892
-	public static function deleteExpiredFiles($files, $user) {
893
-		/** @var Expiration $expiration */
894
-		$expiration = \OC::$server->query(Expiration::class);
895
-		$size = 0;
896
-		$count = 0;
897
-		foreach ($files as $file) {
898
-			$timestamp = $file['mtime'];
899
-			$filename = $file['name'];
900
-			if ($expiration->isExpired($timestamp)) {
901
-				try {
902
-					$size += self::delete($filename, $user, $timestamp);
903
-					$count++;
904
-				} catch (\OCP\Files\NotPermittedException $e) {
905
-					\OC::$server->get(LoggerInterface::class)->warning('Removing "' . $filename . '" from trashbin failed.',
906
-						[
907
-							'exception' => $e,
908
-							'app' => 'files_trashbin',
909
-						]
910
-					);
911
-				}
912
-				\OC::$server->get(LoggerInterface::class)->info(
913
-					'Remove "' . $filename . '" from trashbin because it exceeds max retention obligation term.',
914
-					['app' => 'files_trashbin']
915
-				);
916
-			} else {
917
-				break;
918
-			}
919
-		}
920
-
921
-		return [$size, $count];
922
-	}
923
-
924
-	/**
925
-	 * recursive copy to copy a whole directory
926
-	 *
927
-	 * @param string $source source path, relative to the users files directory
928
-	 * @param string $destination destination path relative to the users root directory
929
-	 * @param View $view file view for the users root directory
930
-	 * @return int
931
-	 * @throws Exceptions\CopyRecursiveException
932
-	 */
933
-	private static function copy_recursive($source, $destination, View $view) {
934
-		$size = 0;
935
-		if ($view->is_dir($source)) {
936
-			$view->mkdir($destination);
937
-			$view->touch($destination, $view->filemtime($source));
938
-			foreach ($view->getDirectoryContent($source) as $i) {
939
-				$pathDir = $source . '/' . $i['name'];
940
-				if ($view->is_dir($pathDir)) {
941
-					$size += self::copy_recursive($pathDir, $destination . '/' . $i['name'], $view);
942
-				} else {
943
-					$size += $view->filesize($pathDir);
944
-					$result = $view->copy($pathDir, $destination . '/' . $i['name']);
945
-					if (!$result) {
946
-						throw new \OCA\Files_Trashbin\Exceptions\CopyRecursiveException();
947
-					}
948
-					$view->touch($destination . '/' . $i['name'], $view->filemtime($pathDir));
949
-				}
950
-			}
951
-		} else {
952
-			$size += $view->filesize($source);
953
-			$result = $view->copy($source, $destination);
954
-			if (!$result) {
955
-				throw new \OCA\Files_Trashbin\Exceptions\CopyRecursiveException();
956
-			}
957
-			$view->touch($destination, $view->filemtime($source));
958
-		}
959
-		return $size;
960
-	}
961
-
962
-	/**
963
-	 * find all versions which belong to the file we want to restore
964
-	 *
965
-	 * @param string $filename name of the file which should be restored
966
-	 * @param int $timestamp timestamp when the file was deleted
967
-	 * @return array
968
-	 */
969
-	private static function getVersionsFromTrash($filename, $timestamp, $user) {
970
-		$view = new View('/' . $user . '/files_trashbin/versions');
971
-		$versions = [];
972
-
973
-		/** @var \OC\Files\Storage\Storage $storage */
974
-		[$storage,] = $view->resolvePath('/');
975
-
976
-		$pattern = \OC::$server->getDatabaseConnection()->escapeLikeParameter(basename($filename));
977
-		if ($timestamp) {
978
-			// fetch for old versions
979
-			$escapedTimestamp = \OC::$server->getDatabaseConnection()->escapeLikeParameter($timestamp);
980
-			$pattern .= '.v%.d' . $escapedTimestamp;
981
-			$offset = -strlen($escapedTimestamp) - 2;
982
-		} else {
983
-			$pattern .= '.v%';
984
-		}
985
-
986
-		// Manually fetch all versions from the file cache to be able to filter them by their parent
987
-		$cache = $storage->getCache('');
988
-		$query = new CacheQueryBuilder(
989
-			\OC::$server->getDatabaseConnection(),
990
-			\OC::$server->getSystemConfig(),
991
-			\OC::$server->get(LoggerInterface::class)
992
-		);
993
-		$normalizedParentPath = ltrim(Filesystem::normalizePath(dirname('files_trashbin/versions/'. $filename)), '/');
994
-		$parentId = $cache->getId($normalizedParentPath);
995
-		if ($parentId === -1) {
996
-			return [];
997
-		}
998
-
999
-		$query->selectFileCache()
1000
-			->whereStorageId($cache->getNumericStorageId())
1001
-			->andWhere($query->expr()->eq('parent', $query->createNamedParameter($parentId)))
1002
-			->andWhere($query->expr()->iLike('name', $query->createNamedParameter($pattern)));
1003
-
1004
-		$result = $query->executeQuery();
1005
-		$entries = $result->fetchAll();
1006
-		$result->closeCursor();
1007
-
1008
-		/** @var CacheEntry[] $matches */
1009
-		$matches = array_map(function (array $data) {
1010
-			return Cache::cacheEntryFromData($data, \OC::$server->getMimeTypeLoader());
1011
-		}, $entries);
1012
-
1013
-		foreach ($matches as $ma) {
1014
-			if ($timestamp) {
1015
-				$parts = explode('.v', substr($ma['path'], 0, $offset));
1016
-				$versions[] = end($parts);
1017
-			} else {
1018
-				$parts = explode('.v', $ma['path']);
1019
-				$versions[] = end($parts);
1020
-			}
1021
-		}
1022
-
1023
-		return $versions;
1024
-	}
1025
-
1026
-	/**
1027
-	 * find unique extension for restored file if a file with the same name already exists
1028
-	 *
1029
-	 * @param string $location where the file should be restored
1030
-	 * @param string $filename name of the file
1031
-	 * @param View $view filesystem view relative to users root directory
1032
-	 * @return string with unique extension
1033
-	 */
1034
-	private static function getUniqueFilename($location, $filename, View $view) {
1035
-		$ext = pathinfo($filename, PATHINFO_EXTENSION);
1036
-		$name = pathinfo($filename, PATHINFO_FILENAME);
1037
-		$l = \OC::$server->getL10N('files_trashbin');
1038
-
1039
-		$location = '/' . trim($location, '/');
1040
-
1041
-		// if extension is not empty we set a dot in front of it
1042
-		if ($ext !== '') {
1043
-			$ext = '.' . $ext;
1044
-		}
1045
-
1046
-		if ($view->file_exists('files' . $location . '/' . $filename)) {
1047
-			$i = 2;
1048
-			$uniqueName = $name . " (" . $l->t("restored") . ")" . $ext;
1049
-			while ($view->file_exists('files' . $location . '/' . $uniqueName)) {
1050
-				$uniqueName = $name . " (" . $l->t("restored") . " " . $i . ")" . $ext;
1051
-				$i++;
1052
-			}
1053
-
1054
-			return $uniqueName;
1055
-		}
1056
-
1057
-		return $filename;
1058
-	}
1059
-
1060
-	/**
1061
-	 * get the size from a given root folder
1062
-	 *
1063
-	 * @param View $view file view on the root folder
1064
-	 * @return integer size of the folder
1065
-	 */
1066
-	private static function calculateSize($view) {
1067
-		$root = \OC::$server->getConfig()->getSystemValue('datadirectory', \OC::$SERVERROOT . '/data') . $view->getAbsolutePath('');
1068
-		if (!file_exists($root)) {
1069
-			return 0;
1070
-		}
1071
-		$iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($root), \RecursiveIteratorIterator::CHILD_FIRST);
1072
-		$size = 0;
1073
-
1074
-		/**
1075
-		 * RecursiveDirectoryIterator on an NFS path isn't iterable with foreach
1076
-		 * This bug is fixed in PHP 5.5.9 or before
1077
-		 * See #8376
1078
-		 */
1079
-		$iterator->rewind();
1080
-		while ($iterator->valid()) {
1081
-			$path = $iterator->current();
1082
-			$relpath = substr($path, strlen($root) - 1);
1083
-			if (!$view->is_dir($relpath)) {
1084
-				$size += $view->filesize($relpath);
1085
-			}
1086
-			$iterator->next();
1087
-		}
1088
-		return $size;
1089
-	}
1090
-
1091
-	/**
1092
-	 * get current size of trash bin from a given user
1093
-	 *
1094
-	 * @param string $user user who owns the trash bin
1095
-	 * @return integer trash bin size
1096
-	 */
1097
-	private static function getTrashbinSize($user) {
1098
-		$view = new View('/' . $user);
1099
-		$fileInfo = $view->getFileInfo('/files_trashbin');
1100
-		return isset($fileInfo['size']) ? $fileInfo['size'] : 0;
1101
-	}
1102
-
1103
-	/**
1104
-	 * check if trash bin is empty for a given user
1105
-	 *
1106
-	 * @param string $user
1107
-	 * @return bool
1108
-	 */
1109
-	public static function isEmpty($user) {
1110
-		$view = new View('/' . $user . '/files_trashbin');
1111
-		if ($view->is_dir('/files') && $dh = $view->opendir('/files')) {
1112
-			while ($file = readdir($dh)) {
1113
-				if (!Filesystem::isIgnoredDir($file)) {
1114
-					return false;
1115
-				}
1116
-			}
1117
-		}
1118
-		return true;
1119
-	}
1120
-
1121
-	/**
1122
-	 * @param $path
1123
-	 * @return string
1124
-	 */
1125
-	public static function preview_icon($path) {
1126
-		return \OC::$server->getURLGenerator()->linkToRoute('core_ajax_trashbin_preview', ['x' => 32, 'y' => 32, 'file' => $path]);
1127
-	}
1128
-
1129
-	/**
1130
-	 * Return the filename used in the trash bin
1131
-	 */
1132
-	public static function getTrashFilename(string $filename, int $timestamp): string {
1133
-		$trashFilename = $filename . '.d' . $timestamp;
1134
-		$length = strlen($trashFilename);
1135
-		// oc_filecache `name` column has a limit of 250 chars
1136
-		$maxLength = 250;
1137
-		if ($length > $maxLength) {
1138
-			$trashFilename = substr_replace(
1139
-				$trashFilename,
1140
-				'',
1141
-				$maxLength / 2,
1142
-				$length - $maxLength
1143
-			);
1144
-		}
1145
-		return $trashFilename;
1146
-	}
68
+    // unit: percentage; 50% of available disk space/quota
69
+    public const DEFAULTMAXSIZE = 50;
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 = OC_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 !== OC_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::$server->getDatabaseConnection()->getQueryBuilder();
127
+        $query->select('id', 'timestamp', 'location')
128
+            ->from('files_trash')
129
+            ->where($query->expr()->eq('user', $query->createNamedParameter($user)));
130
+        $result = $query->executeQuery();
131
+        $array = [];
132
+        while ($row = $result->fetch()) {
133
+            if (isset($array[$row['id']])) {
134
+                $array[$row['id']][$row['timestamp']] = $row['location'];
135
+            } else {
136
+                $array[$row['id']] = [$row['timestamp'] => $row['location']];
137
+            }
138
+        }
139
+        $result->closeCursor();
140
+        return $array;
141
+    }
142
+
143
+    /**
144
+     * get original location of file
145
+     *
146
+     * @param string $user
147
+     * @param string $filename
148
+     * @param string $timestamp
149
+     * @return string original location
150
+     */
151
+    public static function getLocation($user, $filename, $timestamp) {
152
+        $query = \OC::$server->getDatabaseConnection()->getQueryBuilder();
153
+        $query->select('location')
154
+            ->from('files_trash')
155
+            ->where($query->expr()->eq('user', $query->createNamedParameter($user)))
156
+            ->andWhere($query->expr()->eq('id', $query->createNamedParameter($filename)))
157
+            ->andWhere($query->expr()->eq('timestamp', $query->createNamedParameter($timestamp)));
158
+
159
+        $result = $query->executeQuery();
160
+        $row = $result->fetch();
161
+        $result->closeCursor();
162
+
163
+        if (isset($row['location'])) {
164
+            return $row['location'];
165
+        } else {
166
+            return false;
167
+        }
168
+    }
169
+
170
+    private static function setUpTrash($user) {
171
+        $view = new View('/' . $user);
172
+        if (!$view->is_dir('files_trashbin')) {
173
+            $view->mkdir('files_trashbin');
174
+        }
175
+        if (!$view->is_dir('files_trashbin/files')) {
176
+            $view->mkdir('files_trashbin/files');
177
+        }
178
+        if (!$view->is_dir('files_trashbin/versions')) {
179
+            $view->mkdir('files_trashbin/versions');
180
+        }
181
+        if (!$view->is_dir('files_trashbin/keys')) {
182
+            $view->mkdir('files_trashbin/keys');
183
+        }
184
+    }
185
+
186
+
187
+    /**
188
+     * copy file to owners trash
189
+     *
190
+     * @param string $sourcePath
191
+     * @param string $owner
192
+     * @param string $targetPath
193
+     * @param $user
194
+     * @param integer $timestamp
195
+     */
196
+    private static function copyFilesToUser($sourcePath, $owner, $targetPath, $user, $timestamp) {
197
+        self::setUpTrash($owner);
198
+
199
+        $targetFilename = basename($targetPath);
200
+        $targetLocation = dirname($targetPath);
201
+
202
+        $sourceFilename = basename($sourcePath);
203
+
204
+        $view = new View('/');
205
+
206
+        $target = $user . '/files_trashbin/files/' . static::getTrashFilename($targetFilename, $timestamp);
207
+        $source = $owner . '/files_trashbin/files/' . static::getTrashFilename($sourceFilename, $timestamp);
208
+        $free = $view->free_space($target);
209
+        $isUnknownOrUnlimitedFreeSpace = $free < 0;
210
+        $isEnoughFreeSpaceLeft = $view->filesize($source) < $free;
211
+        if ($isUnknownOrUnlimitedFreeSpace || $isEnoughFreeSpaceLeft) {
212
+            self::copy_recursive($source, $target, $view);
213
+        }
214
+
215
+
216
+        if ($view->file_exists($target)) {
217
+            $query = \OC::$server->getDatabaseConnection()->getQueryBuilder();
218
+            $query->insert('files_trash')
219
+                ->setValue('id', $query->createNamedParameter($targetFilename))
220
+                ->setValue('timestamp', $query->createNamedParameter($timestamp))
221
+                ->setValue('location', $query->createNamedParameter($targetLocation))
222
+                ->setValue('user', $query->createNamedParameter($user));
223
+            $result = $query->executeStatement();
224
+            if (!$result) {
225
+                \OC::$server->get(LoggerInterface::class)->error('trash bin database couldn\'t be updated for the files owner', ['app' => 'files_trashbin']);
226
+            }
227
+        }
228
+    }
229
+
230
+
231
+    /**
232
+     * move file to the trash bin
233
+     *
234
+     * @param string $file_path path to the deleted file/directory relative to the files root directory
235
+     * @param bool $ownerOnly delete for owner only (if file gets moved out of a shared folder)
236
+     *
237
+     * @return bool
238
+     */
239
+    public static function move2trash($file_path, $ownerOnly = false) {
240
+        // get the user for which the filesystem is setup
241
+        $root = Filesystem::getRoot();
242
+        [, $user] = explode('/', $root);
243
+        [$owner, $ownerPath] = self::getUidAndFilename($file_path);
244
+
245
+        // if no owner found (ex: ext storage + share link), will use the current user's trashbin then
246
+        if (is_null($owner)) {
247
+            $owner = $user;
248
+            $ownerPath = $file_path;
249
+        }
250
+
251
+        $ownerView = new View('/' . $owner);
252
+
253
+        // file has been deleted in between
254
+        if (is_null($ownerPath) || $ownerPath === '') {
255
+            return true;
256
+        }
257
+
258
+        $sourceInfo = $ownerView->getFileInfo('/files/' . $ownerPath);
259
+
260
+        if ($sourceInfo === false) {
261
+            return true;
262
+        }
263
+
264
+        self::setUpTrash($user);
265
+        if ($owner !== $user) {
266
+            // also setup for owner
267
+            self::setUpTrash($owner);
268
+        }
269
+
270
+        $path_parts = pathinfo($ownerPath);
271
+
272
+        $filename = $path_parts['basename'];
273
+        $location = $path_parts['dirname'];
274
+        /** @var ITimeFactory $timeFactory */
275
+        $timeFactory = \OC::$server->query(ITimeFactory::class);
276
+        $timestamp = $timeFactory->getTime();
277
+
278
+        $lockingProvider = \OC::$server->getLockingProvider();
279
+
280
+        // disable proxy to prevent recursive calls
281
+        $trashPath = '/files_trashbin/files/' . static::getTrashFilename($filename, $timestamp);
282
+        $gotLock = false;
283
+
284
+        while (!$gotLock) {
285
+            try {
286
+                /** @var \OC\Files\Storage\Storage $trashStorage */
287
+                [$trashStorage, $trashInternalPath] = $ownerView->resolvePath($trashPath);
288
+
289
+                $trashStorage->acquireLock($trashInternalPath, ILockingProvider::LOCK_EXCLUSIVE, $lockingProvider);
290
+                $gotLock = true;
291
+            } catch (LockedException $e) {
292
+                // a file with the same name is being deleted concurrently
293
+                // nudge the timestamp a bit to resolve the conflict
294
+
295
+                $timestamp = $timestamp + 1;
296
+
297
+                $trashPath = '/files_trashbin/files/' . static::getTrashFilename($filename, $timestamp);
298
+            }
299
+        }
300
+
301
+        $sourceStorage = $sourceInfo->getStorage();
302
+        $sourceInternalPath = $sourceInfo->getInternalPath();
303
+
304
+        if ($trashStorage->file_exists($trashInternalPath)) {
305
+            $trashStorage->unlink($trashInternalPath);
306
+        }
307
+
308
+        $config = \OC::$server->getConfig();
309
+        $systemTrashbinSize = (int)$config->getAppValue('files_trashbin', 'trashbin_size', '-1');
310
+        $userTrashbinSize = (int)$config->getUserValue($owner, 'files_trashbin', 'trashbin_size', '-1');
311
+        $configuredTrashbinSize = ($userTrashbinSize < 0) ? $systemTrashbinSize : $userTrashbinSize;
312
+        if ($configuredTrashbinSize >= 0 && $sourceInfo->getSize() >= $configuredTrashbinSize) {
313
+            return false;
314
+        }
315
+
316
+        $trashStorage->getUpdater()->renameFromStorage($sourceStorage, $sourceInternalPath, $trashInternalPath);
317
+
318
+        try {
319
+            $moveSuccessful = true;
320
+
321
+            // when moving within the same object store, the cache update done above is enough to move the file
322
+            if (!($trashStorage->instanceOfStorage(ObjectStoreStorage::class) && $trashStorage->getId() === $sourceStorage->getId())) {
323
+                $trashStorage->moveFromStorage($sourceStorage, $sourceInternalPath, $trashInternalPath);
324
+            }
325
+        } catch (\OCA\Files_Trashbin\Exceptions\CopyRecursiveException $e) {
326
+            $moveSuccessful = false;
327
+            if ($trashStorage->file_exists($trashInternalPath)) {
328
+                $trashStorage->unlink($trashInternalPath);
329
+            }
330
+            \OC::$server->get(LoggerInterface::class)->error('Couldn\'t move ' . $file_path . ' to the trash bin', ['app' => 'files_trashbin']);
331
+        }
332
+
333
+        if ($sourceStorage->file_exists($sourceInternalPath)) { // failed to delete the original file, abort
334
+            if ($sourceStorage->is_dir($sourceInternalPath)) {
335
+                $sourceStorage->rmdir($sourceInternalPath);
336
+            } else {
337
+                $sourceStorage->unlink($sourceInternalPath);
338
+            }
339
+
340
+            if ($sourceStorage->file_exists($sourceInternalPath)) {
341
+                // undo the cache move
342
+                $sourceStorage->getUpdater()->renameFromStorage($trashStorage, $trashInternalPath, $sourceInternalPath);
343
+            } else {
344
+                $trashStorage->getUpdater()->remove($trashInternalPath);
345
+            }
346
+            return false;
347
+        }
348
+
349
+        if ($moveSuccessful) {
350
+            $query = \OC::$server->getDatabaseConnection()->getQueryBuilder();
351
+            $query->insert('files_trash')
352
+                ->setValue('id', $query->createNamedParameter($filename))
353
+                ->setValue('timestamp', $query->createNamedParameter($timestamp))
354
+                ->setValue('location', $query->createNamedParameter($location))
355
+                ->setValue('user', $query->createNamedParameter($owner));
356
+            $result = $query->executeStatement();
357
+            if (!$result) {
358
+                \OC::$server->get(LoggerInterface::class)->error('trash bin database couldn\'t be updated', ['app' => 'files_trashbin']);
359
+            }
360
+            \OCP\Util::emitHook('\OCA\Files_Trashbin\Trashbin', 'post_moveToTrash', ['filePath' => Filesystem::normalizePath($file_path),
361
+                'trashPath' => Filesystem::normalizePath(static::getTrashFilename($filename, $timestamp))]);
362
+
363
+            self::retainVersions($filename, $owner, $ownerPath, $timestamp);
364
+
365
+            // if owner !== user we need to also add a copy to the users trash
366
+            if ($user !== $owner && $ownerOnly === false) {
367
+                self::copyFilesToUser($ownerPath, $owner, $file_path, $user, $timestamp);
368
+            }
369
+        }
370
+
371
+        $trashStorage->releaseLock($trashInternalPath, ILockingProvider::LOCK_EXCLUSIVE, $lockingProvider);
372
+
373
+        self::scheduleExpire($user);
374
+
375
+        // if owner !== user we also need to update the owners trash size
376
+        if ($owner !== $user) {
377
+            self::scheduleExpire($owner);
378
+        }
379
+
380
+        return $moveSuccessful;
381
+    }
382
+
383
+    /**
384
+     * Move file versions to trash so that they can be restored later
385
+     *
386
+     * @param string $filename of deleted file
387
+     * @param string $owner owner user id
388
+     * @param string $ownerPath path relative to the owner's home storage
389
+     * @param integer $timestamp when the file was deleted
390
+     */
391
+    private static function retainVersions($filename, $owner, $ownerPath, $timestamp) {
392
+        if (\OCP\Server::get(IAppManager::class)->isEnabledForUser('files_versions') && !empty($ownerPath)) {
393
+            $user = OC_User::getUser();
394
+            $rootView = new View('/');
395
+
396
+            if ($rootView->is_dir($owner . '/files_versions/' . $ownerPath)) {
397
+                if ($owner !== $user) {
398
+                    self::copy_recursive($owner . '/files_versions/' . $ownerPath, $owner . '/files_trashbin/versions/' . static::getTrashFilename(basename($ownerPath), $timestamp), $rootView);
399
+                }
400
+                self::move($rootView, $owner . '/files_versions/' . $ownerPath, $user . '/files_trashbin/versions/' . static::getTrashFilename($filename, $timestamp));
401
+            } elseif ($versions = \OCA\Files_Versions\Storage::getVersions($owner, $ownerPath)) {
402
+                foreach ($versions as $v) {
403
+                    if ($owner !== $user) {
404
+                        self::copy($rootView, $owner . '/files_versions' . $v['path'] . '.v' . $v['version'], $owner . '/files_trashbin/versions/' . static::getTrashFilename($v['name'] . '.v' . $v['version'], $timestamp));
405
+                    }
406
+                    self::move($rootView, $owner . '/files_versions' . $v['path'] . '.v' . $v['version'], $user . '/files_trashbin/versions/' . static::getTrashFilename($filename . '.v' . $v['version'], $timestamp));
407
+                }
408
+            }
409
+        }
410
+    }
411
+
412
+    /**
413
+     * Move a file or folder on storage level
414
+     *
415
+     * @param View $view
416
+     * @param string $source
417
+     * @param string $target
418
+     * @return bool
419
+     */
420
+    private static function move(View $view, $source, $target) {
421
+        /** @var \OC\Files\Storage\Storage $sourceStorage */
422
+        [$sourceStorage, $sourceInternalPath] = $view->resolvePath($source);
423
+        /** @var \OC\Files\Storage\Storage $targetStorage */
424
+        [$targetStorage, $targetInternalPath] = $view->resolvePath($target);
425
+        /** @var \OC\Files\Storage\Storage $ownerTrashStorage */
426
+
427
+        $result = $targetStorage->moveFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
428
+        if ($result) {
429
+            $targetStorage->getUpdater()->renameFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
430
+        }
431
+        return $result;
432
+    }
433
+
434
+    /**
435
+     * Copy a file or folder on storage level
436
+     *
437
+     * @param View $view
438
+     * @param string $source
439
+     * @param string $target
440
+     * @return bool
441
+     */
442
+    private static function copy(View $view, $source, $target) {
443
+        /** @var \OC\Files\Storage\Storage $sourceStorage */
444
+        [$sourceStorage, $sourceInternalPath] = $view->resolvePath($source);
445
+        /** @var \OC\Files\Storage\Storage $targetStorage */
446
+        [$targetStorage, $targetInternalPath] = $view->resolvePath($target);
447
+        /** @var \OC\Files\Storage\Storage $ownerTrashStorage */
448
+
449
+        $result = $targetStorage->copyFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
450
+        if ($result) {
451
+            $targetStorage->getUpdater()->update($targetInternalPath);
452
+        }
453
+        return $result;
454
+    }
455
+
456
+    /**
457
+     * Restore a file or folder from trash bin
458
+     *
459
+     * @param string $file path to the deleted file/folder relative to "files_trashbin/files/",
460
+     * including the timestamp suffix ".d12345678"
461
+     * @param string $filename name of the file/folder
462
+     * @param int $timestamp time when the file/folder was deleted
463
+     *
464
+     * @return bool true on success, false otherwise
465
+     */
466
+    public static function restore($file, $filename, $timestamp) {
467
+        $user = OC_User::getUser();
468
+        $view = new View('/' . $user);
469
+
470
+        $location = '';
471
+        if ($timestamp) {
472
+            $location = self::getLocation($user, $filename, $timestamp);
473
+            if ($location === false) {
474
+                \OC::$server->get(LoggerInterface::class)->error('trash bin database inconsistent! ($user: ' . $user . ' $filename: ' . $filename . ', $timestamp: ' . $timestamp . ')', ['app' => 'files_trashbin']);
475
+            } else {
476
+                // if location no longer exists, restore file in the root directory
477
+                if ($location !== '/' &&
478
+                    (!$view->is_dir('files/' . $location) ||
479
+                        !$view->isCreatable('files/' . $location))
480
+                ) {
481
+                    $location = '';
482
+                }
483
+            }
484
+        }
485
+
486
+        // we need a  extension in case a file/dir with the same name already exists
487
+        $uniqueFilename = self::getUniqueFilename($location, $filename, $view);
488
+
489
+        $source = Filesystem::normalizePath('files_trashbin/files/' . $file);
490
+        $target = Filesystem::normalizePath('files/' . $location . '/' . $uniqueFilename);
491
+        if (!$view->file_exists($source)) {
492
+            return false;
493
+        }
494
+        $mtime = $view->filemtime($source);
495
+
496
+        // restore file
497
+        if (!$view->isCreatable(dirname($target))) {
498
+            throw new NotPermittedException("Can't restore trash item because the target folder is not writable");
499
+        }
500
+        $restoreResult = $view->rename($source, $target);
501
+
502
+        // handle the restore result
503
+        if ($restoreResult) {
504
+            $fakeRoot = $view->getRoot();
505
+            $view->chroot('/' . $user . '/files');
506
+            $view->touch('/' . $location . '/' . $uniqueFilename, $mtime);
507
+            $view->chroot($fakeRoot);
508
+            \OCP\Util::emitHook('\OCA\Files_Trashbin\Trashbin', 'post_restore', ['filePath' => Filesystem::normalizePath('/' . $location . '/' . $uniqueFilename),
509
+                'trashPath' => Filesystem::normalizePath($file)]);
510
+
511
+            self::restoreVersions($view, $file, $filename, $uniqueFilename, $location, $timestamp);
512
+
513
+            if ($timestamp) {
514
+                $query = \OC::$server->getDatabaseConnection()->getQueryBuilder();
515
+                $query->delete('files_trash')
516
+                    ->where($query->expr()->eq('user', $query->createNamedParameter($user)))
517
+                    ->andWhere($query->expr()->eq('id', $query->createNamedParameter($filename)))
518
+                    ->andWhere($query->expr()->eq('timestamp', $query->createNamedParameter($timestamp)));
519
+                $query->executeStatement();
520
+            }
521
+
522
+            return true;
523
+        }
524
+
525
+        return false;
526
+    }
527
+
528
+    /**
529
+     * restore versions from trash bin
530
+     *
531
+     * @param View $view file view
532
+     * @param string $file complete path to file
533
+     * @param string $filename name of file once it was deleted
534
+     * @param string $uniqueFilename new file name to restore the file without overwriting existing files
535
+     * @param string $location location if file
536
+     * @param int $timestamp deletion time
537
+     * @return false|null
538
+     */
539
+    private static function restoreVersions(View $view, $file, $filename, $uniqueFilename, $location, $timestamp) {
540
+        if (\OCP\Server::get(IAppManager::class)->isEnabledForUser('files_versions')) {
541
+            $user = OC_User::getUser();
542
+            $rootView = new View('/');
543
+
544
+            $target = Filesystem::normalizePath('/' . $location . '/' . $uniqueFilename);
545
+
546
+            [$owner, $ownerPath] = self::getUidAndFilename($target);
547
+
548
+            // file has been deleted in between
549
+            if (empty($ownerPath)) {
550
+                return false;
551
+            }
552
+
553
+            if ($timestamp) {
554
+                $versionedFile = $filename;
555
+            } else {
556
+                $versionedFile = $file;
557
+            }
558
+
559
+            if ($view->is_dir('/files_trashbin/versions/' . $file)) {
560
+                $rootView->rename(Filesystem::normalizePath($user . '/files_trashbin/versions/' . $file), Filesystem::normalizePath($owner . '/files_versions/' . $ownerPath));
561
+            } elseif ($versions = self::getVersionsFromTrash($versionedFile, $timestamp, $user)) {
562
+                foreach ($versions as $v) {
563
+                    if ($timestamp) {
564
+                        $rootView->rename($user . '/files_trashbin/versions/' . static::getTrashFilename($versionedFile . '.v' . $v, $timestamp), $owner . '/files_versions/' . $ownerPath . '.v' . $v);
565
+                    } else {
566
+                        $rootView->rename($user . '/files_trashbin/versions/' . $versionedFile . '.v' . $v, $owner . '/files_versions/' . $ownerPath . '.v' . $v);
567
+                    }
568
+                }
569
+            }
570
+        }
571
+    }
572
+
573
+    /**
574
+     * delete all files from the trash
575
+     */
576
+    public static function deleteAll() {
577
+        $user = OC_User::getUser();
578
+        $userRoot = \OC::$server->getUserFolder($user)->getParent();
579
+        $view = new View('/' . $user);
580
+        $fileInfos = $view->getDirectoryContent('files_trashbin/files');
581
+
582
+        try {
583
+            $trash = $userRoot->get('files_trashbin');
584
+        } catch (NotFoundException $e) {
585
+            return false;
586
+        }
587
+
588
+        // Array to store the relative path in (after the file is deleted, the view won't be able to relativise the path anymore)
589
+        $filePaths = [];
590
+        foreach ($fileInfos as $fileInfo) {
591
+            $filePaths[] = $view->getRelativePath($fileInfo->getPath());
592
+        }
593
+        unset($fileInfos); // save memory
594
+
595
+        // Bulk PreDelete-Hook
596
+        \OC_Hook::emit('\OCP\Trashbin', 'preDeleteAll', ['paths' => $filePaths]);
597
+
598
+        // Single-File Hooks
599
+        foreach ($filePaths as $path) {
600
+            self::emitTrashbinPreDelete($path);
601
+        }
602
+
603
+        // actual file deletion
604
+        $trash->delete();
605
+
606
+        $query = \OC::$server->getDatabaseConnection()->getQueryBuilder();
607
+        $query->delete('files_trash')
608
+            ->where($query->expr()->eq('user', $query->createNamedParameter($user)));
609
+        $query->executeStatement();
610
+
611
+        // Bulk PostDelete-Hook
612
+        \OC_Hook::emit('\OCP\Trashbin', 'deleteAll', ['paths' => $filePaths]);
613
+
614
+        // Single-File Hooks
615
+        foreach ($filePaths as $path) {
616
+            self::emitTrashbinPostDelete($path);
617
+        }
618
+
619
+        $trash = $userRoot->newFolder('files_trashbin');
620
+        $trash->newFolder('files');
621
+
622
+        return true;
623
+    }
624
+
625
+    /**
626
+     * wrapper function to emit the 'preDelete' hook of \OCP\Trashbin before a file is deleted
627
+     *
628
+     * @param string $path
629
+     */
630
+    protected static function emitTrashbinPreDelete($path) {
631
+        \OC_Hook::emit('\OCP\Trashbin', 'preDelete', ['path' => $path]);
632
+    }
633
+
634
+    /**
635
+     * wrapper function to emit the 'delete' hook of \OCP\Trashbin after a file has been deleted
636
+     *
637
+     * @param string $path
638
+     */
639
+    protected static function emitTrashbinPostDelete($path) {
640
+        \OC_Hook::emit('\OCP\Trashbin', 'delete', ['path' => $path]);
641
+    }
642
+
643
+    /**
644
+     * delete file from trash bin permanently
645
+     *
646
+     * @param string $filename path to the file
647
+     * @param string $user
648
+     * @param int $timestamp of deletion time
649
+     *
650
+     * @return int size of deleted files
651
+     */
652
+    public static function delete($filename, $user, $timestamp = null) {
653
+        $userRoot = \OC::$server->getUserFolder($user)->getParent();
654
+        $view = new View('/' . $user);
655
+        $size = 0;
656
+
657
+        if ($timestamp) {
658
+            $query = \OC::$server->getDatabaseConnection()->getQueryBuilder();
659
+            $query->delete('files_trash')
660
+                ->where($query->expr()->eq('user', $query->createNamedParameter($user)))
661
+                ->andWhere($query->expr()->eq('id', $query->createNamedParameter($filename)))
662
+                ->andWhere($query->expr()->eq('timestamp', $query->createNamedParameter($timestamp)));
663
+            $query->executeStatement();
664
+
665
+            $file = static::getTrashFilename($filename, $timestamp);
666
+        } else {
667
+            $file = $filename;
668
+        }
669
+
670
+        $size += self::deleteVersions($view, $file, $filename, $timestamp, $user);
671
+
672
+        try {
673
+            $node = $userRoot->get('/files_trashbin/files/' . $file);
674
+        } catch (NotFoundException $e) {
675
+            return $size;
676
+        }
677
+
678
+        if ($node instanceof Folder) {
679
+            $size += self::calculateSize(new View('/' . $user . '/files_trashbin/files/' . $file));
680
+        } elseif ($node instanceof File) {
681
+            $size += $view->filesize('/files_trashbin/files/' . $file);
682
+        }
683
+
684
+        self::emitTrashbinPreDelete('/files_trashbin/files/' . $file);
685
+        $node->delete();
686
+        self::emitTrashbinPostDelete('/files_trashbin/files/' . $file);
687
+
688
+        return $size;
689
+    }
690
+
691
+    /**
692
+     * @param View $view
693
+     * @param string $file
694
+     * @param string $filename
695
+     * @param integer|null $timestamp
696
+     * @param string $user
697
+     * @return int
698
+     */
699
+    private static function deleteVersions(View $view, $file, $filename, $timestamp, $user) {
700
+        $size = 0;
701
+        if (\OCP\Server::get(IAppManager::class)->isEnabledForUser('files_versions')) {
702
+            if ($view->is_dir('files_trashbin/versions/' . $file)) {
703
+                $size += self::calculateSize(new View('/' . $user . '/files_trashbin/versions/' . $file));
704
+                $view->unlink('files_trashbin/versions/' . $file);
705
+            } elseif ($versions = self::getVersionsFromTrash($filename, $timestamp, $user)) {
706
+                foreach ($versions as $v) {
707
+                    if ($timestamp) {
708
+                        $size += $view->filesize('/files_trashbin/versions/' . static::getTrashFilename($filename . '.v' . $v, $timestamp));
709
+                        $view->unlink('/files_trashbin/versions/' . static::getTrashFilename($filename . '.v' . $v, $timestamp));
710
+                    } else {
711
+                        $size += $view->filesize('/files_trashbin/versions/' . $filename . '.v' . $v);
712
+                        $view->unlink('/files_trashbin/versions/' . $filename . '.v' . $v);
713
+                    }
714
+                }
715
+            }
716
+        }
717
+        return $size;
718
+    }
719
+
720
+    /**
721
+     * check to see whether a file exists in trashbin
722
+     *
723
+     * @param string $filename path to the file
724
+     * @param int $timestamp of deletion time
725
+     * @return bool true if file exists, otherwise false
726
+     */
727
+    public static function file_exists($filename, $timestamp = null) {
728
+        $user = OC_User::getUser();
729
+        $view = new View('/' . $user);
730
+
731
+        if ($timestamp) {
732
+            $filename = static::getTrashFilename($filename, $timestamp);
733
+        }
734
+
735
+        $target = Filesystem::normalizePath('files_trashbin/files/' . $filename);
736
+        return $view->file_exists($target);
737
+    }
738
+
739
+    /**
740
+     * deletes used space for trash bin in db if user was deleted
741
+     *
742
+     * @param string $uid id of deleted user
743
+     * @return bool result of db delete operation
744
+     */
745
+    public static function deleteUser($uid) {
746
+        $query = \OC::$server->getDatabaseConnection()->getQueryBuilder();
747
+        $query->delete('files_trash')
748
+            ->where($query->expr()->eq('user', $query->createNamedParameter($uid)));
749
+        return (bool) $query->executeStatement();
750
+    }
751
+
752
+    /**
753
+     * calculate remaining free space for trash bin
754
+     *
755
+     * @param integer $trashbinSize current size of the trash bin
756
+     * @param string $user
757
+     * @return int available free space for trash bin
758
+     */
759
+    private static function calculateFreeSpace($trashbinSize, $user) {
760
+        $config = \OC::$server->getConfig();
761
+        $userTrashbinSize = (int)$config->getUserValue($user, 'files_trashbin', 'trashbin_size', '-1');
762
+        if ($userTrashbinSize > -1) {
763
+            return $userTrashbinSize - $trashbinSize;
764
+        }
765
+        $systemTrashbinSize = (int)$config->getAppValue('files_trashbin', 'trashbin_size', '-1');
766
+        if ($systemTrashbinSize > -1) {
767
+            return $systemTrashbinSize - $trashbinSize;
768
+        }
769
+
770
+        $softQuota = true;
771
+        $userObject = \OC::$server->getUserManager()->get($user);
772
+        if (is_null($userObject)) {
773
+            return 0;
774
+        }
775
+        $quota = $userObject->getQuota();
776
+        if ($quota === null || $quota === 'none') {
777
+            $quota = Filesystem::free_space('/');
778
+            $softQuota = false;
779
+            // inf or unknown free space
780
+            if ($quota < 0) {
781
+                $quota = PHP_INT_MAX;
782
+            }
783
+        } else {
784
+            $quota = \OCP\Util::computerFileSize($quota);
785
+        }
786
+
787
+        // calculate available space for trash bin
788
+        // subtract size of files and current trash bin size from quota
789
+        if ($softQuota) {
790
+            $userFolder = \OC::$server->getUserFolder($user);
791
+            if (is_null($userFolder)) {
792
+                return 0;
793
+            }
794
+            $free = $quota - $userFolder->getSize(false); // remaining free space for user
795
+            if ($free > 0) {
796
+                $availableSpace = ($free * self::DEFAULTMAXSIZE / 100) - $trashbinSize; // how much space can be used for versions
797
+            } else {
798
+                $availableSpace = $free - $trashbinSize;
799
+            }
800
+        } else {
801
+            $availableSpace = $quota;
802
+        }
803
+
804
+        return (int)$availableSpace;
805
+    }
806
+
807
+    /**
808
+     * resize trash bin if necessary after a new file was added to Nextcloud
809
+     *
810
+     * @param string $user user id
811
+     */
812
+    public static function resizeTrash($user) {
813
+        $size = self::getTrashbinSize($user);
814
+
815
+        $freeSpace = self::calculateFreeSpace($size, $user);
816
+
817
+        if ($freeSpace < 0) {
818
+            self::scheduleExpire($user);
819
+        }
820
+    }
821
+
822
+    /**
823
+     * clean up the trash bin
824
+     *
825
+     * @param string $user
826
+     */
827
+    public static function expire($user) {
828
+        $trashBinSize = self::getTrashbinSize($user);
829
+        $availableSpace = self::calculateFreeSpace($trashBinSize, $user);
830
+
831
+        $dirContent = Helper::getTrashFiles('/', $user, 'mtime');
832
+
833
+        // delete all files older then $retention_obligation
834
+        [$delSize, $count] = self::deleteExpiredFiles($dirContent, $user);
835
+
836
+        $availableSpace += $delSize;
837
+
838
+        // delete files from trash until we meet the trash bin size limit again
839
+        self::deleteFiles(array_slice($dirContent, $count), $user, $availableSpace);
840
+    }
841
+
842
+    /**
843
+     * @param string $user
844
+     */
845
+    private static function scheduleExpire($user) {
846
+        // let the admin disable auto expire
847
+        /** @var Application $application */
848
+        $application = \OC::$server->query(Application::class);
849
+        $expiration = $application->getContainer()->query('Expiration');
850
+        if ($expiration->isEnabled()) {
851
+            \OC::$server->getCommandBus()->push(new Expire($user));
852
+        }
853
+    }
854
+
855
+    /**
856
+     * if the size limit for the trash bin is reached, we delete the oldest
857
+     * files in the trash bin until we meet the limit again
858
+     *
859
+     * @param array $files
860
+     * @param string $user
861
+     * @param int $availableSpace available disc space
862
+     * @return int size of deleted files
863
+     */
864
+    protected static function deleteFiles($files, $user, $availableSpace) {
865
+        /** @var Application $application */
866
+        $application = \OC::$server->query(Application::class);
867
+        $expiration = $application->getContainer()->query('Expiration');
868
+        $size = 0;
869
+
870
+        if ($availableSpace < 0) {
871
+            foreach ($files as $file) {
872
+                if ($availableSpace < 0 && $expiration->isExpired($file['mtime'], true)) {
873
+                    $tmp = self::delete($file['name'], $user, $file['mtime']);
874
+                    \OC::$server->get(LoggerInterface::class)->info('remove "' . $file['name'] . '" (' . $tmp . 'B) to meet the limit of trash bin size (50% of available quota)', ['app' => 'files_trashbin']);
875
+                    $availableSpace += $tmp;
876
+                    $size += $tmp;
877
+                } else {
878
+                    break;
879
+                }
880
+            }
881
+        }
882
+        return $size;
883
+    }
884
+
885
+    /**
886
+     * delete files older then max storage time
887
+     *
888
+     * @param array $files list of files sorted by mtime
889
+     * @param string $user
890
+     * @return integer[] size of deleted files and number of deleted files
891
+     */
892
+    public static function deleteExpiredFiles($files, $user) {
893
+        /** @var Expiration $expiration */
894
+        $expiration = \OC::$server->query(Expiration::class);
895
+        $size = 0;
896
+        $count = 0;
897
+        foreach ($files as $file) {
898
+            $timestamp = $file['mtime'];
899
+            $filename = $file['name'];
900
+            if ($expiration->isExpired($timestamp)) {
901
+                try {
902
+                    $size += self::delete($filename, $user, $timestamp);
903
+                    $count++;
904
+                } catch (\OCP\Files\NotPermittedException $e) {
905
+                    \OC::$server->get(LoggerInterface::class)->warning('Removing "' . $filename . '" from trashbin failed.',
906
+                        [
907
+                            'exception' => $e,
908
+                            'app' => 'files_trashbin',
909
+                        ]
910
+                    );
911
+                }
912
+                \OC::$server->get(LoggerInterface::class)->info(
913
+                    'Remove "' . $filename . '" from trashbin because it exceeds max retention obligation term.',
914
+                    ['app' => 'files_trashbin']
915
+                );
916
+            } else {
917
+                break;
918
+            }
919
+        }
920
+
921
+        return [$size, $count];
922
+    }
923
+
924
+    /**
925
+     * recursive copy to copy a whole directory
926
+     *
927
+     * @param string $source source path, relative to the users files directory
928
+     * @param string $destination destination path relative to the users root directory
929
+     * @param View $view file view for the users root directory
930
+     * @return int
931
+     * @throws Exceptions\CopyRecursiveException
932
+     */
933
+    private static function copy_recursive($source, $destination, View $view) {
934
+        $size = 0;
935
+        if ($view->is_dir($source)) {
936
+            $view->mkdir($destination);
937
+            $view->touch($destination, $view->filemtime($source));
938
+            foreach ($view->getDirectoryContent($source) as $i) {
939
+                $pathDir = $source . '/' . $i['name'];
940
+                if ($view->is_dir($pathDir)) {
941
+                    $size += self::copy_recursive($pathDir, $destination . '/' . $i['name'], $view);
942
+                } else {
943
+                    $size += $view->filesize($pathDir);
944
+                    $result = $view->copy($pathDir, $destination . '/' . $i['name']);
945
+                    if (!$result) {
946
+                        throw new \OCA\Files_Trashbin\Exceptions\CopyRecursiveException();
947
+                    }
948
+                    $view->touch($destination . '/' . $i['name'], $view->filemtime($pathDir));
949
+                }
950
+            }
951
+        } else {
952
+            $size += $view->filesize($source);
953
+            $result = $view->copy($source, $destination);
954
+            if (!$result) {
955
+                throw new \OCA\Files_Trashbin\Exceptions\CopyRecursiveException();
956
+            }
957
+            $view->touch($destination, $view->filemtime($source));
958
+        }
959
+        return $size;
960
+    }
961
+
962
+    /**
963
+     * find all versions which belong to the file we want to restore
964
+     *
965
+     * @param string $filename name of the file which should be restored
966
+     * @param int $timestamp timestamp when the file was deleted
967
+     * @return array
968
+     */
969
+    private static function getVersionsFromTrash($filename, $timestamp, $user) {
970
+        $view = new View('/' . $user . '/files_trashbin/versions');
971
+        $versions = [];
972
+
973
+        /** @var \OC\Files\Storage\Storage $storage */
974
+        [$storage,] = $view->resolvePath('/');
975
+
976
+        $pattern = \OC::$server->getDatabaseConnection()->escapeLikeParameter(basename($filename));
977
+        if ($timestamp) {
978
+            // fetch for old versions
979
+            $escapedTimestamp = \OC::$server->getDatabaseConnection()->escapeLikeParameter($timestamp);
980
+            $pattern .= '.v%.d' . $escapedTimestamp;
981
+            $offset = -strlen($escapedTimestamp) - 2;
982
+        } else {
983
+            $pattern .= '.v%';
984
+        }
985
+
986
+        // Manually fetch all versions from the file cache to be able to filter them by their parent
987
+        $cache = $storage->getCache('');
988
+        $query = new CacheQueryBuilder(
989
+            \OC::$server->getDatabaseConnection(),
990
+            \OC::$server->getSystemConfig(),
991
+            \OC::$server->get(LoggerInterface::class)
992
+        );
993
+        $normalizedParentPath = ltrim(Filesystem::normalizePath(dirname('files_trashbin/versions/'. $filename)), '/');
994
+        $parentId = $cache->getId($normalizedParentPath);
995
+        if ($parentId === -1) {
996
+            return [];
997
+        }
998
+
999
+        $query->selectFileCache()
1000
+            ->whereStorageId($cache->getNumericStorageId())
1001
+            ->andWhere($query->expr()->eq('parent', $query->createNamedParameter($parentId)))
1002
+            ->andWhere($query->expr()->iLike('name', $query->createNamedParameter($pattern)));
1003
+
1004
+        $result = $query->executeQuery();
1005
+        $entries = $result->fetchAll();
1006
+        $result->closeCursor();
1007
+
1008
+        /** @var CacheEntry[] $matches */
1009
+        $matches = array_map(function (array $data) {
1010
+            return Cache::cacheEntryFromData($data, \OC::$server->getMimeTypeLoader());
1011
+        }, $entries);
1012
+
1013
+        foreach ($matches as $ma) {
1014
+            if ($timestamp) {
1015
+                $parts = explode('.v', substr($ma['path'], 0, $offset));
1016
+                $versions[] = end($parts);
1017
+            } else {
1018
+                $parts = explode('.v', $ma['path']);
1019
+                $versions[] = end($parts);
1020
+            }
1021
+        }
1022
+
1023
+        return $versions;
1024
+    }
1025
+
1026
+    /**
1027
+     * find unique extension for restored file if a file with the same name already exists
1028
+     *
1029
+     * @param string $location where the file should be restored
1030
+     * @param string $filename name of the file
1031
+     * @param View $view filesystem view relative to users root directory
1032
+     * @return string with unique extension
1033
+     */
1034
+    private static function getUniqueFilename($location, $filename, View $view) {
1035
+        $ext = pathinfo($filename, PATHINFO_EXTENSION);
1036
+        $name = pathinfo($filename, PATHINFO_FILENAME);
1037
+        $l = \OC::$server->getL10N('files_trashbin');
1038
+
1039
+        $location = '/' . trim($location, '/');
1040
+
1041
+        // if extension is not empty we set a dot in front of it
1042
+        if ($ext !== '') {
1043
+            $ext = '.' . $ext;
1044
+        }
1045
+
1046
+        if ($view->file_exists('files' . $location . '/' . $filename)) {
1047
+            $i = 2;
1048
+            $uniqueName = $name . " (" . $l->t("restored") . ")" . $ext;
1049
+            while ($view->file_exists('files' . $location . '/' . $uniqueName)) {
1050
+                $uniqueName = $name . " (" . $l->t("restored") . " " . $i . ")" . $ext;
1051
+                $i++;
1052
+            }
1053
+
1054
+            return $uniqueName;
1055
+        }
1056
+
1057
+        return $filename;
1058
+    }
1059
+
1060
+    /**
1061
+     * get the size from a given root folder
1062
+     *
1063
+     * @param View $view file view on the root folder
1064
+     * @return integer size of the folder
1065
+     */
1066
+    private static function calculateSize($view) {
1067
+        $root = \OC::$server->getConfig()->getSystemValue('datadirectory', \OC::$SERVERROOT . '/data') . $view->getAbsolutePath('');
1068
+        if (!file_exists($root)) {
1069
+            return 0;
1070
+        }
1071
+        $iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($root), \RecursiveIteratorIterator::CHILD_FIRST);
1072
+        $size = 0;
1073
+
1074
+        /**
1075
+         * RecursiveDirectoryIterator on an NFS path isn't iterable with foreach
1076
+         * This bug is fixed in PHP 5.5.9 or before
1077
+         * See #8376
1078
+         */
1079
+        $iterator->rewind();
1080
+        while ($iterator->valid()) {
1081
+            $path = $iterator->current();
1082
+            $relpath = substr($path, strlen($root) - 1);
1083
+            if (!$view->is_dir($relpath)) {
1084
+                $size += $view->filesize($relpath);
1085
+            }
1086
+            $iterator->next();
1087
+        }
1088
+        return $size;
1089
+    }
1090
+
1091
+    /**
1092
+     * get current size of trash bin from a given user
1093
+     *
1094
+     * @param string $user user who owns the trash bin
1095
+     * @return integer trash bin size
1096
+     */
1097
+    private static function getTrashbinSize($user) {
1098
+        $view = new View('/' . $user);
1099
+        $fileInfo = $view->getFileInfo('/files_trashbin');
1100
+        return isset($fileInfo['size']) ? $fileInfo['size'] : 0;
1101
+    }
1102
+
1103
+    /**
1104
+     * check if trash bin is empty for a given user
1105
+     *
1106
+     * @param string $user
1107
+     * @return bool
1108
+     */
1109
+    public static function isEmpty($user) {
1110
+        $view = new View('/' . $user . '/files_trashbin');
1111
+        if ($view->is_dir('/files') && $dh = $view->opendir('/files')) {
1112
+            while ($file = readdir($dh)) {
1113
+                if (!Filesystem::isIgnoredDir($file)) {
1114
+                    return false;
1115
+                }
1116
+            }
1117
+        }
1118
+        return true;
1119
+    }
1120
+
1121
+    /**
1122
+     * @param $path
1123
+     * @return string
1124
+     */
1125
+    public static function preview_icon($path) {
1126
+        return \OC::$server->getURLGenerator()->linkToRoute('core_ajax_trashbin_preview', ['x' => 32, 'y' => 32, 'file' => $path]);
1127
+    }
1128
+
1129
+    /**
1130
+     * Return the filename used in the trash bin
1131
+     */
1132
+    public static function getTrashFilename(string $filename, int $timestamp): string {
1133
+        $trashFilename = $filename . '.d' . $timestamp;
1134
+        $length = strlen($trashFilename);
1135
+        // oc_filecache `name` column has a limit of 250 chars
1136
+        $maxLength = 250;
1137
+        if ($length > $maxLength) {
1138
+            $trashFilename = substr_replace(
1139
+                $trashFilename,
1140
+                '',
1141
+                $maxLength / 2,
1142
+                $length - $maxLength
1143
+            );
1144
+        }
1145
+        return $trashFilename;
1146
+    }
1147 1147
 }
Please login to merge, or discard this patch.
Spacing   +71 added lines, -71 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 !== OC_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) {
@@ -168,7 +168,7 @@  discard block
 block discarded – undo
168 168
 	}
169 169
 
170 170
 	private static function setUpTrash($user) {
171
-		$view = new View('/' . $user);
171
+		$view = new View('/'.$user);
172 172
 		if (!$view->is_dir('files_trashbin')) {
173 173
 			$view->mkdir('files_trashbin');
174 174
 		}
@@ -203,8 +203,8 @@  discard block
 block discarded – undo
203 203
 
204 204
 		$view = new View('/');
205 205
 
206
-		$target = $user . '/files_trashbin/files/' . static::getTrashFilename($targetFilename, $timestamp);
207
-		$source = $owner . '/files_trashbin/files/' . static::getTrashFilename($sourceFilename, $timestamp);
206
+		$target = $user.'/files_trashbin/files/'.static::getTrashFilename($targetFilename, $timestamp);
207
+		$source = $owner.'/files_trashbin/files/'.static::getTrashFilename($sourceFilename, $timestamp);
208 208
 		$free = $view->free_space($target);
209 209
 		$isUnknownOrUnlimitedFreeSpace = $free < 0;
210 210
 		$isEnoughFreeSpaceLeft = $view->filesize($source) < $free;
@@ -248,14 +248,14 @@  discard block
 block discarded – undo
248 248
 			$ownerPath = $file_path;
249 249
 		}
250 250
 
251
-		$ownerView = new View('/' . $owner);
251
+		$ownerView = new View('/'.$owner);
252 252
 
253 253
 		// file has been deleted in between
254 254
 		if (is_null($ownerPath) || $ownerPath === '') {
255 255
 			return true;
256 256
 		}
257 257
 
258
-		$sourceInfo = $ownerView->getFileInfo('/files/' . $ownerPath);
258
+		$sourceInfo = $ownerView->getFileInfo('/files/'.$ownerPath);
259 259
 
260 260
 		if ($sourceInfo === false) {
261 261
 			return true;
@@ -278,7 +278,7 @@  discard block
 block discarded – undo
278 278
 		$lockingProvider = \OC::$server->getLockingProvider();
279 279
 
280 280
 		// disable proxy to prevent recursive calls
281
-		$trashPath = '/files_trashbin/files/' . static::getTrashFilename($filename, $timestamp);
281
+		$trashPath = '/files_trashbin/files/'.static::getTrashFilename($filename, $timestamp);
282 282
 		$gotLock = false;
283 283
 
284 284
 		while (!$gotLock) {
@@ -294,7 +294,7 @@  discard block
 block discarded – undo
294 294
 
295 295
 				$timestamp = $timestamp + 1;
296 296
 
297
-				$trashPath = '/files_trashbin/files/' . static::getTrashFilename($filename, $timestamp);
297
+				$trashPath = '/files_trashbin/files/'.static::getTrashFilename($filename, $timestamp);
298 298
 			}
299 299
 		}
300 300
 
@@ -306,8 +306,8 @@  discard block
 block discarded – undo
306 306
 		}
307 307
 
308 308
 		$config = \OC::$server->getConfig();
309
-		$systemTrashbinSize = (int)$config->getAppValue('files_trashbin', 'trashbin_size', '-1');
310
-		$userTrashbinSize = (int)$config->getUserValue($owner, 'files_trashbin', 'trashbin_size', '-1');
309
+		$systemTrashbinSize = (int) $config->getAppValue('files_trashbin', 'trashbin_size', '-1');
310
+		$userTrashbinSize = (int) $config->getUserValue($owner, 'files_trashbin', 'trashbin_size', '-1');
311 311
 		$configuredTrashbinSize = ($userTrashbinSize < 0) ? $systemTrashbinSize : $userTrashbinSize;
312 312
 		if ($configuredTrashbinSize >= 0 && $sourceInfo->getSize() >= $configuredTrashbinSize) {
313 313
 			return false;
@@ -327,7 +327,7 @@  discard block
 block discarded – undo
327 327
 			if ($trashStorage->file_exists($trashInternalPath)) {
328 328
 				$trashStorage->unlink($trashInternalPath);
329 329
 			}
330
-			\OC::$server->get(LoggerInterface::class)->error('Couldn\'t move ' . $file_path . ' to the trash bin', ['app' => 'files_trashbin']);
330
+			\OC::$server->get(LoggerInterface::class)->error('Couldn\'t move '.$file_path.' to the trash bin', ['app' => 'files_trashbin']);
331 331
 		}
332 332
 
333 333
 		if ($sourceStorage->file_exists($sourceInternalPath)) { // failed to delete the original file, abort
@@ -393,17 +393,17 @@  discard block
 block discarded – undo
393 393
 			$user = OC_User::getUser();
394 394
 			$rootView = new View('/');
395 395
 
396
-			if ($rootView->is_dir($owner . '/files_versions/' . $ownerPath)) {
396
+			if ($rootView->is_dir($owner.'/files_versions/'.$ownerPath)) {
397 397
 				if ($owner !== $user) {
398
-					self::copy_recursive($owner . '/files_versions/' . $ownerPath, $owner . '/files_trashbin/versions/' . static::getTrashFilename(basename($ownerPath), $timestamp), $rootView);
398
+					self::copy_recursive($owner.'/files_versions/'.$ownerPath, $owner.'/files_trashbin/versions/'.static::getTrashFilename(basename($ownerPath), $timestamp), $rootView);
399 399
 				}
400
-				self::move($rootView, $owner . '/files_versions/' . $ownerPath, $user . '/files_trashbin/versions/' . static::getTrashFilename($filename, $timestamp));
400
+				self::move($rootView, $owner.'/files_versions/'.$ownerPath, $user.'/files_trashbin/versions/'.static::getTrashFilename($filename, $timestamp));
401 401
 			} elseif ($versions = \OCA\Files_Versions\Storage::getVersions($owner, $ownerPath)) {
402 402
 				foreach ($versions as $v) {
403 403
 					if ($owner !== $user) {
404
-						self::copy($rootView, $owner . '/files_versions' . $v['path'] . '.v' . $v['version'], $owner . '/files_trashbin/versions/' . static::getTrashFilename($v['name'] . '.v' . $v['version'], $timestamp));
404
+						self::copy($rootView, $owner.'/files_versions'.$v['path'].'.v'.$v['version'], $owner.'/files_trashbin/versions/'.static::getTrashFilename($v['name'].'.v'.$v['version'], $timestamp));
405 405
 					}
406
-					self::move($rootView, $owner . '/files_versions' . $v['path'] . '.v' . $v['version'], $user . '/files_trashbin/versions/' . static::getTrashFilename($filename . '.v' . $v['version'], $timestamp));
406
+					self::move($rootView, $owner.'/files_versions'.$v['path'].'.v'.$v['version'], $user.'/files_trashbin/versions/'.static::getTrashFilename($filename.'.v'.$v['version'], $timestamp));
407 407
 				}
408 408
 			}
409 409
 		}
@@ -465,18 +465,18 @@  discard block
 block discarded – undo
465 465
 	 */
466 466
 	public static function restore($file, $filename, $timestamp) {
467 467
 		$user = OC_User::getUser();
468
-		$view = new View('/' . $user);
468
+		$view = new View('/'.$user);
469 469
 
470 470
 		$location = '';
471 471
 		if ($timestamp) {
472 472
 			$location = self::getLocation($user, $filename, $timestamp);
473 473
 			if ($location === false) {
474
-				\OC::$server->get(LoggerInterface::class)->error('trash bin database inconsistent! ($user: ' . $user . ' $filename: ' . $filename . ', $timestamp: ' . $timestamp . ')', ['app' => 'files_trashbin']);
474
+				\OC::$server->get(LoggerInterface::class)->error('trash bin database inconsistent! ($user: '.$user.' $filename: '.$filename.', $timestamp: '.$timestamp.')', ['app' => 'files_trashbin']);
475 475
 			} else {
476 476
 				// if location no longer exists, restore file in the root directory
477 477
 				if ($location !== '/' &&
478
-					(!$view->is_dir('files/' . $location) ||
479
-						!$view->isCreatable('files/' . $location))
478
+					(!$view->is_dir('files/'.$location) ||
479
+						!$view->isCreatable('files/'.$location))
480 480
 				) {
481 481
 					$location = '';
482 482
 				}
@@ -486,8 +486,8 @@  discard block
 block discarded – undo
486 486
 		// we need a  extension in case a file/dir with the same name already exists
487 487
 		$uniqueFilename = self::getUniqueFilename($location, $filename, $view);
488 488
 
489
-		$source = Filesystem::normalizePath('files_trashbin/files/' . $file);
490
-		$target = Filesystem::normalizePath('files/' . $location . '/' . $uniqueFilename);
489
+		$source = Filesystem::normalizePath('files_trashbin/files/'.$file);
490
+		$target = Filesystem::normalizePath('files/'.$location.'/'.$uniqueFilename);
491 491
 		if (!$view->file_exists($source)) {
492 492
 			return false;
493 493
 		}
@@ -502,10 +502,10 @@  discard block
 block discarded – undo
502 502
 		// handle the restore result
503 503
 		if ($restoreResult) {
504 504
 			$fakeRoot = $view->getRoot();
505
-			$view->chroot('/' . $user . '/files');
506
-			$view->touch('/' . $location . '/' . $uniqueFilename, $mtime);
505
+			$view->chroot('/'.$user.'/files');
506
+			$view->touch('/'.$location.'/'.$uniqueFilename, $mtime);
507 507
 			$view->chroot($fakeRoot);
508
-			\OCP\Util::emitHook('\OCA\Files_Trashbin\Trashbin', 'post_restore', ['filePath' => Filesystem::normalizePath('/' . $location . '/' . $uniqueFilename),
508
+			\OCP\Util::emitHook('\OCA\Files_Trashbin\Trashbin', 'post_restore', ['filePath' => Filesystem::normalizePath('/'.$location.'/'.$uniqueFilename),
509 509
 				'trashPath' => Filesystem::normalizePath($file)]);
510 510
 
511 511
 			self::restoreVersions($view, $file, $filename, $uniqueFilename, $location, $timestamp);
@@ -541,7 +541,7 @@  discard block
 block discarded – undo
541 541
 			$user = OC_User::getUser();
542 542
 			$rootView = new View('/');
543 543
 
544
-			$target = Filesystem::normalizePath('/' . $location . '/' . $uniqueFilename);
544
+			$target = Filesystem::normalizePath('/'.$location.'/'.$uniqueFilename);
545 545
 
546 546
 			[$owner, $ownerPath] = self::getUidAndFilename($target);
547 547
 
@@ -556,14 +556,14 @@  discard block
 block discarded – undo
556 556
 				$versionedFile = $file;
557 557
 			}
558 558
 
559
-			if ($view->is_dir('/files_trashbin/versions/' . $file)) {
560
-				$rootView->rename(Filesystem::normalizePath($user . '/files_trashbin/versions/' . $file), Filesystem::normalizePath($owner . '/files_versions/' . $ownerPath));
559
+			if ($view->is_dir('/files_trashbin/versions/'.$file)) {
560
+				$rootView->rename(Filesystem::normalizePath($user.'/files_trashbin/versions/'.$file), Filesystem::normalizePath($owner.'/files_versions/'.$ownerPath));
561 561
 			} elseif ($versions = self::getVersionsFromTrash($versionedFile, $timestamp, $user)) {
562 562
 				foreach ($versions as $v) {
563 563
 					if ($timestamp) {
564
-						$rootView->rename($user . '/files_trashbin/versions/' . static::getTrashFilename($versionedFile . '.v' . $v, $timestamp), $owner . '/files_versions/' . $ownerPath . '.v' . $v);
564
+						$rootView->rename($user.'/files_trashbin/versions/'.static::getTrashFilename($versionedFile.'.v'.$v, $timestamp), $owner.'/files_versions/'.$ownerPath.'.v'.$v);
565 565
 					} else {
566
-						$rootView->rename($user . '/files_trashbin/versions/' . $versionedFile . '.v' . $v, $owner . '/files_versions/' . $ownerPath . '.v' . $v);
566
+						$rootView->rename($user.'/files_trashbin/versions/'.$versionedFile.'.v'.$v, $owner.'/files_versions/'.$ownerPath.'.v'.$v);
567 567
 					}
568 568
 				}
569 569
 			}
@@ -576,7 +576,7 @@  discard block
 block discarded – undo
576 576
 	public static function deleteAll() {
577 577
 		$user = OC_User::getUser();
578 578
 		$userRoot = \OC::$server->getUserFolder($user)->getParent();
579
-		$view = new View('/' . $user);
579
+		$view = new View('/'.$user);
580 580
 		$fileInfos = $view->getDirectoryContent('files_trashbin/files');
581 581
 
582 582
 		try {
@@ -651,7 +651,7 @@  discard block
 block discarded – undo
651 651
 	 */
652 652
 	public static function delete($filename, $user, $timestamp = null) {
653 653
 		$userRoot = \OC::$server->getUserFolder($user)->getParent();
654
-		$view = new View('/' . $user);
654
+		$view = new View('/'.$user);
655 655
 		$size = 0;
656 656
 
657 657
 		if ($timestamp) {
@@ -670,20 +670,20 @@  discard block
 block discarded – undo
670 670
 		$size += self::deleteVersions($view, $file, $filename, $timestamp, $user);
671 671
 
672 672
 		try {
673
-			$node = $userRoot->get('/files_trashbin/files/' . $file);
673
+			$node = $userRoot->get('/files_trashbin/files/'.$file);
674 674
 		} catch (NotFoundException $e) {
675 675
 			return $size;
676 676
 		}
677 677
 
678 678
 		if ($node instanceof Folder) {
679
-			$size += self::calculateSize(new View('/' . $user . '/files_trashbin/files/' . $file));
679
+			$size += self::calculateSize(new View('/'.$user.'/files_trashbin/files/'.$file));
680 680
 		} elseif ($node instanceof File) {
681
-			$size += $view->filesize('/files_trashbin/files/' . $file);
681
+			$size += $view->filesize('/files_trashbin/files/'.$file);
682 682
 		}
683 683
 
684
-		self::emitTrashbinPreDelete('/files_trashbin/files/' . $file);
684
+		self::emitTrashbinPreDelete('/files_trashbin/files/'.$file);
685 685
 		$node->delete();
686
-		self::emitTrashbinPostDelete('/files_trashbin/files/' . $file);
686
+		self::emitTrashbinPostDelete('/files_trashbin/files/'.$file);
687 687
 
688 688
 		return $size;
689 689
 	}
@@ -699,17 +699,17 @@  discard block
 block discarded – undo
699 699
 	private static function deleteVersions(View $view, $file, $filename, $timestamp, $user) {
700 700
 		$size = 0;
701 701
 		if (\OCP\Server::get(IAppManager::class)->isEnabledForUser('files_versions')) {
702
-			if ($view->is_dir('files_trashbin/versions/' . $file)) {
703
-				$size += self::calculateSize(new View('/' . $user . '/files_trashbin/versions/' . $file));
704
-				$view->unlink('files_trashbin/versions/' . $file);
702
+			if ($view->is_dir('files_trashbin/versions/'.$file)) {
703
+				$size += self::calculateSize(new View('/'.$user.'/files_trashbin/versions/'.$file));
704
+				$view->unlink('files_trashbin/versions/'.$file);
705 705
 			} elseif ($versions = self::getVersionsFromTrash($filename, $timestamp, $user)) {
706 706
 				foreach ($versions as $v) {
707 707
 					if ($timestamp) {
708
-						$size += $view->filesize('/files_trashbin/versions/' . static::getTrashFilename($filename . '.v' . $v, $timestamp));
709
-						$view->unlink('/files_trashbin/versions/' . static::getTrashFilename($filename . '.v' . $v, $timestamp));
708
+						$size += $view->filesize('/files_trashbin/versions/'.static::getTrashFilename($filename.'.v'.$v, $timestamp));
709
+						$view->unlink('/files_trashbin/versions/'.static::getTrashFilename($filename.'.v'.$v, $timestamp));
710 710
 					} else {
711
-						$size += $view->filesize('/files_trashbin/versions/' . $filename . '.v' . $v);
712
-						$view->unlink('/files_trashbin/versions/' . $filename . '.v' . $v);
711
+						$size += $view->filesize('/files_trashbin/versions/'.$filename.'.v'.$v);
712
+						$view->unlink('/files_trashbin/versions/'.$filename.'.v'.$v);
713 713
 					}
714 714
 				}
715 715
 			}
@@ -726,13 +726,13 @@  discard block
 block discarded – undo
726 726
 	 */
727 727
 	public static function file_exists($filename, $timestamp = null) {
728 728
 		$user = OC_User::getUser();
729
-		$view = new View('/' . $user);
729
+		$view = new View('/'.$user);
730 730
 
731 731
 		if ($timestamp) {
732 732
 			$filename = static::getTrashFilename($filename, $timestamp);
733 733
 		}
734 734
 
735
-		$target = Filesystem::normalizePath('files_trashbin/files/' . $filename);
735
+		$target = Filesystem::normalizePath('files_trashbin/files/'.$filename);
736 736
 		return $view->file_exists($target);
737 737
 	}
738 738
 
@@ -758,11 +758,11 @@  discard block
 block discarded – undo
758 758
 	 */
759 759
 	private static function calculateFreeSpace($trashbinSize, $user) {
760 760
 		$config = \OC::$server->getConfig();
761
-		$userTrashbinSize = (int)$config->getUserValue($user, 'files_trashbin', 'trashbin_size', '-1');
761
+		$userTrashbinSize = (int) $config->getUserValue($user, 'files_trashbin', 'trashbin_size', '-1');
762 762
 		if ($userTrashbinSize > -1) {
763 763
 			return $userTrashbinSize - $trashbinSize;
764 764
 		}
765
-		$systemTrashbinSize = (int)$config->getAppValue('files_trashbin', 'trashbin_size', '-1');
765
+		$systemTrashbinSize = (int) $config->getAppValue('files_trashbin', 'trashbin_size', '-1');
766 766
 		if ($systemTrashbinSize > -1) {
767 767
 			return $systemTrashbinSize - $trashbinSize;
768 768
 		}
@@ -801,7 +801,7 @@  discard block
 block discarded – undo
801 801
 			$availableSpace = $quota;
802 802
 		}
803 803
 
804
-		return (int)$availableSpace;
804
+		return (int) $availableSpace;
805 805
 	}
806 806
 
807 807
 	/**
@@ -871,7 +871,7 @@  discard block
 block discarded – undo
871 871
 			foreach ($files as $file) {
872 872
 				if ($availableSpace < 0 && $expiration->isExpired($file['mtime'], true)) {
873 873
 					$tmp = self::delete($file['name'], $user, $file['mtime']);
874
-					\OC::$server->get(LoggerInterface::class)->info('remove "' . $file['name'] . '" (' . $tmp . 'B) to meet the limit of trash bin size (50% of available quota)', ['app' => 'files_trashbin']);
874
+					\OC::$server->get(LoggerInterface::class)->info('remove "'.$file['name'].'" ('.$tmp.'B) to meet the limit of trash bin size (50% of available quota)', ['app' => 'files_trashbin']);
875 875
 					$availableSpace += $tmp;
876 876
 					$size += $tmp;
877 877
 				} else {
@@ -902,7 +902,7 @@  discard block
 block discarded – undo
902 902
 					$size += self::delete($filename, $user, $timestamp);
903 903
 					$count++;
904 904
 				} catch (\OCP\Files\NotPermittedException $e) {
905
-					\OC::$server->get(LoggerInterface::class)->warning('Removing "' . $filename . '" from trashbin failed.',
905
+					\OC::$server->get(LoggerInterface::class)->warning('Removing "'.$filename.'" from trashbin failed.',
906 906
 						[
907 907
 							'exception' => $e,
908 908
 							'app' => 'files_trashbin',
@@ -910,7 +910,7 @@  discard block
 block discarded – undo
910 910
 					);
911 911
 				}
912 912
 				\OC::$server->get(LoggerInterface::class)->info(
913
-					'Remove "' . $filename . '" from trashbin because it exceeds max retention obligation term.',
913
+					'Remove "'.$filename.'" from trashbin because it exceeds max retention obligation term.',
914 914
 					['app' => 'files_trashbin']
915 915
 				);
916 916
 			} else {
@@ -936,16 +936,16 @@  discard block
 block discarded – undo
936 936
 			$view->mkdir($destination);
937 937
 			$view->touch($destination, $view->filemtime($source));
938 938
 			foreach ($view->getDirectoryContent($source) as $i) {
939
-				$pathDir = $source . '/' . $i['name'];
939
+				$pathDir = $source.'/'.$i['name'];
940 940
 				if ($view->is_dir($pathDir)) {
941
-					$size += self::copy_recursive($pathDir, $destination . '/' . $i['name'], $view);
941
+					$size += self::copy_recursive($pathDir, $destination.'/'.$i['name'], $view);
942 942
 				} else {
943 943
 					$size += $view->filesize($pathDir);
944
-					$result = $view->copy($pathDir, $destination . '/' . $i['name']);
944
+					$result = $view->copy($pathDir, $destination.'/'.$i['name']);
945 945
 					if (!$result) {
946 946
 						throw new \OCA\Files_Trashbin\Exceptions\CopyRecursiveException();
947 947
 					}
948
-					$view->touch($destination . '/' . $i['name'], $view->filemtime($pathDir));
948
+					$view->touch($destination.'/'.$i['name'], $view->filemtime($pathDir));
949 949
 				}
950 950
 			}
951 951
 		} else {
@@ -967,17 +967,17 @@  discard block
 block discarded – undo
967 967
 	 * @return array
968 968
 	 */
969 969
 	private static function getVersionsFromTrash($filename, $timestamp, $user) {
970
-		$view = new View('/' . $user . '/files_trashbin/versions');
970
+		$view = new View('/'.$user.'/files_trashbin/versions');
971 971
 		$versions = [];
972 972
 
973 973
 		/** @var \OC\Files\Storage\Storage $storage */
974
-		[$storage,] = $view->resolvePath('/');
974
+		[$storage, ] = $view->resolvePath('/');
975 975
 
976 976
 		$pattern = \OC::$server->getDatabaseConnection()->escapeLikeParameter(basename($filename));
977 977
 		if ($timestamp) {
978 978
 			// fetch for old versions
979 979
 			$escapedTimestamp = \OC::$server->getDatabaseConnection()->escapeLikeParameter($timestamp);
980
-			$pattern .= '.v%.d' . $escapedTimestamp;
980
+			$pattern .= '.v%.d'.$escapedTimestamp;
981 981
 			$offset = -strlen($escapedTimestamp) - 2;
982 982
 		} else {
983 983
 			$pattern .= '.v%';
@@ -990,7 +990,7 @@  discard block
 block discarded – undo
990 990
 			\OC::$server->getSystemConfig(),
991 991
 			\OC::$server->get(LoggerInterface::class)
992 992
 		);
993
-		$normalizedParentPath = ltrim(Filesystem::normalizePath(dirname('files_trashbin/versions/'. $filename)), '/');
993
+		$normalizedParentPath = ltrim(Filesystem::normalizePath(dirname('files_trashbin/versions/'.$filename)), '/');
994 994
 		$parentId = $cache->getId($normalizedParentPath);
995 995
 		if ($parentId === -1) {
996 996
 			return [];
@@ -1006,7 +1006,7 @@  discard block
 block discarded – undo
1006 1006
 		$result->closeCursor();
1007 1007
 
1008 1008
 		/** @var CacheEntry[] $matches */
1009
-		$matches = array_map(function (array $data) {
1009
+		$matches = array_map(function(array $data) {
1010 1010
 			return Cache::cacheEntryFromData($data, \OC::$server->getMimeTypeLoader());
1011 1011
 		}, $entries);
1012 1012
 
@@ -1036,18 +1036,18 @@  discard block
 block discarded – undo
1036 1036
 		$name = pathinfo($filename, PATHINFO_FILENAME);
1037 1037
 		$l = \OC::$server->getL10N('files_trashbin');
1038 1038
 
1039
-		$location = '/' . trim($location, '/');
1039
+		$location = '/'.trim($location, '/');
1040 1040
 
1041 1041
 		// if extension is not empty we set a dot in front of it
1042 1042
 		if ($ext !== '') {
1043
-			$ext = '.' . $ext;
1043
+			$ext = '.'.$ext;
1044 1044
 		}
1045 1045
 
1046
-		if ($view->file_exists('files' . $location . '/' . $filename)) {
1046
+		if ($view->file_exists('files'.$location.'/'.$filename)) {
1047 1047
 			$i = 2;
1048
-			$uniqueName = $name . " (" . $l->t("restored") . ")" . $ext;
1049
-			while ($view->file_exists('files' . $location . '/' . $uniqueName)) {
1050
-				$uniqueName = $name . " (" . $l->t("restored") . " " . $i . ")" . $ext;
1048
+			$uniqueName = $name." (".$l->t("restored").")".$ext;
1049
+			while ($view->file_exists('files'.$location.'/'.$uniqueName)) {
1050
+				$uniqueName = $name." (".$l->t("restored")." ".$i.")".$ext;
1051 1051
 				$i++;
1052 1052
 			}
1053 1053
 
@@ -1064,7 +1064,7 @@  discard block
 block discarded – undo
1064 1064
 	 * @return integer size of the folder
1065 1065
 	 */
1066 1066
 	private static function calculateSize($view) {
1067
-		$root = \OC::$server->getConfig()->getSystemValue('datadirectory', \OC::$SERVERROOT . '/data') . $view->getAbsolutePath('');
1067
+		$root = \OC::$server->getConfig()->getSystemValue('datadirectory', \OC::$SERVERROOT.'/data').$view->getAbsolutePath('');
1068 1068
 		if (!file_exists($root)) {
1069 1069
 			return 0;
1070 1070
 		}
@@ -1095,7 +1095,7 @@  discard block
 block discarded – undo
1095 1095
 	 * @return integer trash bin size
1096 1096
 	 */
1097 1097
 	private static function getTrashbinSize($user) {
1098
-		$view = new View('/' . $user);
1098
+		$view = new View('/'.$user);
1099 1099
 		$fileInfo = $view->getFileInfo('/files_trashbin');
1100 1100
 		return isset($fileInfo['size']) ? $fileInfo['size'] : 0;
1101 1101
 	}
@@ -1107,7 +1107,7 @@  discard block
 block discarded – undo
1107 1107
 	 * @return bool
1108 1108
 	 */
1109 1109
 	public static function isEmpty($user) {
1110
-		$view = new View('/' . $user . '/files_trashbin');
1110
+		$view = new View('/'.$user.'/files_trashbin');
1111 1111
 		if ($view->is_dir('/files') && $dh = $view->opendir('/files')) {
1112 1112
 			while ($file = readdir($dh)) {
1113 1113
 				if (!Filesystem::isIgnoredDir($file)) {
@@ -1130,7 +1130,7 @@  discard block
 block discarded – undo
1130 1130
 	 * Return the filename used in the trash bin
1131 1131
 	 */
1132 1132
 	public static function getTrashFilename(string $filename, int $timestamp): string {
1133
-		$trashFilename = $filename . '.d' . $timestamp;
1133
+		$trashFilename = $filename.'.d'.$timestamp;
1134 1134
 		$length = strlen($trashFilename);
1135 1135
 		// oc_filecache `name` column has a limit of 250 chars
1136 1136
 		$maxLength = 250;
Please login to merge, or discard this patch.
apps/files_trashbin/lib/Sabre/TrashFile.php 1 patch
Indentation   +6 added lines, -6 removed lines patch added patch discarded remove patch
@@ -29,11 +29,11 @@
 block discarded – undo
29 29
 use OCA\Files_Trashbin\Trashbin;
30 30
 
31 31
 class TrashFile extends AbstractTrashFile {
32
-	public function get() {
33
-		return $this->data->getStorage()->fopen(Trashbin::getTrashFilename($this->data->getInternalPath(), $this->getLastModified()), 'rb');
34
-	}
32
+    public function get() {
33
+        return $this->data->getStorage()->fopen(Trashbin::getTrashFilename($this->data->getInternalPath(), $this->getLastModified()), 'rb');
34
+    }
35 35
 
36
-	public function getName(): string {
37
-		return Trashbin::getTrashFilename($this->data->getName(), $this->getLastModified());
38
-	}
36
+    public function getName(): string {
37
+        return Trashbin::getTrashFilename($this->data->getName(), $this->getLastModified());
38
+    }
39 39
 }
Please login to merge, or discard this patch.
apps/files_trashbin/lib/Sabre/TrashFolder.php 1 patch
Indentation   +3 added lines, -3 removed lines patch added patch discarded remove patch
@@ -29,7 +29,7 @@
 block discarded – undo
29 29
 use OCA\Files_Trashbin\Trashbin;
30 30
 
31 31
 class TrashFolder extends AbstractTrashFolder {
32
-	public function getName(): string {
33
-		return Trashbin::getTrashFilename($this->data->getName(), $this->getLastModified());
34
-	}
32
+    public function getName(): string {
33
+        return Trashbin::getTrashFilename($this->data->getName(), $this->getLastModified());
34
+    }
35 35
 }
Please login to merge, or discard this patch.
apps/files_trashbin/lib/Trash/LegacyTrashBackend.php 2 patches
Indentation   +86 added lines, -86 removed lines patch added patch discarded remove patch
@@ -34,100 +34,100 @@
 block discarded – undo
34 34
 use OCP\IUser;
35 35
 
36 36
 class LegacyTrashBackend implements ITrashBackend {
37
-	/** @var array */
38
-	private $deletedFiles = [];
37
+    /** @var array */
38
+    private $deletedFiles = [];
39 39
 
40
-	/** @var IRootFolder */
41
-	private $rootFolder;
40
+    /** @var IRootFolder */
41
+    private $rootFolder;
42 42
 
43
-	public function __construct(IRootFolder $rootFolder) {
44
-		$this->rootFolder = $rootFolder;
45
-	}
43
+    public function __construct(IRootFolder $rootFolder) {
44
+        $this->rootFolder = $rootFolder;
45
+    }
46 46
 
47
-	/**
48
-	 * @param array $items
49
-	 * @param IUser $user
50
-	 * @param ITrashItem $parent
51
-	 * @return ITrashItem[]
52
-	 */
53
-	private function mapTrashItems(array $items, IUser $user, ITrashItem $parent = null): array {
54
-		$parentTrashPath = ($parent instanceof ITrashItem) ? $parent->getTrashPath() : '';
55
-		$isRoot = $parent === null;
56
-		return array_map(function (FileInfo $file) use ($parent, $parentTrashPath, $isRoot, $user) {
57
-			$originalLocation = $isRoot ? $file['extraData'] : $parent->getOriginalLocation() . '/' . $file->getName();
58
-			if (!$originalLocation) {
59
-				$originalLocation = $file->getName();
60
-			}
61
-			$trashFilename = Trashbin::getTrashFilename($file->getName(), $file->getMtime());
62
-			return new TrashItem(
63
-				$this,
64
-				$originalLocation,
65
-				$file->getMTime(),
66
-				$parentTrashPath . '/' . ($isRoot ? $trashFilename : $file->getName()),
67
-				$file,
68
-				$user
69
-			);
70
-		}, $items);
71
-	}
47
+    /**
48
+     * @param array $items
49
+     * @param IUser $user
50
+     * @param ITrashItem $parent
51
+     * @return ITrashItem[]
52
+     */
53
+    private function mapTrashItems(array $items, IUser $user, ITrashItem $parent = null): array {
54
+        $parentTrashPath = ($parent instanceof ITrashItem) ? $parent->getTrashPath() : '';
55
+        $isRoot = $parent === null;
56
+        return array_map(function (FileInfo $file) use ($parent, $parentTrashPath, $isRoot, $user) {
57
+            $originalLocation = $isRoot ? $file['extraData'] : $parent->getOriginalLocation() . '/' . $file->getName();
58
+            if (!$originalLocation) {
59
+                $originalLocation = $file->getName();
60
+            }
61
+            $trashFilename = Trashbin::getTrashFilename($file->getName(), $file->getMtime());
62
+            return new TrashItem(
63
+                $this,
64
+                $originalLocation,
65
+                $file->getMTime(),
66
+                $parentTrashPath . '/' . ($isRoot ? $trashFilename : $file->getName()),
67
+                $file,
68
+                $user
69
+            );
70
+        }, $items);
71
+    }
72 72
 
73
-	public function listTrashRoot(IUser $user): array {
74
-		$entries = Helper::getTrashFiles('/', $user->getUID());
75
-		return $this->mapTrashItems($entries, $user);
76
-	}
73
+    public function listTrashRoot(IUser $user): array {
74
+        $entries = Helper::getTrashFiles('/', $user->getUID());
75
+        return $this->mapTrashItems($entries, $user);
76
+    }
77 77
 
78
-	public function listTrashFolder(ITrashItem $folder): array {
79
-		$user = $folder->getUser();
80
-		$entries = Helper::getTrashFiles($folder->getTrashPath(), $user->getUID());
81
-		return $this->mapTrashItems($entries, $user ,$folder);
82
-	}
78
+    public function listTrashFolder(ITrashItem $folder): array {
79
+        $user = $folder->getUser();
80
+        $entries = Helper::getTrashFiles($folder->getTrashPath(), $user->getUID());
81
+        return $this->mapTrashItems($entries, $user ,$folder);
82
+    }
83 83
 
84
-	public function restoreItem(ITrashItem $item) {
85
-		Trashbin::restore($item->getTrashPath(), $item->getName(), $item->isRootItem() ? $item->getDeletedTime() : null);
86
-	}
84
+    public function restoreItem(ITrashItem $item) {
85
+        Trashbin::restore($item->getTrashPath(), $item->getName(), $item->isRootItem() ? $item->getDeletedTime() : null);
86
+    }
87 87
 
88
-	public function removeItem(ITrashItem $item) {
89
-		$user = $item->getUser();
90
-		if ($item->isRootItem()) {
91
-			$path = substr($item->getTrashPath(), 0, -strlen('.d' . $item->getDeletedTime()));
92
-			Trashbin::delete($path, $user->getUID(), $item->getDeletedTime());
93
-		} else {
94
-			Trashbin::delete($item->getTrashPath(), $user->getUID(), null);
95
-		}
96
-	}
88
+    public function removeItem(ITrashItem $item) {
89
+        $user = $item->getUser();
90
+        if ($item->isRootItem()) {
91
+            $path = substr($item->getTrashPath(), 0, -strlen('.d' . $item->getDeletedTime()));
92
+            Trashbin::delete($path, $user->getUID(), $item->getDeletedTime());
93
+        } else {
94
+            Trashbin::delete($item->getTrashPath(), $user->getUID(), null);
95
+        }
96
+    }
97 97
 
98
-	public function moveToTrash(IStorage $storage, string $internalPath): bool {
99
-		if (!$storage instanceof Storage) {
100
-			return false;
101
-		}
102
-		$normalized = Filesystem::normalizePath($storage->getMountPoint() . '/' . $internalPath, true, false, true);
103
-		$view = Filesystem::getView();
104
-		if (!isset($this->deletedFiles[$normalized]) && $view instanceof View) {
105
-			$this->deletedFiles[$normalized] = $normalized;
106
-			if ($filesPath = $view->getRelativePath($normalized)) {
107
-				$filesPath = trim($filesPath, '/');
108
-				$result = \OCA\Files_Trashbin\Trashbin::move2trash($filesPath);
109
-			} else {
110
-				$result = false;
111
-			}
112
-			unset($this->deletedFiles[$normalized]);
113
-		} else {
114
-			$result = false;
115
-		}
98
+    public function moveToTrash(IStorage $storage, string $internalPath): bool {
99
+        if (!$storage instanceof Storage) {
100
+            return false;
101
+        }
102
+        $normalized = Filesystem::normalizePath($storage->getMountPoint() . '/' . $internalPath, true, false, true);
103
+        $view = Filesystem::getView();
104
+        if (!isset($this->deletedFiles[$normalized]) && $view instanceof View) {
105
+            $this->deletedFiles[$normalized] = $normalized;
106
+            if ($filesPath = $view->getRelativePath($normalized)) {
107
+                $filesPath = trim($filesPath, '/');
108
+                $result = \OCA\Files_Trashbin\Trashbin::move2trash($filesPath);
109
+            } else {
110
+                $result = false;
111
+            }
112
+            unset($this->deletedFiles[$normalized]);
113
+        } else {
114
+            $result = false;
115
+        }
116 116
 
117
-		return $result;
118
-	}
117
+        return $result;
118
+    }
119 119
 
120
-	public function getTrashNodeById(IUser $user, int $fileId) {
121
-		try {
122
-			$userFolder = $this->rootFolder->getUserFolder($user->getUID());
123
-			$trash = $userFolder->getParent()->get('files_trashbin/files');
124
-			$trashFiles = $trash->getById($fileId);
125
-			if (!$trashFiles) {
126
-				return null;
127
-			}
128
-			return $trashFiles ? array_pop($trashFiles) : null;
129
-		} catch (NotFoundException $e) {
130
-			return null;
131
-		}
132
-	}
120
+    public function getTrashNodeById(IUser $user, int $fileId) {
121
+        try {
122
+            $userFolder = $this->rootFolder->getUserFolder($user->getUID());
123
+            $trash = $userFolder->getParent()->get('files_trashbin/files');
124
+            $trashFiles = $trash->getById($fileId);
125
+            if (!$trashFiles) {
126
+                return null;
127
+            }
128
+            return $trashFiles ? array_pop($trashFiles) : null;
129
+        } catch (NotFoundException $e) {
130
+            return null;
131
+        }
132
+    }
133 133
 }
Please login to merge, or discard this patch.
Spacing   +6 added lines, -6 removed lines patch added patch discarded remove patch
@@ -53,8 +53,8 @@  discard block
 block discarded – undo
53 53
 	private function mapTrashItems(array $items, IUser $user, ITrashItem $parent = null): array {
54 54
 		$parentTrashPath = ($parent instanceof ITrashItem) ? $parent->getTrashPath() : '';
55 55
 		$isRoot = $parent === null;
56
-		return array_map(function (FileInfo $file) use ($parent, $parentTrashPath, $isRoot, $user) {
57
-			$originalLocation = $isRoot ? $file['extraData'] : $parent->getOriginalLocation() . '/' . $file->getName();
56
+		return array_map(function(FileInfo $file) use ($parent, $parentTrashPath, $isRoot, $user) {
57
+			$originalLocation = $isRoot ? $file['extraData'] : $parent->getOriginalLocation().'/'.$file->getName();
58 58
 			if (!$originalLocation) {
59 59
 				$originalLocation = $file->getName();
60 60
 			}
@@ -63,7 +63,7 @@  discard block
 block discarded – undo
63 63
 				$this,
64 64
 				$originalLocation,
65 65
 				$file->getMTime(),
66
-				$parentTrashPath . '/' . ($isRoot ? $trashFilename : $file->getName()),
66
+				$parentTrashPath.'/'.($isRoot ? $trashFilename : $file->getName()),
67 67
 				$file,
68 68
 				$user
69 69
 			);
@@ -78,7 +78,7 @@  discard block
 block discarded – undo
78 78
 	public function listTrashFolder(ITrashItem $folder): array {
79 79
 		$user = $folder->getUser();
80 80
 		$entries = Helper::getTrashFiles($folder->getTrashPath(), $user->getUID());
81
-		return $this->mapTrashItems($entries, $user ,$folder);
81
+		return $this->mapTrashItems($entries, $user, $folder);
82 82
 	}
83 83
 
84 84
 	public function restoreItem(ITrashItem $item) {
@@ -88,7 +88,7 @@  discard block
 block discarded – undo
88 88
 	public function removeItem(ITrashItem $item) {
89 89
 		$user = $item->getUser();
90 90
 		if ($item->isRootItem()) {
91
-			$path = substr($item->getTrashPath(), 0, -strlen('.d' . $item->getDeletedTime()));
91
+			$path = substr($item->getTrashPath(), 0, -strlen('.d'.$item->getDeletedTime()));
92 92
 			Trashbin::delete($path, $user->getUID(), $item->getDeletedTime());
93 93
 		} else {
94 94
 			Trashbin::delete($item->getTrashPath(), $user->getUID(), null);
@@ -99,7 +99,7 @@  discard block
 block discarded – undo
99 99
 		if (!$storage instanceof Storage) {
100 100
 			return false;
101 101
 		}
102
-		$normalized = Filesystem::normalizePath($storage->getMountPoint() . '/' . $internalPath, true, false, true);
102
+		$normalized = Filesystem::normalizePath($storage->getMountPoint().'/'.$internalPath, true, false, true);
103 103
 		$view = Filesystem::getView();
104 104
 		if (!isset($this->deletedFiles[$normalized]) && $view instanceof View) {
105 105
 			$this->deletedFiles[$normalized] = $normalized;
Please login to merge, or discard this patch.