Completed
Push — master ( dde668...3f9849 )
by Robin
30:36
created
lib/private/Files/Config/UserMountCache.php 1 patch
Indentation   +535 added lines, -535 removed lines patch added patch discarded remove patch
@@ -32,539 +32,539 @@
 block discarded – undo
32 32
  */
33 33
 class UserMountCache implements IUserMountCache {
34 34
 
35
-	/**
36
-	 * Cached mount info.
37
-	 * @var CappedMemoryCache<ICachedMountInfo[]>
38
-	 **/
39
-	private CappedMemoryCache $mountsForUsers;
40
-	/**
41
-	 * fileid => internal path mapping for cached mount info.
42
-	 * @var CappedMemoryCache<string>
43
-	 **/
44
-	private CappedMemoryCache $internalPathCache;
45
-	/** @var CappedMemoryCache<array> */
46
-	private CappedMemoryCache $cacheInfoCache;
47
-
48
-	/**
49
-	 * UserMountCache constructor.
50
-	 */
51
-	public function __construct(
52
-		private IDBConnection $connection,
53
-		private IUserManager $userManager,
54
-		private LoggerInterface $logger,
55
-		private IEventLogger $eventLogger,
56
-		private IEventDispatcher $eventDispatcher,
57
-	) {
58
-		$this->cacheInfoCache = new CappedMemoryCache();
59
-		$this->internalPathCache = new CappedMemoryCache();
60
-		$this->mountsForUsers = new CappedMemoryCache();
61
-	}
62
-
63
-	public function registerMounts(IUser $user, array $mounts, ?array $mountProviderClasses = null) {
64
-		$this->eventLogger->start('fs:setup:user:register', 'Registering mounts for user');
65
-		/** @var array<string, ICachedMountInfo> $newMounts */
66
-		$newMounts = [];
67
-		foreach ($mounts as $mount) {
68
-			// filter out any storages which aren't scanned yet since we aren't interested in files from those storages (yet)
69
-			if ($mount->getStorageRootId() !== -1) {
70
-				$mountInfo = new LazyStorageMountInfo($user, $mount);
71
-				$newMounts[$mountInfo->getKey()] = $mountInfo;
72
-			}
73
-		}
74
-
75
-		$cachedMounts = $this->getMountsForUser($user);
76
-		if (is_array($mountProviderClasses)) {
77
-			$cachedMounts = array_filter($cachedMounts, function (ICachedMountInfo $mountInfo) use ($mountProviderClasses, $newMounts) {
78
-				// for existing mounts that didn't have a mount provider set
79
-				// we still want the ones that map to new mounts
80
-				if ($mountInfo->getMountProvider() === '' && isset($newMounts[$mountInfo->getKey()])) {
81
-					return true;
82
-				}
83
-				return in_array($mountInfo->getMountProvider(), $mountProviderClasses);
84
-			});
85
-		}
86
-
87
-		$addedMounts = [];
88
-		$removedMounts = [];
89
-
90
-		foreach ($newMounts as $mountKey => $newMount) {
91
-			if (!isset($cachedMounts[$mountKey])) {
92
-				$addedMounts[] = $newMount;
93
-			}
94
-		}
95
-
96
-		foreach ($cachedMounts as $mountKey => $cachedMount) {
97
-			if (!isset($newMounts[$mountKey])) {
98
-				$removedMounts[] = $cachedMount;
99
-			}
100
-		}
101
-
102
-		$changedMounts = $this->findChangedMounts($newMounts, $cachedMounts);
103
-
104
-		if ($addedMounts || $removedMounts || $changedMounts) {
105
-			$this->connection->beginTransaction();
106
-			$userUID = $user->getUID();
107
-			try {
108
-				foreach ($addedMounts as $mount) {
109
-					$this->logger->debug("Adding mount '{$mount->getKey()}' for user '$userUID'", ['app' => 'files', 'mount_provider' => $mount->getMountProvider()]);
110
-					$this->addToCache($mount);
111
-					/** @psalm-suppress InvalidArgument */
112
-					$this->mountsForUsers[$userUID][$mount->getKey()] = $mount;
113
-				}
114
-				foreach ($removedMounts as $mount) {
115
-					$this->logger->debug("Removing mount '{$mount->getKey()}' for user '$userUID'", ['app' => 'files', 'mount_provider' => $mount->getMountProvider()]);
116
-					$this->removeFromCache($mount);
117
-					unset($this->mountsForUsers[$userUID][$mount->getKey()]);
118
-				}
119
-				foreach ($changedMounts as $mountPair) {
120
-					$newMount = $mountPair[1];
121
-					$this->logger->debug("Updating mount '{$newMount->getKey()}' for user '$userUID'", ['app' => 'files', 'mount_provider' => $newMount->getMountProvider()]);
122
-					$this->updateCachedMount($newMount);
123
-					/** @psalm-suppress InvalidArgument */
124
-					$this->mountsForUsers[$userUID][$newMount->getKey()] = $newMount;
125
-				}
126
-				$this->connection->commit();
127
-			} catch (\Throwable $e) {
128
-				$this->connection->rollBack();
129
-				throw $e;
130
-			}
131
-
132
-			// Only fire events after all mounts have already been adjusted in the database.
133
-			foreach ($addedMounts as $mount) {
134
-				$this->eventDispatcher->dispatchTyped(new UserMountAddedEvent($mount));
135
-			}
136
-			foreach ($removedMounts as $mount) {
137
-				$this->eventDispatcher->dispatchTyped(new UserMountRemovedEvent($mount));
138
-			}
139
-			foreach ($changedMounts as $mountPair) {
140
-				$this->eventDispatcher->dispatchTyped(new UserMountUpdatedEvent($mountPair[0], $mountPair[1]));
141
-			}
142
-		}
143
-		$this->eventLogger->end('fs:setup:user:register');
144
-	}
145
-
146
-	/**
147
-	 * @param array<string, ICachedMountInfo> $newMounts
148
-	 * @param array<string, ICachedMountInfo> $cachedMounts
149
-	 * @return list<list{0: ICachedMountInfo, 1: ICachedMountInfo}> Pairs of old and new mounts
150
-	 */
151
-	private function findChangedMounts(array $newMounts, array $cachedMounts): array {
152
-		$changed = [];
153
-		foreach ($cachedMounts as $key => $cachedMount) {
154
-			if (isset($newMounts[$key])) {
155
-				$newMount = $newMounts[$key];
156
-				if (
157
-					$newMount->getStorageId() !== $cachedMount->getStorageId()
158
-					|| $newMount->getMountId() !== $cachedMount->getMountId()
159
-					|| $newMount->getMountProvider() !== $cachedMount->getMountProvider()
160
-				) {
161
-					$changed[] = [$cachedMount, $newMount];
162
-				}
163
-			}
164
-		}
165
-		return $changed;
166
-	}
167
-
168
-	private function addToCache(ICachedMountInfo $mount) {
169
-		if ($mount->getStorageId() !== -1) {
170
-			$qb = $this->connection->getQueryBuilder();
171
-			$qb
172
-				->insert('mounts')
173
-				->values([
174
-					'storage_id' => $qb->createNamedParameter($mount->getStorageId(), IQueryBuilder::PARAM_INT),
175
-					'root_id' => $qb->createNamedParameter($mount->getRootId(), IQueryBuilder::PARAM_INT),
176
-					'user_id' => $qb->createNamedParameter($mount->getUser()->getUID()),
177
-					'mount_point' => $qb->createNamedParameter($mount->getMountPoint()),
178
-					'mount_point_hash' => $qb->createNamedParameter(hash('xxh128', $mount->getMountPoint())),
179
-					'mount_id' => $qb->createNamedParameter($mount->getMountId(), IQueryBuilder::PARAM_INT),
180
-					'mount_provider_class' => $qb->createNamedParameter($mount->getMountProvider()),
181
-				]);
182
-			try {
183
-				$qb->executeStatement();
184
-			} catch (Exception $e) {
185
-				if ($e->getReason() !== Exception::REASON_UNIQUE_CONSTRAINT_VIOLATION) {
186
-					throw $e;
187
-				}
188
-			}
189
-		} else {
190
-			// in some cases this is legitimate, like orphaned shares
191
-			$this->logger->debug('Could not get storage info for mount at ' . $mount->getMountPoint());
192
-		}
193
-	}
194
-
195
-	private function updateCachedMount(ICachedMountInfo $mount) {
196
-		$builder = $this->connection->getQueryBuilder();
197
-
198
-		$query = $builder->update('mounts')
199
-			->set('storage_id', $builder->createNamedParameter($mount->getStorageId()))
200
-			->set('mount_point', $builder->createNamedParameter($mount->getMountPoint()))
201
-			->set('mount_point_hash', $builder->createNamedParameter(hash('xxh128', $mount->getMountPoint())))
202
-			->set('mount_id', $builder->createNamedParameter($mount->getMountId(), IQueryBuilder::PARAM_INT))
203
-			->set('mount_provider_class', $builder->createNamedParameter($mount->getMountProvider()))
204
-			->where($builder->expr()->eq('user_id', $builder->createNamedParameter($mount->getUser()->getUID())))
205
-			->andWhere($builder->expr()->eq('root_id', $builder->createNamedParameter($mount->getRootId(), IQueryBuilder::PARAM_INT)));
206
-
207
-		$query->executeStatement();
208
-	}
209
-
210
-	private function removeFromCache(ICachedMountInfo $mount) {
211
-		$builder = $this->connection->getQueryBuilder();
212
-
213
-		$query = $builder->delete('mounts')
214
-			->where($builder->expr()->eq('user_id', $builder->createNamedParameter($mount->getUser()->getUID())))
215
-			->andWhere($builder->expr()->eq('root_id', $builder->createNamedParameter($mount->getRootId(), IQueryBuilder::PARAM_INT)))
216
-			->andWhere($builder->expr()->eq('mount_point_hash', $builder->createNamedParameter(hash('xxh128', $mount->getMountPoint()))));
217
-		$query->executeStatement();
218
-	}
219
-
220
-	/**
221
-	 * @param array $row
222
-	 * @param (callable(CachedMountInfo): string)|null $pathCallback
223
-	 * @return CachedMountInfo
224
-	 */
225
-	private function dbRowToMountInfo(array $row, ?callable $pathCallback = null): ICachedMountInfo {
226
-		$user = new LazyUser($row['user_id'], $this->userManager);
227
-		$mount_id = $row['mount_id'];
228
-		if (!is_null($mount_id)) {
229
-			$mount_id = (int)$mount_id;
230
-		}
231
-		if ($pathCallback) {
232
-			return new LazyPathCachedMountInfo(
233
-				$user,
234
-				(int)$row['storage_id'],
235
-				(int)$row['root_id'],
236
-				$row['mount_point'],
237
-				$row['mount_provider_class'] ?? '',
238
-				$mount_id,
239
-				$pathCallback,
240
-			);
241
-		} else {
242
-			return new CachedMountInfo(
243
-				$user,
244
-				(int)$row['storage_id'],
245
-				(int)$row['root_id'],
246
-				$row['mount_point'],
247
-				$row['mount_provider_class'] ?? '',
248
-				$mount_id,
249
-				$row['path'] ?? '',
250
-			);
251
-		}
252
-	}
253
-
254
-	/**
255
-	 * @param IUser $user
256
-	 * @return ICachedMountInfo[]
257
-	 */
258
-	public function getMountsForUser(IUser $user) {
259
-		$userUID = $user->getUID();
260
-		if (!$this->userManager->userExists($userUID)) {
261
-			return [];
262
-		}
263
-		if (!isset($this->mountsForUsers[$userUID])) {
264
-			$builder = $this->connection->getQueryBuilder();
265
-			$query = $builder->select('storage_id', 'root_id', 'user_id', 'mount_point', 'mount_id', 'mount_provider_class')
266
-				->from('mounts', 'm')
267
-				->where($builder->expr()->eq('user_id', $builder->createNamedParameter($userUID)));
268
-
269
-			$result = $query->executeQuery();
270
-			$rows = $result->fetchAll();
271
-			$result->closeCursor();
272
-
273
-			/** @var array<string, ICachedMountInfo> $mounts */
274
-			$mounts = [];
275
-			foreach ($rows as $row) {
276
-				$mount = $this->dbRowToMountInfo($row, [$this, 'getInternalPathForMountInfo']);
277
-				if ($mount !== null) {
278
-					$mounts[$mount->getKey()] = $mount;
279
-				}
280
-			}
281
-			$this->mountsForUsers[$userUID] = $mounts;
282
-		}
283
-		return $this->mountsForUsers[$userUID];
284
-	}
285
-
286
-	public function getInternalPathForMountInfo(CachedMountInfo $info): string {
287
-		$cached = $this->internalPathCache->get($info->getRootId());
288
-		if ($cached !== null) {
289
-			return $cached;
290
-		}
291
-		$builder = $this->connection->getQueryBuilder();
292
-		$query = $builder->select('path')
293
-			->from('filecache')
294
-			->where($builder->expr()->eq('fileid', $builder->createNamedParameter($info->getRootId())));
295
-		return $query->executeQuery()->fetchOne() ?: '';
296
-	}
297
-
298
-	/**
299
-	 * @param int $numericStorageId
300
-	 * @param string|null $user limit the results to a single user
301
-	 * @return CachedMountInfo[]
302
-	 */
303
-	public function getMountsForStorageId($numericStorageId, $user = null) {
304
-		$builder = $this->connection->getQueryBuilder();
305
-		$query = $builder->select('storage_id', 'root_id', 'user_id', 'mount_point', 'mount_id', 'f.path', 'mount_provider_class')
306
-			->from('mounts', 'm')
307
-			->innerJoin('m', 'filecache', 'f', $builder->expr()->eq('m.root_id', 'f.fileid'))
308
-			->where($builder->expr()->eq('storage_id', $builder->createNamedParameter($numericStorageId, IQueryBuilder::PARAM_INT)));
309
-
310
-		if ($user) {
311
-			$query->andWhere($builder->expr()->eq('user_id', $builder->createNamedParameter($user)));
312
-		}
313
-
314
-		$result = $query->executeQuery();
315
-		$rows = $result->fetchAll();
316
-		$result->closeCursor();
317
-
318
-		return array_filter(array_map([$this, 'dbRowToMountInfo'], $rows));
319
-	}
320
-
321
-	/**
322
-	 * @param int $rootFileId
323
-	 * @return CachedMountInfo[]
324
-	 */
325
-	public function getMountsForRootId($rootFileId) {
326
-		$builder = $this->connection->getQueryBuilder();
327
-		$query = $builder->select('storage_id', 'root_id', 'user_id', 'mount_point', 'mount_id', 'f.path', 'mount_provider_class')
328
-			->from('mounts', 'm')
329
-			->innerJoin('m', 'filecache', 'f', $builder->expr()->eq('m.root_id', 'f.fileid'))
330
-			->where($builder->expr()->eq('root_id', $builder->createNamedParameter($rootFileId, IQueryBuilder::PARAM_INT)));
331
-
332
-		$result = $query->executeQuery();
333
-		$rows = $result->fetchAll();
334
-		$result->closeCursor();
335
-
336
-		return array_filter(array_map([$this, 'dbRowToMountInfo'], $rows));
337
-	}
338
-
339
-	/**
340
-	 * @param $fileId
341
-	 * @return array{int, string, int}
342
-	 * @throws \OCP\Files\NotFoundException
343
-	 */
344
-	private function getCacheInfoFromFileId($fileId): array {
345
-		if (!isset($this->cacheInfoCache[$fileId])) {
346
-			$builder = $this->connection->getQueryBuilder();
347
-			$query = $builder->select('storage', 'path', 'mimetype')
348
-				->from('filecache')
349
-				->where($builder->expr()->eq('fileid', $builder->createNamedParameter($fileId, IQueryBuilder::PARAM_INT)));
350
-
351
-			$result = $query->executeQuery();
352
-			$row = $result->fetch();
353
-			$result->closeCursor();
354
-
355
-			if (is_array($row)) {
356
-				$this->cacheInfoCache[$fileId] = [
357
-					(int)$row['storage'],
358
-					(string)$row['path'],
359
-					(int)$row['mimetype']
360
-				];
361
-			} else {
362
-				throw new NotFoundException('File with id "' . $fileId . '" not found');
363
-			}
364
-		}
365
-		return $this->cacheInfoCache[$fileId];
366
-	}
367
-
368
-	/**
369
-	 * @param int $fileId
370
-	 * @param string|null $user optionally restrict the results to a single user
371
-	 * @return ICachedMountFileInfo[]
372
-	 * @since 9.0.0
373
-	 */
374
-	public function getMountsForFileId($fileId, $user = null) {
375
-		try {
376
-			[$storageId, $internalPath] = $this->getCacheInfoFromFileId($fileId);
377
-		} catch (NotFoundException $e) {
378
-			return [];
379
-		}
380
-
381
-		$builder = $this->connection->getQueryBuilder();
382
-		$query = $builder->select('storage_id', 'root_id', 'user_id', 'mount_point', 'mount_id', 'f.path', 'mount_provider_class')
383
-			->from('mounts', 'm')
384
-			->innerJoin('m', 'filecache', 'f', $builder->expr()->eq('m.root_id', 'f.fileid'))
385
-			->where($builder->expr()->eq('storage_id', $builder->createNamedParameter($storageId, IQueryBuilder::PARAM_INT)))
386
-			->andWhere(
387
-				$builder->expr()->orX(
388
-					$builder->expr()->eq('f.fileid', $builder->createNamedParameter($fileId)),
389
-					$builder->expr()->emptyString('f.path'),
390
-					$builder->expr()->eq(
391
-						$builder->func()->concat('f.path', $builder->createNamedParameter('/')),
392
-						$builder->func()->substring(
393
-							$builder->createNamedParameter($internalPath),
394
-							$builder->createNamedParameter(1, IQueryBuilder::PARAM_INT),
395
-							$builder->func()->add(
396
-								$builder->func()->charLength('f.path'),
397
-								$builder->createNamedParameter(1, IQueryBuilder::PARAM_INT),
398
-							),
399
-						),
400
-					),
401
-				)
402
-			);
403
-
404
-		if ($user !== null) {
405
-			$query->andWhere($builder->expr()->eq('user_id', $builder->createNamedParameter($user)));
406
-		}
407
-		$result = $query->executeQuery();
408
-
409
-		$mounts = [];
410
-		while ($row = $result->fetch()) {
411
-			if ($user === null && !$this->userManager->userExists($row['user_id'])) {
412
-				continue;
413
-			}
414
-
415
-			$mounts[] = new CachedMountFileInfo(
416
-				new LazyUser($row['user_id'], $this->userManager),
417
-				(int)$row['storage_id'],
418
-				(int)$row['root_id'],
419
-				$row['mount_point'],
420
-				$row['mount_id'] === null ? null : (int)$row['mount_id'],
421
-				$row['mount_provider_class'] ?? '',
422
-				$row['path'] ?? '',
423
-				$internalPath,
424
-			);
425
-		}
426
-
427
-		return $mounts;
428
-	}
429
-
430
-	/**
431
-	 * Remove all cached mounts for a user
432
-	 *
433
-	 * @param IUser $user
434
-	 */
435
-	public function removeUserMounts(IUser $user) {
436
-		$builder = $this->connection->getQueryBuilder();
437
-
438
-		$query = $builder->delete('mounts')
439
-			->where($builder->expr()->eq('user_id', $builder->createNamedParameter($user->getUID())));
440
-		$query->executeStatement();
441
-	}
442
-
443
-	public function removeUserStorageMount($storageId, $userId) {
444
-		$builder = $this->connection->getQueryBuilder();
445
-
446
-		$query = $builder->delete('mounts')
447
-			->where($builder->expr()->eq('user_id', $builder->createNamedParameter($userId)))
448
-			->andWhere($builder->expr()->eq('storage_id', $builder->createNamedParameter($storageId, IQueryBuilder::PARAM_INT)));
449
-		$query->executeStatement();
450
-	}
451
-
452
-	public function remoteStorageMounts($storageId) {
453
-		$builder = $this->connection->getQueryBuilder();
454
-
455
-		$query = $builder->delete('mounts')
456
-			->where($builder->expr()->eq('storage_id', $builder->createNamedParameter($storageId, IQueryBuilder::PARAM_INT)));
457
-		$query->executeStatement();
458
-	}
459
-
460
-	/**
461
-	 * @param array $users
462
-	 * @return array
463
-	 */
464
-	public function getUsedSpaceForUsers(array $users) {
465
-		$builder = $this->connection->getQueryBuilder();
466
-
467
-		$mountPointHashes = array_map(static fn (IUser $user) => hash('xxh128', '/' . $user->getUID() . '/'), $users);
468
-		$userIds = array_map(static fn (IUser $user) => $user->getUID(), $users);
469
-
470
-		$query = $builder->select('m.user_id', 'f.size')
471
-			->from('mounts', 'm')
472
-			->innerJoin('m', 'filecache', 'f',
473
-				$builder->expr()->andX(
474
-					$builder->expr()->eq('m.storage_id', 'f.storage'),
475
-					$builder->expr()->eq('f.path_hash', $builder->createNamedParameter(md5('files')))
476
-				))
477
-			->where($builder->expr()->in('m.mount_point_hash', $builder->createNamedParameter($mountPointHashes, IQueryBuilder::PARAM_STR_ARRAY)))
478
-			->andWhere($builder->expr()->in('m.user_id', $builder->createNamedParameter($userIds, IQueryBuilder::PARAM_STR_ARRAY)));
479
-
480
-		$result = $query->executeQuery();
481
-
482
-		$results = [];
483
-		while ($row = $result->fetch()) {
484
-			$results[$row['user_id']] = $row['size'];
485
-		}
486
-		$result->closeCursor();
487
-		return $results;
488
-	}
489
-
490
-	public function clear(): void {
491
-		$this->cacheInfoCache = new CappedMemoryCache();
492
-		$this->mountsForUsers = new CappedMemoryCache();
493
-	}
494
-
495
-	public function getMountForPath(IUser $user, string $path): ICachedMountInfo {
496
-		$mounts = [];
497
-		foreach ($this->getMountsForUser($user) as $mount) {
498
-			$mounts[$mount->getMountPoint()] = $mount;
499
-		}
500
-
501
-		$current = rtrim($path, '/');
502
-		// walk up the directory tree until we find a path that has a mountpoint set
503
-		// the loop will return if a mountpoint is found or break if none are found
504
-		while (true) {
505
-			$mountPoint = $current . '/';
506
-			if (isset($mounts[$mountPoint])) {
507
-				return $mounts[$mountPoint];
508
-			} elseif ($current === '') {
509
-				break;
510
-			}
511
-
512
-			$current = dirname($current);
513
-			if ($current === '.' || $current === '/') {
514
-				$current = '';
515
-			}
516
-		}
517
-
518
-		throw new NotFoundException('No cached mount for path ' . $path);
519
-	}
520
-
521
-	public function getMountsInPath(IUser $user, string $path): array {
522
-		$path = rtrim($path, '/') . '/';
523
-		$result = [];
524
-		foreach ($this->getMountsForUser($user) as $key => $mount) {
525
-			$mountPoint = $mount->getMountPoint();
526
-			if ($mountPoint !== $path && str_starts_with($mountPoint, $path)) {
527
-				$result[$key] = $mount;
528
-			}
529
-		}
530
-		return $result;
531
-	}
532
-
533
-	public function removeMount(string $mountPoint): void {
534
-		$query = $this->connection->getQueryBuilder();
535
-		$query->delete('mounts')
536
-			->where($query->expr()->eq('mount_point', $query->createNamedParameter($mountPoint)));
537
-		$query->executeStatement();
538
-	}
539
-
540
-	public function addMount(IUser $user, string $mountPoint, ICacheEntry $rootCacheEntry, string $mountProvider, ?int $mountId = null): void {
541
-		$query = $this->connection->getQueryBuilder();
542
-		$query->insert('mounts')
543
-			->values([
544
-				'storage_id' => $query->createNamedParameter($rootCacheEntry->getStorageId()),
545
-				'root_id' => $query->createNamedParameter($rootCacheEntry->getId()),
546
-				'user_id' => $query->createNamedParameter($user->getUID()),
547
-				'mount_point' => $query->createNamedParameter($mountPoint),
548
-				'mount_point_hash' => $query->createNamedParameter(hash('xxh128', $mountPoint)),
549
-				'mount_id' => $query->createNamedParameter($mountId),
550
-				'mount_provider_class' => $query->createNamedParameter($mountProvider)
551
-			]);
552
-
553
-		try {
554
-			$query->executeStatement();
555
-		} catch (DbalException $e) {
556
-			if ($e->getReason() !== DbalException::REASON_UNIQUE_CONSTRAINT_VIOLATION) {
557
-				throw $e;
558
-			}
559
-		}
560
-	}
561
-
562
-	/**
563
-	 * Clear the internal in-memory caches
564
-	 */
565
-	public function flush(): void {
566
-		$this->cacheInfoCache = new CappedMemoryCache();
567
-		$this->internalPathCache = new CappedMemoryCache();
568
-		$this->mountsForUsers = new CappedMemoryCache();
569
-	}
35
+    /**
36
+     * Cached mount info.
37
+     * @var CappedMemoryCache<ICachedMountInfo[]>
38
+     **/
39
+    private CappedMemoryCache $mountsForUsers;
40
+    /**
41
+     * fileid => internal path mapping for cached mount info.
42
+     * @var CappedMemoryCache<string>
43
+     **/
44
+    private CappedMemoryCache $internalPathCache;
45
+    /** @var CappedMemoryCache<array> */
46
+    private CappedMemoryCache $cacheInfoCache;
47
+
48
+    /**
49
+     * UserMountCache constructor.
50
+     */
51
+    public function __construct(
52
+        private IDBConnection $connection,
53
+        private IUserManager $userManager,
54
+        private LoggerInterface $logger,
55
+        private IEventLogger $eventLogger,
56
+        private IEventDispatcher $eventDispatcher,
57
+    ) {
58
+        $this->cacheInfoCache = new CappedMemoryCache();
59
+        $this->internalPathCache = new CappedMemoryCache();
60
+        $this->mountsForUsers = new CappedMemoryCache();
61
+    }
62
+
63
+    public function registerMounts(IUser $user, array $mounts, ?array $mountProviderClasses = null) {
64
+        $this->eventLogger->start('fs:setup:user:register', 'Registering mounts for user');
65
+        /** @var array<string, ICachedMountInfo> $newMounts */
66
+        $newMounts = [];
67
+        foreach ($mounts as $mount) {
68
+            // filter out any storages which aren't scanned yet since we aren't interested in files from those storages (yet)
69
+            if ($mount->getStorageRootId() !== -1) {
70
+                $mountInfo = new LazyStorageMountInfo($user, $mount);
71
+                $newMounts[$mountInfo->getKey()] = $mountInfo;
72
+            }
73
+        }
74
+
75
+        $cachedMounts = $this->getMountsForUser($user);
76
+        if (is_array($mountProviderClasses)) {
77
+            $cachedMounts = array_filter($cachedMounts, function (ICachedMountInfo $mountInfo) use ($mountProviderClasses, $newMounts) {
78
+                // for existing mounts that didn't have a mount provider set
79
+                // we still want the ones that map to new mounts
80
+                if ($mountInfo->getMountProvider() === '' && isset($newMounts[$mountInfo->getKey()])) {
81
+                    return true;
82
+                }
83
+                return in_array($mountInfo->getMountProvider(), $mountProviderClasses);
84
+            });
85
+        }
86
+
87
+        $addedMounts = [];
88
+        $removedMounts = [];
89
+
90
+        foreach ($newMounts as $mountKey => $newMount) {
91
+            if (!isset($cachedMounts[$mountKey])) {
92
+                $addedMounts[] = $newMount;
93
+            }
94
+        }
95
+
96
+        foreach ($cachedMounts as $mountKey => $cachedMount) {
97
+            if (!isset($newMounts[$mountKey])) {
98
+                $removedMounts[] = $cachedMount;
99
+            }
100
+        }
101
+
102
+        $changedMounts = $this->findChangedMounts($newMounts, $cachedMounts);
103
+
104
+        if ($addedMounts || $removedMounts || $changedMounts) {
105
+            $this->connection->beginTransaction();
106
+            $userUID = $user->getUID();
107
+            try {
108
+                foreach ($addedMounts as $mount) {
109
+                    $this->logger->debug("Adding mount '{$mount->getKey()}' for user '$userUID'", ['app' => 'files', 'mount_provider' => $mount->getMountProvider()]);
110
+                    $this->addToCache($mount);
111
+                    /** @psalm-suppress InvalidArgument */
112
+                    $this->mountsForUsers[$userUID][$mount->getKey()] = $mount;
113
+                }
114
+                foreach ($removedMounts as $mount) {
115
+                    $this->logger->debug("Removing mount '{$mount->getKey()}' for user '$userUID'", ['app' => 'files', 'mount_provider' => $mount->getMountProvider()]);
116
+                    $this->removeFromCache($mount);
117
+                    unset($this->mountsForUsers[$userUID][$mount->getKey()]);
118
+                }
119
+                foreach ($changedMounts as $mountPair) {
120
+                    $newMount = $mountPair[1];
121
+                    $this->logger->debug("Updating mount '{$newMount->getKey()}' for user '$userUID'", ['app' => 'files', 'mount_provider' => $newMount->getMountProvider()]);
122
+                    $this->updateCachedMount($newMount);
123
+                    /** @psalm-suppress InvalidArgument */
124
+                    $this->mountsForUsers[$userUID][$newMount->getKey()] = $newMount;
125
+                }
126
+                $this->connection->commit();
127
+            } catch (\Throwable $e) {
128
+                $this->connection->rollBack();
129
+                throw $e;
130
+            }
131
+
132
+            // Only fire events after all mounts have already been adjusted in the database.
133
+            foreach ($addedMounts as $mount) {
134
+                $this->eventDispatcher->dispatchTyped(new UserMountAddedEvent($mount));
135
+            }
136
+            foreach ($removedMounts as $mount) {
137
+                $this->eventDispatcher->dispatchTyped(new UserMountRemovedEvent($mount));
138
+            }
139
+            foreach ($changedMounts as $mountPair) {
140
+                $this->eventDispatcher->dispatchTyped(new UserMountUpdatedEvent($mountPair[0], $mountPair[1]));
141
+            }
142
+        }
143
+        $this->eventLogger->end('fs:setup:user:register');
144
+    }
145
+
146
+    /**
147
+     * @param array<string, ICachedMountInfo> $newMounts
148
+     * @param array<string, ICachedMountInfo> $cachedMounts
149
+     * @return list<list{0: ICachedMountInfo, 1: ICachedMountInfo}> Pairs of old and new mounts
150
+     */
151
+    private function findChangedMounts(array $newMounts, array $cachedMounts): array {
152
+        $changed = [];
153
+        foreach ($cachedMounts as $key => $cachedMount) {
154
+            if (isset($newMounts[$key])) {
155
+                $newMount = $newMounts[$key];
156
+                if (
157
+                    $newMount->getStorageId() !== $cachedMount->getStorageId()
158
+                    || $newMount->getMountId() !== $cachedMount->getMountId()
159
+                    || $newMount->getMountProvider() !== $cachedMount->getMountProvider()
160
+                ) {
161
+                    $changed[] = [$cachedMount, $newMount];
162
+                }
163
+            }
164
+        }
165
+        return $changed;
166
+    }
167
+
168
+    private function addToCache(ICachedMountInfo $mount) {
169
+        if ($mount->getStorageId() !== -1) {
170
+            $qb = $this->connection->getQueryBuilder();
171
+            $qb
172
+                ->insert('mounts')
173
+                ->values([
174
+                    'storage_id' => $qb->createNamedParameter($mount->getStorageId(), IQueryBuilder::PARAM_INT),
175
+                    'root_id' => $qb->createNamedParameter($mount->getRootId(), IQueryBuilder::PARAM_INT),
176
+                    'user_id' => $qb->createNamedParameter($mount->getUser()->getUID()),
177
+                    'mount_point' => $qb->createNamedParameter($mount->getMountPoint()),
178
+                    'mount_point_hash' => $qb->createNamedParameter(hash('xxh128', $mount->getMountPoint())),
179
+                    'mount_id' => $qb->createNamedParameter($mount->getMountId(), IQueryBuilder::PARAM_INT),
180
+                    'mount_provider_class' => $qb->createNamedParameter($mount->getMountProvider()),
181
+                ]);
182
+            try {
183
+                $qb->executeStatement();
184
+            } catch (Exception $e) {
185
+                if ($e->getReason() !== Exception::REASON_UNIQUE_CONSTRAINT_VIOLATION) {
186
+                    throw $e;
187
+                }
188
+            }
189
+        } else {
190
+            // in some cases this is legitimate, like orphaned shares
191
+            $this->logger->debug('Could not get storage info for mount at ' . $mount->getMountPoint());
192
+        }
193
+    }
194
+
195
+    private function updateCachedMount(ICachedMountInfo $mount) {
196
+        $builder = $this->connection->getQueryBuilder();
197
+
198
+        $query = $builder->update('mounts')
199
+            ->set('storage_id', $builder->createNamedParameter($mount->getStorageId()))
200
+            ->set('mount_point', $builder->createNamedParameter($mount->getMountPoint()))
201
+            ->set('mount_point_hash', $builder->createNamedParameter(hash('xxh128', $mount->getMountPoint())))
202
+            ->set('mount_id', $builder->createNamedParameter($mount->getMountId(), IQueryBuilder::PARAM_INT))
203
+            ->set('mount_provider_class', $builder->createNamedParameter($mount->getMountProvider()))
204
+            ->where($builder->expr()->eq('user_id', $builder->createNamedParameter($mount->getUser()->getUID())))
205
+            ->andWhere($builder->expr()->eq('root_id', $builder->createNamedParameter($mount->getRootId(), IQueryBuilder::PARAM_INT)));
206
+
207
+        $query->executeStatement();
208
+    }
209
+
210
+    private function removeFromCache(ICachedMountInfo $mount) {
211
+        $builder = $this->connection->getQueryBuilder();
212
+
213
+        $query = $builder->delete('mounts')
214
+            ->where($builder->expr()->eq('user_id', $builder->createNamedParameter($mount->getUser()->getUID())))
215
+            ->andWhere($builder->expr()->eq('root_id', $builder->createNamedParameter($mount->getRootId(), IQueryBuilder::PARAM_INT)))
216
+            ->andWhere($builder->expr()->eq('mount_point_hash', $builder->createNamedParameter(hash('xxh128', $mount->getMountPoint()))));
217
+        $query->executeStatement();
218
+    }
219
+
220
+    /**
221
+     * @param array $row
222
+     * @param (callable(CachedMountInfo): string)|null $pathCallback
223
+     * @return CachedMountInfo
224
+     */
225
+    private function dbRowToMountInfo(array $row, ?callable $pathCallback = null): ICachedMountInfo {
226
+        $user = new LazyUser($row['user_id'], $this->userManager);
227
+        $mount_id = $row['mount_id'];
228
+        if (!is_null($mount_id)) {
229
+            $mount_id = (int)$mount_id;
230
+        }
231
+        if ($pathCallback) {
232
+            return new LazyPathCachedMountInfo(
233
+                $user,
234
+                (int)$row['storage_id'],
235
+                (int)$row['root_id'],
236
+                $row['mount_point'],
237
+                $row['mount_provider_class'] ?? '',
238
+                $mount_id,
239
+                $pathCallback,
240
+            );
241
+        } else {
242
+            return new CachedMountInfo(
243
+                $user,
244
+                (int)$row['storage_id'],
245
+                (int)$row['root_id'],
246
+                $row['mount_point'],
247
+                $row['mount_provider_class'] ?? '',
248
+                $mount_id,
249
+                $row['path'] ?? '',
250
+            );
251
+        }
252
+    }
253
+
254
+    /**
255
+     * @param IUser $user
256
+     * @return ICachedMountInfo[]
257
+     */
258
+    public function getMountsForUser(IUser $user) {
259
+        $userUID = $user->getUID();
260
+        if (!$this->userManager->userExists($userUID)) {
261
+            return [];
262
+        }
263
+        if (!isset($this->mountsForUsers[$userUID])) {
264
+            $builder = $this->connection->getQueryBuilder();
265
+            $query = $builder->select('storage_id', 'root_id', 'user_id', 'mount_point', 'mount_id', 'mount_provider_class')
266
+                ->from('mounts', 'm')
267
+                ->where($builder->expr()->eq('user_id', $builder->createNamedParameter($userUID)));
268
+
269
+            $result = $query->executeQuery();
270
+            $rows = $result->fetchAll();
271
+            $result->closeCursor();
272
+
273
+            /** @var array<string, ICachedMountInfo> $mounts */
274
+            $mounts = [];
275
+            foreach ($rows as $row) {
276
+                $mount = $this->dbRowToMountInfo($row, [$this, 'getInternalPathForMountInfo']);
277
+                if ($mount !== null) {
278
+                    $mounts[$mount->getKey()] = $mount;
279
+                }
280
+            }
281
+            $this->mountsForUsers[$userUID] = $mounts;
282
+        }
283
+        return $this->mountsForUsers[$userUID];
284
+    }
285
+
286
+    public function getInternalPathForMountInfo(CachedMountInfo $info): string {
287
+        $cached = $this->internalPathCache->get($info->getRootId());
288
+        if ($cached !== null) {
289
+            return $cached;
290
+        }
291
+        $builder = $this->connection->getQueryBuilder();
292
+        $query = $builder->select('path')
293
+            ->from('filecache')
294
+            ->where($builder->expr()->eq('fileid', $builder->createNamedParameter($info->getRootId())));
295
+        return $query->executeQuery()->fetchOne() ?: '';
296
+    }
297
+
298
+    /**
299
+     * @param int $numericStorageId
300
+     * @param string|null $user limit the results to a single user
301
+     * @return CachedMountInfo[]
302
+     */
303
+    public function getMountsForStorageId($numericStorageId, $user = null) {
304
+        $builder = $this->connection->getQueryBuilder();
305
+        $query = $builder->select('storage_id', 'root_id', 'user_id', 'mount_point', 'mount_id', 'f.path', 'mount_provider_class')
306
+            ->from('mounts', 'm')
307
+            ->innerJoin('m', 'filecache', 'f', $builder->expr()->eq('m.root_id', 'f.fileid'))
308
+            ->where($builder->expr()->eq('storage_id', $builder->createNamedParameter($numericStorageId, IQueryBuilder::PARAM_INT)));
309
+
310
+        if ($user) {
311
+            $query->andWhere($builder->expr()->eq('user_id', $builder->createNamedParameter($user)));
312
+        }
313
+
314
+        $result = $query->executeQuery();
315
+        $rows = $result->fetchAll();
316
+        $result->closeCursor();
317
+
318
+        return array_filter(array_map([$this, 'dbRowToMountInfo'], $rows));
319
+    }
320
+
321
+    /**
322
+     * @param int $rootFileId
323
+     * @return CachedMountInfo[]
324
+     */
325
+    public function getMountsForRootId($rootFileId) {
326
+        $builder = $this->connection->getQueryBuilder();
327
+        $query = $builder->select('storage_id', 'root_id', 'user_id', 'mount_point', 'mount_id', 'f.path', 'mount_provider_class')
328
+            ->from('mounts', 'm')
329
+            ->innerJoin('m', 'filecache', 'f', $builder->expr()->eq('m.root_id', 'f.fileid'))
330
+            ->where($builder->expr()->eq('root_id', $builder->createNamedParameter($rootFileId, IQueryBuilder::PARAM_INT)));
331
+
332
+        $result = $query->executeQuery();
333
+        $rows = $result->fetchAll();
334
+        $result->closeCursor();
335
+
336
+        return array_filter(array_map([$this, 'dbRowToMountInfo'], $rows));
337
+    }
338
+
339
+    /**
340
+     * @param $fileId
341
+     * @return array{int, string, int}
342
+     * @throws \OCP\Files\NotFoundException
343
+     */
344
+    private function getCacheInfoFromFileId($fileId): array {
345
+        if (!isset($this->cacheInfoCache[$fileId])) {
346
+            $builder = $this->connection->getQueryBuilder();
347
+            $query = $builder->select('storage', 'path', 'mimetype')
348
+                ->from('filecache')
349
+                ->where($builder->expr()->eq('fileid', $builder->createNamedParameter($fileId, IQueryBuilder::PARAM_INT)));
350
+
351
+            $result = $query->executeQuery();
352
+            $row = $result->fetch();
353
+            $result->closeCursor();
354
+
355
+            if (is_array($row)) {
356
+                $this->cacheInfoCache[$fileId] = [
357
+                    (int)$row['storage'],
358
+                    (string)$row['path'],
359
+                    (int)$row['mimetype']
360
+                ];
361
+            } else {
362
+                throw new NotFoundException('File with id "' . $fileId . '" not found');
363
+            }
364
+        }
365
+        return $this->cacheInfoCache[$fileId];
366
+    }
367
+
368
+    /**
369
+     * @param int $fileId
370
+     * @param string|null $user optionally restrict the results to a single user
371
+     * @return ICachedMountFileInfo[]
372
+     * @since 9.0.0
373
+     */
374
+    public function getMountsForFileId($fileId, $user = null) {
375
+        try {
376
+            [$storageId, $internalPath] = $this->getCacheInfoFromFileId($fileId);
377
+        } catch (NotFoundException $e) {
378
+            return [];
379
+        }
380
+
381
+        $builder = $this->connection->getQueryBuilder();
382
+        $query = $builder->select('storage_id', 'root_id', 'user_id', 'mount_point', 'mount_id', 'f.path', 'mount_provider_class')
383
+            ->from('mounts', 'm')
384
+            ->innerJoin('m', 'filecache', 'f', $builder->expr()->eq('m.root_id', 'f.fileid'))
385
+            ->where($builder->expr()->eq('storage_id', $builder->createNamedParameter($storageId, IQueryBuilder::PARAM_INT)))
386
+            ->andWhere(
387
+                $builder->expr()->orX(
388
+                    $builder->expr()->eq('f.fileid', $builder->createNamedParameter($fileId)),
389
+                    $builder->expr()->emptyString('f.path'),
390
+                    $builder->expr()->eq(
391
+                        $builder->func()->concat('f.path', $builder->createNamedParameter('/')),
392
+                        $builder->func()->substring(
393
+                            $builder->createNamedParameter($internalPath),
394
+                            $builder->createNamedParameter(1, IQueryBuilder::PARAM_INT),
395
+                            $builder->func()->add(
396
+                                $builder->func()->charLength('f.path'),
397
+                                $builder->createNamedParameter(1, IQueryBuilder::PARAM_INT),
398
+                            ),
399
+                        ),
400
+                    ),
401
+                )
402
+            );
403
+
404
+        if ($user !== null) {
405
+            $query->andWhere($builder->expr()->eq('user_id', $builder->createNamedParameter($user)));
406
+        }
407
+        $result = $query->executeQuery();
408
+
409
+        $mounts = [];
410
+        while ($row = $result->fetch()) {
411
+            if ($user === null && !$this->userManager->userExists($row['user_id'])) {
412
+                continue;
413
+            }
414
+
415
+            $mounts[] = new CachedMountFileInfo(
416
+                new LazyUser($row['user_id'], $this->userManager),
417
+                (int)$row['storage_id'],
418
+                (int)$row['root_id'],
419
+                $row['mount_point'],
420
+                $row['mount_id'] === null ? null : (int)$row['mount_id'],
421
+                $row['mount_provider_class'] ?? '',
422
+                $row['path'] ?? '',
423
+                $internalPath,
424
+            );
425
+        }
426
+
427
+        return $mounts;
428
+    }
429
+
430
+    /**
431
+     * Remove all cached mounts for a user
432
+     *
433
+     * @param IUser $user
434
+     */
435
+    public function removeUserMounts(IUser $user) {
436
+        $builder = $this->connection->getQueryBuilder();
437
+
438
+        $query = $builder->delete('mounts')
439
+            ->where($builder->expr()->eq('user_id', $builder->createNamedParameter($user->getUID())));
440
+        $query->executeStatement();
441
+    }
442
+
443
+    public function removeUserStorageMount($storageId, $userId) {
444
+        $builder = $this->connection->getQueryBuilder();
445
+
446
+        $query = $builder->delete('mounts')
447
+            ->where($builder->expr()->eq('user_id', $builder->createNamedParameter($userId)))
448
+            ->andWhere($builder->expr()->eq('storage_id', $builder->createNamedParameter($storageId, IQueryBuilder::PARAM_INT)));
449
+        $query->executeStatement();
450
+    }
451
+
452
+    public function remoteStorageMounts($storageId) {
453
+        $builder = $this->connection->getQueryBuilder();
454
+
455
+        $query = $builder->delete('mounts')
456
+            ->where($builder->expr()->eq('storage_id', $builder->createNamedParameter($storageId, IQueryBuilder::PARAM_INT)));
457
+        $query->executeStatement();
458
+    }
459
+
460
+    /**
461
+     * @param array $users
462
+     * @return array
463
+     */
464
+    public function getUsedSpaceForUsers(array $users) {
465
+        $builder = $this->connection->getQueryBuilder();
466
+
467
+        $mountPointHashes = array_map(static fn (IUser $user) => hash('xxh128', '/' . $user->getUID() . '/'), $users);
468
+        $userIds = array_map(static fn (IUser $user) => $user->getUID(), $users);
469
+
470
+        $query = $builder->select('m.user_id', 'f.size')
471
+            ->from('mounts', 'm')
472
+            ->innerJoin('m', 'filecache', 'f',
473
+                $builder->expr()->andX(
474
+                    $builder->expr()->eq('m.storage_id', 'f.storage'),
475
+                    $builder->expr()->eq('f.path_hash', $builder->createNamedParameter(md5('files')))
476
+                ))
477
+            ->where($builder->expr()->in('m.mount_point_hash', $builder->createNamedParameter($mountPointHashes, IQueryBuilder::PARAM_STR_ARRAY)))
478
+            ->andWhere($builder->expr()->in('m.user_id', $builder->createNamedParameter($userIds, IQueryBuilder::PARAM_STR_ARRAY)));
479
+
480
+        $result = $query->executeQuery();
481
+
482
+        $results = [];
483
+        while ($row = $result->fetch()) {
484
+            $results[$row['user_id']] = $row['size'];
485
+        }
486
+        $result->closeCursor();
487
+        return $results;
488
+    }
489
+
490
+    public function clear(): void {
491
+        $this->cacheInfoCache = new CappedMemoryCache();
492
+        $this->mountsForUsers = new CappedMemoryCache();
493
+    }
494
+
495
+    public function getMountForPath(IUser $user, string $path): ICachedMountInfo {
496
+        $mounts = [];
497
+        foreach ($this->getMountsForUser($user) as $mount) {
498
+            $mounts[$mount->getMountPoint()] = $mount;
499
+        }
500
+
501
+        $current = rtrim($path, '/');
502
+        // walk up the directory tree until we find a path that has a mountpoint set
503
+        // the loop will return if a mountpoint is found or break if none are found
504
+        while (true) {
505
+            $mountPoint = $current . '/';
506
+            if (isset($mounts[$mountPoint])) {
507
+                return $mounts[$mountPoint];
508
+            } elseif ($current === '') {
509
+                break;
510
+            }
511
+
512
+            $current = dirname($current);
513
+            if ($current === '.' || $current === '/') {
514
+                $current = '';
515
+            }
516
+        }
517
+
518
+        throw new NotFoundException('No cached mount for path ' . $path);
519
+    }
520
+
521
+    public function getMountsInPath(IUser $user, string $path): array {
522
+        $path = rtrim($path, '/') . '/';
523
+        $result = [];
524
+        foreach ($this->getMountsForUser($user) as $key => $mount) {
525
+            $mountPoint = $mount->getMountPoint();
526
+            if ($mountPoint !== $path && str_starts_with($mountPoint, $path)) {
527
+                $result[$key] = $mount;
528
+            }
529
+        }
530
+        return $result;
531
+    }
532
+
533
+    public function removeMount(string $mountPoint): void {
534
+        $query = $this->connection->getQueryBuilder();
535
+        $query->delete('mounts')
536
+            ->where($query->expr()->eq('mount_point', $query->createNamedParameter($mountPoint)));
537
+        $query->executeStatement();
538
+    }
539
+
540
+    public function addMount(IUser $user, string $mountPoint, ICacheEntry $rootCacheEntry, string $mountProvider, ?int $mountId = null): void {
541
+        $query = $this->connection->getQueryBuilder();
542
+        $query->insert('mounts')
543
+            ->values([
544
+                'storage_id' => $query->createNamedParameter($rootCacheEntry->getStorageId()),
545
+                'root_id' => $query->createNamedParameter($rootCacheEntry->getId()),
546
+                'user_id' => $query->createNamedParameter($user->getUID()),
547
+                'mount_point' => $query->createNamedParameter($mountPoint),
548
+                'mount_point_hash' => $query->createNamedParameter(hash('xxh128', $mountPoint)),
549
+                'mount_id' => $query->createNamedParameter($mountId),
550
+                'mount_provider_class' => $query->createNamedParameter($mountProvider)
551
+            ]);
552
+
553
+        try {
554
+            $query->executeStatement();
555
+        } catch (DbalException $e) {
556
+            if ($e->getReason() !== DbalException::REASON_UNIQUE_CONSTRAINT_VIOLATION) {
557
+                throw $e;
558
+            }
559
+        }
560
+    }
561
+
562
+    /**
563
+     * Clear the internal in-memory caches
564
+     */
565
+    public function flush(): void {
566
+        $this->cacheInfoCache = new CappedMemoryCache();
567
+        $this->internalPathCache = new CappedMemoryCache();
568
+        $this->mountsForUsers = new CappedMemoryCache();
569
+    }
570 570
 }
Please login to merge, or discard this patch.
apps/files_versions/tests/VersioningTest.php 1 patch
Indentation   +870 added lines, -870 removed lines patch added patch discarded remove patch
@@ -37,962 +37,962 @@
 block discarded – undo
37 37
  */
38 38
 #[\PHPUnit\Framework\Attributes\Group(name: 'DB')]
39 39
 class VersioningTest extends \Test\TestCase {
40
-	public const TEST_VERSIONS_USER = 'test-versions-user';
41
-	public const TEST_VERSIONS_USER2 = 'test-versions-user2';
42
-	public const USERS_VERSIONS_ROOT = '/test-versions-user/files_versions';
43
-
44
-	/**
45
-	 * @var View
46
-	 */
47
-	private $rootView;
48
-	/**
49
-	 * @var VersionsMapper
50
-	 */
51
-	private $versionsMapper;
52
-	/**
53
-	 * @var IMimeTypeLoader
54
-	 */
55
-	private $mimeTypeLoader;
56
-	private $user1;
57
-	private $user2;
58
-
59
-	public static function setUpBeforeClass(): void {
60
-		parent::setUpBeforeClass();
61
-
62
-		$application = new Application();
63
-
64
-		// create test user
65
-		self::loginHelper(self::TEST_VERSIONS_USER2, true);
66
-		self::loginHelper(self::TEST_VERSIONS_USER, true);
67
-	}
68
-
69
-	public static function tearDownAfterClass(): void {
70
-		// cleanup test user
71
-		$user = Server::get(IUserManager::class)->get(self::TEST_VERSIONS_USER);
72
-		if ($user !== null) {
73
-			$user->delete();
74
-		}
75
-		$user = Server::get(IUserManager::class)->get(self::TEST_VERSIONS_USER2);
76
-		if ($user !== null) {
77
-			$user->delete();
78
-		}
79
-
80
-		parent::tearDownAfterClass();
81
-	}
82
-
83
-	protected function setUp(): void {
84
-		parent::setUp();
85
-
86
-		$config = Server::get(IConfig::class);
87
-		$mockConfig = $this->getMockBuilder(AllConfig::class)
88
-			->onlyMethods(['getSystemValue'])
89
-			->setConstructorArgs([Server::get(SystemConfig::class)])
90
-			->getMock();
91
-		$mockConfig->expects($this->any())
92
-			->method('getSystemValue')
93
-			->willReturnCallback(function ($key, $default) use ($config) {
94
-				if ($key === 'filesystem_check_changes') {
95
-					return Watcher::CHECK_ONCE;
96
-				} else {
97
-					return $config->getSystemValue($key, $default);
98
-				}
99
-			});
100
-		$this->overwriteService(AllConfig::class, $mockConfig);
101
-
102
-		// clear hooks
103
-		\OC_Hook::clear();
104
-		\OC::registerShareHooks(Server::get(SystemConfig::class));
105
-		\OC::$server->boot();
106
-
107
-		// ensure both users have an up-to-date state
108
-		self::loginHelper(self::TEST_VERSIONS_USER2);
109
-		self::loginHelper(self::TEST_VERSIONS_USER);
110
-		$this->rootView = new View();
111
-		if (!$this->rootView->file_exists(self::USERS_VERSIONS_ROOT)) {
112
-			$this->rootView->mkdir(self::USERS_VERSIONS_ROOT);
113
-		}
114
-
115
-		$this->versionsMapper = Server::get(VersionsMapper::class);
116
-		$this->mimeTypeLoader = Server::get(IMimeTypeLoader::class);
117
-
118
-		$this->user1 = $this->createMock(IUser::class);
119
-		$this->user1->method('getUID')
120
-			->willReturn(self::TEST_VERSIONS_USER);
121
-		$this->user2 = $this->createMock(IUser::class);
122
-		$this->user2->method('getUID')
123
-			->willReturn(self::TEST_VERSIONS_USER2);
124
-	}
125
-
126
-	protected function tearDown(): void {
127
-		$this->restoreService(AllConfig::class);
128
-
129
-		if ($this->rootView) {
130
-			$this->rootView->deleteAll(self::TEST_VERSIONS_USER . '/files/');
131
-			$this->rootView->deleteAll(self::TEST_VERSIONS_USER2 . '/files/');
132
-			$this->rootView->deleteAll(self::TEST_VERSIONS_USER . '/files_versions/');
133
-			$this->rootView->deleteAll(self::TEST_VERSIONS_USER2 . '/files_versions/');
134
-		}
135
-
136
-		\OC_Hook::clear();
137
-
138
-		parent::tearDown();
139
-	}
140
-
141
-	/**
142
-	 * test expire logic
143
-	 */
144
-	#[\PHPUnit\Framework\Attributes\DataProvider(methodName: 'versionsProvider')]
145
-	public function testGetExpireList($versions, $sizeOfAllDeletedFiles): void {
146
-
147
-		// last interval end at 2592000
148
-		$startTime = 5000000;
149
-
150
-		$testClass = new VersionStorageToTest();
151
-		[$deleted, $size] = $testClass->callProtectedGetExpireList($startTime, $versions);
152
-
153
-		// we should have deleted 16 files each of the size 1
154
-		$this->assertEquals($sizeOfAllDeletedFiles, $size);
155
-
156
-		// the deleted array should only contain versions which should be deleted
157
-		foreach ($deleted as $key => $path) {
158
-			unset($versions[$key]);
159
-			$this->assertEquals('delete', substr($path, 0, strlen('delete')));
160
-		}
161
-
162
-		// the versions array should only contain versions which should be kept
163
-		foreach ($versions as $version) {
164
-			$this->assertEquals('keep', $version['path']);
165
-		}
166
-	}
167
-
168
-	public static function versionsProvider(): array {
169
-		return [
170
-			// first set of versions uniformly distributed versions
171
-			[
172
-				[
173
-					// first slice (10sec) keep one version every 2 seconds
174
-					['version' => 4999999, 'path' => 'keep', 'size' => 1],
175
-					['version' => 4999998, 'path' => 'delete', 'size' => 1],
176
-					['version' => 4999997, 'path' => 'keep', 'size' => 1],
177
-					['version' => 4999995, 'path' => 'keep', 'size' => 1],
178
-					['version' => 4999994, 'path' => 'delete', 'size' => 1],
179
-					//next slice (60sec) starts at 4999990 keep one version every 10 secons
180
-					['version' => 4999988, 'path' => 'keep', 'size' => 1],
181
-					['version' => 4999978, 'path' => 'keep', 'size' => 1],
182
-					['version' => 4999975, 'path' => 'delete', 'size' => 1],
183
-					['version' => 4999972, 'path' => 'delete', 'size' => 1],
184
-					['version' => 4999967, 'path' => 'keep', 'size' => 1],
185
-					['version' => 4999958, 'path' => 'delete', 'size' => 1],
186
-					['version' => 4999957, 'path' => 'keep', 'size' => 1],
187
-					//next slice (3600sec) start at 4999940 keep one version every 60 seconds
188
-					['version' => 4999900, 'path' => 'keep', 'size' => 1],
189
-					['version' => 4999841, 'path' => 'delete', 'size' => 1],
190
-					['version' => 4999840, 'path' => 'keep', 'size' => 1],
191
-					['version' => 4999780, 'path' => 'keep', 'size' => 1],
192
-					['version' => 4996401, 'path' => 'keep', 'size' => 1],
193
-					// next slice (86400sec) start at 4996400 keep one version every 3600 seconds
194
-					['version' => 4996350, 'path' => 'delete', 'size' => 1],
195
-					['version' => 4992800, 'path' => 'keep', 'size' => 1],
196
-					['version' => 4989800, 'path' => 'delete', 'size' => 1],
197
-					['version' => 4989700, 'path' => 'delete', 'size' => 1],
198
-					['version' => 4989200, 'path' => 'keep', 'size' => 1],
199
-					// next slice (2592000sec) start at 4913600 keep one version every 86400 seconds
200
-					['version' => 4913600, 'path' => 'keep', 'size' => 1],
201
-					['version' => 4852800, 'path' => 'delete', 'size' => 1],
202
-					['version' => 4827201, 'path' => 'delete', 'size' => 1],
203
-					['version' => 4827200, 'path' => 'keep', 'size' => 1],
204
-					['version' => 4777201, 'path' => 'delete', 'size' => 1],
205
-					['version' => 4777501, 'path' => 'delete', 'size' => 1],
206
-					['version' => 4740000, 'path' => 'keep', 'size' => 1],
207
-					// final slice starts at 2408000 keep one version every 604800 secons
208
-					['version' => 2408000, 'path' => 'keep', 'size' => 1],
209
-					['version' => 1803201, 'path' => 'delete', 'size' => 1],
210
-					['version' => 1803200, 'path' => 'keep', 'size' => 1],
211
-					['version' => 1800199, 'path' => 'delete', 'size' => 1],
212
-					['version' => 1800100, 'path' => 'delete', 'size' => 1],
213
-					['version' => 1198300, 'path' => 'keep', 'size' => 1],
214
-				],
215
-				16 // size of all deleted files (every file has the size 1)
216
-			],
217
-			// second set of versions, here we have only really old versions
218
-			[
219
-				[
220
-					// first slice (10sec) keep one version every 2 seconds
221
-					// next slice (60sec) starts at 4999990 keep one version every 10 secons
222
-					// next slice (3600sec) start at 4999940 keep one version every 60 seconds
223
-					// next slice (86400sec) start at 4996400 keep one version every 3600 seconds
224
-					['version' => 4996400, 'path' => 'keep', 'size' => 1],
225
-					['version' => 4996350, 'path' => 'delete', 'size' => 1],
226
-					['version' => 4996350, 'path' => 'delete', 'size' => 1],
227
-					['version' => 4992800, 'path' => 'keep', 'size' => 1],
228
-					['version' => 4989800, 'path' => 'delete', 'size' => 1],
229
-					['version' => 4989700, 'path' => 'delete', 'size' => 1],
230
-					['version' => 4989200, 'path' => 'keep', 'size' => 1],
231
-					// next slice (2592000sec) start at 4913600 keep one version every 86400 seconds
232
-					['version' => 4913600, 'path' => 'keep', 'size' => 1],
233
-					['version' => 4852800, 'path' => 'delete', 'size' => 1],
234
-					['version' => 4827201, 'path' => 'delete', 'size' => 1],
235
-					['version' => 4827200, 'path' => 'keep', 'size' => 1],
236
-					['version' => 4777201, 'path' => 'delete', 'size' => 1],
237
-					['version' => 4777501, 'path' => 'delete', 'size' => 1],
238
-					['version' => 4740000, 'path' => 'keep', 'size' => 1],
239
-					// final slice starts at 2408000 keep one version every 604800 secons
240
-					['version' => 2408000, 'path' => 'keep', 'size' => 1],
241
-					['version' => 1803201, 'path' => 'delete', 'size' => 1],
242
-					['version' => 1803200, 'path' => 'keep', 'size' => 1],
243
-					['version' => 1800199, 'path' => 'delete', 'size' => 1],
244
-					['version' => 1800100, 'path' => 'delete', 'size' => 1],
245
-					['version' => 1198300, 'path' => 'keep', 'size' => 1],
246
-				],
247
-				11 // size of all deleted files (every file has the size 1)
248
-			],
249
-			// third set of versions, with some gaps between
250
-			[
251
-				[
252
-					// first slice (10sec) keep one version every 2 seconds
253
-					['version' => 4999999, 'path' => 'keep', 'size' => 1],
254
-					['version' => 4999998, 'path' => 'delete', 'size' => 1],
255
-					['version' => 4999997, 'path' => 'keep', 'size' => 1],
256
-					['version' => 4999995, 'path' => 'keep', 'size' => 1],
257
-					['version' => 4999994, 'path' => 'delete', 'size' => 1],
258
-					//next slice (60sec) starts at 4999990 keep one version every 10 secons
259
-					['version' => 4999988, 'path' => 'keep', 'size' => 1],
260
-					['version' => 4999978, 'path' => 'keep', 'size' => 1],
261
-					//next slice (3600sec) start at 4999940 keep one version every 60 seconds
262
-					// next slice (86400sec) start at 4996400 keep one version every 3600 seconds
263
-					['version' => 4989200, 'path' => 'keep', 'size' => 1],
264
-					// next slice (2592000sec) start at 4913600 keep one version every 86400 seconds
265
-					['version' => 4913600, 'path' => 'keep', 'size' => 1],
266
-					['version' => 4852800, 'path' => 'delete', 'size' => 1],
267
-					['version' => 4827201, 'path' => 'delete', 'size' => 1],
268
-					['version' => 4827200, 'path' => 'keep', 'size' => 1],
269
-					['version' => 4777201, 'path' => 'delete', 'size' => 1],
270
-					['version' => 4777501, 'path' => 'delete', 'size' => 1],
271
-					['version' => 4740000, 'path' => 'keep', 'size' => 1],
272
-					// final slice starts at 2408000 keep one version every 604800 secons
273
-					['version' => 2408000, 'path' => 'keep', 'size' => 1],
274
-					['version' => 1803201, 'path' => 'delete', 'size' => 1],
275
-					['version' => 1803200, 'path' => 'keep', 'size' => 1],
276
-					['version' => 1800199, 'path' => 'delete', 'size' => 1],
277
-					['version' => 1800100, 'path' => 'delete', 'size' => 1],
278
-					['version' => 1198300, 'path' => 'keep', 'size' => 1],
279
-				],
280
-				9 // size of all deleted files (every file has the size 1)
281
-			],
282
-			// fourth set of versions: empty (see issue #19066)
283
-			[
284
-				[],
285
-				0
286
-			]
287
-
288
-		];
289
-	}
290
-
291
-	public function testRename(): void {
292
-		Filesystem::file_put_contents('test.txt', 'test file');
293
-
294
-		$t1 = time();
295
-		// second version is two weeks older, this way we make sure that no
296
-		// version will be expired
297
-		$t2 = $t1 - 60 * 60 * 24 * 14;
298
-
299
-		// create some versions
300
-		$v1 = self::USERS_VERSIONS_ROOT . '/test.txt.v' . $t1;
301
-		$v2 = self::USERS_VERSIONS_ROOT . '/test.txt.v' . $t2;
302
-		$v1Renamed = self::USERS_VERSIONS_ROOT . '/test2.txt.v' . $t1;
303
-		$v2Renamed = self::USERS_VERSIONS_ROOT . '/test2.txt.v' . $t2;
304
-
305
-		$this->rootView->file_put_contents($v1, 'version1');
306
-		$this->rootView->file_put_contents($v2, 'version2');
307
-
308
-		// execute rename hook of versions app
309
-		Filesystem::rename('test.txt', 'test2.txt');
310
-
311
-		$this->runCommands();
312
-
313
-		$this->assertFalse($this->rootView->file_exists($v1), 'version 1 of old file does not exist');
314
-		$this->assertFalse($this->rootView->file_exists($v2), 'version 2 of old file does not exist');
315
-
316
-		$this->assertTrue($this->rootView->file_exists($v1Renamed), 'version 1 of renamed file exists');
317
-		$this->assertTrue($this->rootView->file_exists($v2Renamed), 'version 2 of renamed file exists');
318
-	}
319
-
320
-	public function testRenameInSharedFolder(): void {
321
-		Filesystem::mkdir('folder1');
322
-		Filesystem::mkdir('folder1/folder2');
323
-		Filesystem::file_put_contents('folder1/test.txt', 'test file');
324
-
325
-		$t1 = time();
326
-		// second version is two weeks older, this way we make sure that no
327
-		// version will be expired
328
-		$t2 = $t1 - 60 * 60 * 24 * 14;
329
-
330
-		$this->rootView->mkdir(self::USERS_VERSIONS_ROOT . '/folder1');
331
-		// create some versions
332
-		$v1 = self::USERS_VERSIONS_ROOT . '/folder1/test.txt.v' . $t1;
333
-		$v2 = self::USERS_VERSIONS_ROOT . '/folder1/test.txt.v' . $t2;
334
-		$v1Renamed = self::USERS_VERSIONS_ROOT . '/folder1/folder2/test.txt.v' . $t1;
335
-		$v2Renamed = self::USERS_VERSIONS_ROOT . '/folder1/folder2/test.txt.v' . $t2;
336
-
337
-		$this->rootView->file_put_contents($v1, 'version1');
338
-		$this->rootView->file_put_contents($v2, 'version2');
339
-
340
-		$node = \OC::$server->getUserFolder(self::TEST_VERSIONS_USER)->get('folder1');
341
-		$share = Server::get(\OCP\Share\IManager::class)->newShare();
342
-		$share->setNode($node)
343
-			->setShareType(IShare::TYPE_USER)
344
-			->setSharedBy(self::TEST_VERSIONS_USER)
345
-			->setSharedWith(self::TEST_VERSIONS_USER2)
346
-			->setPermissions(Constants::PERMISSION_ALL);
347
-		$share = Server::get(\OCP\Share\IManager::class)->createShare($share);
348
-		Server::get(\OCP\Share\IManager::class)->acceptShare($share, self::TEST_VERSIONS_USER2);
349
-
350
-		self::loginHelper(self::TEST_VERSIONS_USER2);
351
-
352
-		$this->assertTrue(Filesystem::file_exists('folder1/test.txt'));
353
-
354
-		// execute rename hook of versions app
355
-		Filesystem::rename('/folder1/test.txt', '/folder1/folder2/test.txt');
356
-
357
-		$this->runCommands();
358
-
359
-		self::loginHelper(self::TEST_VERSIONS_USER);
360
-
361
-		$this->assertFalse($this->rootView->file_exists($v1), 'version 1 of old file does not exist');
362
-		$this->assertFalse($this->rootView->file_exists($v2), 'version 2 of old file does not exist');
363
-
364
-		$this->assertTrue($this->rootView->file_exists($v1Renamed), 'version 1 of renamed file exists');
365
-		$this->assertTrue($this->rootView->file_exists($v2Renamed), 'version 2 of renamed file exists');
366
-
367
-		Server::get(\OCP\Share\IManager::class)->deleteShare($share);
368
-	}
369
-
370
-	public function testMoveFolder(): void {
371
-		Filesystem::mkdir('folder1');
372
-		Filesystem::mkdir('folder2');
373
-		Filesystem::file_put_contents('folder1/test.txt', 'test file');
374
-
375
-		$t1 = time();
376
-		// second version is two weeks older, this way we make sure that no
377
-		// version will be expired
378
-		$t2 = $t1 - 60 * 60 * 24 * 14;
379
-
380
-		// create some versions
381
-		$this->rootView->mkdir(self::USERS_VERSIONS_ROOT . '/folder1');
382
-		$v1 = self::USERS_VERSIONS_ROOT . '/folder1/test.txt.v' . $t1;
383
-		$v2 = self::USERS_VERSIONS_ROOT . '/folder1/test.txt.v' . $t2;
384
-		$v1Renamed = self::USERS_VERSIONS_ROOT . '/folder2/folder1/test.txt.v' . $t1;
385
-		$v2Renamed = self::USERS_VERSIONS_ROOT . '/folder2/folder1/test.txt.v' . $t2;
40
+    public const TEST_VERSIONS_USER = 'test-versions-user';
41
+    public const TEST_VERSIONS_USER2 = 'test-versions-user2';
42
+    public const USERS_VERSIONS_ROOT = '/test-versions-user/files_versions';
43
+
44
+    /**
45
+     * @var View
46
+     */
47
+    private $rootView;
48
+    /**
49
+     * @var VersionsMapper
50
+     */
51
+    private $versionsMapper;
52
+    /**
53
+     * @var IMimeTypeLoader
54
+     */
55
+    private $mimeTypeLoader;
56
+    private $user1;
57
+    private $user2;
58
+
59
+    public static function setUpBeforeClass(): void {
60
+        parent::setUpBeforeClass();
61
+
62
+        $application = new Application();
63
+
64
+        // create test user
65
+        self::loginHelper(self::TEST_VERSIONS_USER2, true);
66
+        self::loginHelper(self::TEST_VERSIONS_USER, true);
67
+    }
68
+
69
+    public static function tearDownAfterClass(): void {
70
+        // cleanup test user
71
+        $user = Server::get(IUserManager::class)->get(self::TEST_VERSIONS_USER);
72
+        if ($user !== null) {
73
+            $user->delete();
74
+        }
75
+        $user = Server::get(IUserManager::class)->get(self::TEST_VERSIONS_USER2);
76
+        if ($user !== null) {
77
+            $user->delete();
78
+        }
79
+
80
+        parent::tearDownAfterClass();
81
+    }
82
+
83
+    protected function setUp(): void {
84
+        parent::setUp();
85
+
86
+        $config = Server::get(IConfig::class);
87
+        $mockConfig = $this->getMockBuilder(AllConfig::class)
88
+            ->onlyMethods(['getSystemValue'])
89
+            ->setConstructorArgs([Server::get(SystemConfig::class)])
90
+            ->getMock();
91
+        $mockConfig->expects($this->any())
92
+            ->method('getSystemValue')
93
+            ->willReturnCallback(function ($key, $default) use ($config) {
94
+                if ($key === 'filesystem_check_changes') {
95
+                    return Watcher::CHECK_ONCE;
96
+                } else {
97
+                    return $config->getSystemValue($key, $default);
98
+                }
99
+            });
100
+        $this->overwriteService(AllConfig::class, $mockConfig);
101
+
102
+        // clear hooks
103
+        \OC_Hook::clear();
104
+        \OC::registerShareHooks(Server::get(SystemConfig::class));
105
+        \OC::$server->boot();
106
+
107
+        // ensure both users have an up-to-date state
108
+        self::loginHelper(self::TEST_VERSIONS_USER2);
109
+        self::loginHelper(self::TEST_VERSIONS_USER);
110
+        $this->rootView = new View();
111
+        if (!$this->rootView->file_exists(self::USERS_VERSIONS_ROOT)) {
112
+            $this->rootView->mkdir(self::USERS_VERSIONS_ROOT);
113
+        }
114
+
115
+        $this->versionsMapper = Server::get(VersionsMapper::class);
116
+        $this->mimeTypeLoader = Server::get(IMimeTypeLoader::class);
117
+
118
+        $this->user1 = $this->createMock(IUser::class);
119
+        $this->user1->method('getUID')
120
+            ->willReturn(self::TEST_VERSIONS_USER);
121
+        $this->user2 = $this->createMock(IUser::class);
122
+        $this->user2->method('getUID')
123
+            ->willReturn(self::TEST_VERSIONS_USER2);
124
+    }
125
+
126
+    protected function tearDown(): void {
127
+        $this->restoreService(AllConfig::class);
128
+
129
+        if ($this->rootView) {
130
+            $this->rootView->deleteAll(self::TEST_VERSIONS_USER . '/files/');
131
+            $this->rootView->deleteAll(self::TEST_VERSIONS_USER2 . '/files/');
132
+            $this->rootView->deleteAll(self::TEST_VERSIONS_USER . '/files_versions/');
133
+            $this->rootView->deleteAll(self::TEST_VERSIONS_USER2 . '/files_versions/');
134
+        }
135
+
136
+        \OC_Hook::clear();
137
+
138
+        parent::tearDown();
139
+    }
140
+
141
+    /**
142
+     * test expire logic
143
+     */
144
+    #[\PHPUnit\Framework\Attributes\DataProvider(methodName: 'versionsProvider')]
145
+    public function testGetExpireList($versions, $sizeOfAllDeletedFiles): void {
146
+
147
+        // last interval end at 2592000
148
+        $startTime = 5000000;
149
+
150
+        $testClass = new VersionStorageToTest();
151
+        [$deleted, $size] = $testClass->callProtectedGetExpireList($startTime, $versions);
152
+
153
+        // we should have deleted 16 files each of the size 1
154
+        $this->assertEquals($sizeOfAllDeletedFiles, $size);
155
+
156
+        // the deleted array should only contain versions which should be deleted
157
+        foreach ($deleted as $key => $path) {
158
+            unset($versions[$key]);
159
+            $this->assertEquals('delete', substr($path, 0, strlen('delete')));
160
+        }
161
+
162
+        // the versions array should only contain versions which should be kept
163
+        foreach ($versions as $version) {
164
+            $this->assertEquals('keep', $version['path']);
165
+        }
166
+    }
167
+
168
+    public static function versionsProvider(): array {
169
+        return [
170
+            // first set of versions uniformly distributed versions
171
+            [
172
+                [
173
+                    // first slice (10sec) keep one version every 2 seconds
174
+                    ['version' => 4999999, 'path' => 'keep', 'size' => 1],
175
+                    ['version' => 4999998, 'path' => 'delete', 'size' => 1],
176
+                    ['version' => 4999997, 'path' => 'keep', 'size' => 1],
177
+                    ['version' => 4999995, 'path' => 'keep', 'size' => 1],
178
+                    ['version' => 4999994, 'path' => 'delete', 'size' => 1],
179
+                    //next slice (60sec) starts at 4999990 keep one version every 10 secons
180
+                    ['version' => 4999988, 'path' => 'keep', 'size' => 1],
181
+                    ['version' => 4999978, 'path' => 'keep', 'size' => 1],
182
+                    ['version' => 4999975, 'path' => 'delete', 'size' => 1],
183
+                    ['version' => 4999972, 'path' => 'delete', 'size' => 1],
184
+                    ['version' => 4999967, 'path' => 'keep', 'size' => 1],
185
+                    ['version' => 4999958, 'path' => 'delete', 'size' => 1],
186
+                    ['version' => 4999957, 'path' => 'keep', 'size' => 1],
187
+                    //next slice (3600sec) start at 4999940 keep one version every 60 seconds
188
+                    ['version' => 4999900, 'path' => 'keep', 'size' => 1],
189
+                    ['version' => 4999841, 'path' => 'delete', 'size' => 1],
190
+                    ['version' => 4999840, 'path' => 'keep', 'size' => 1],
191
+                    ['version' => 4999780, 'path' => 'keep', 'size' => 1],
192
+                    ['version' => 4996401, 'path' => 'keep', 'size' => 1],
193
+                    // next slice (86400sec) start at 4996400 keep one version every 3600 seconds
194
+                    ['version' => 4996350, 'path' => 'delete', 'size' => 1],
195
+                    ['version' => 4992800, 'path' => 'keep', 'size' => 1],
196
+                    ['version' => 4989800, 'path' => 'delete', 'size' => 1],
197
+                    ['version' => 4989700, 'path' => 'delete', 'size' => 1],
198
+                    ['version' => 4989200, 'path' => 'keep', 'size' => 1],
199
+                    // next slice (2592000sec) start at 4913600 keep one version every 86400 seconds
200
+                    ['version' => 4913600, 'path' => 'keep', 'size' => 1],
201
+                    ['version' => 4852800, 'path' => 'delete', 'size' => 1],
202
+                    ['version' => 4827201, 'path' => 'delete', 'size' => 1],
203
+                    ['version' => 4827200, 'path' => 'keep', 'size' => 1],
204
+                    ['version' => 4777201, 'path' => 'delete', 'size' => 1],
205
+                    ['version' => 4777501, 'path' => 'delete', 'size' => 1],
206
+                    ['version' => 4740000, 'path' => 'keep', 'size' => 1],
207
+                    // final slice starts at 2408000 keep one version every 604800 secons
208
+                    ['version' => 2408000, 'path' => 'keep', 'size' => 1],
209
+                    ['version' => 1803201, 'path' => 'delete', 'size' => 1],
210
+                    ['version' => 1803200, 'path' => 'keep', 'size' => 1],
211
+                    ['version' => 1800199, 'path' => 'delete', 'size' => 1],
212
+                    ['version' => 1800100, 'path' => 'delete', 'size' => 1],
213
+                    ['version' => 1198300, 'path' => 'keep', 'size' => 1],
214
+                ],
215
+                16 // size of all deleted files (every file has the size 1)
216
+            ],
217
+            // second set of versions, here we have only really old versions
218
+            [
219
+                [
220
+                    // first slice (10sec) keep one version every 2 seconds
221
+                    // next slice (60sec) starts at 4999990 keep one version every 10 secons
222
+                    // next slice (3600sec) start at 4999940 keep one version every 60 seconds
223
+                    // next slice (86400sec) start at 4996400 keep one version every 3600 seconds
224
+                    ['version' => 4996400, 'path' => 'keep', 'size' => 1],
225
+                    ['version' => 4996350, 'path' => 'delete', 'size' => 1],
226
+                    ['version' => 4996350, 'path' => 'delete', 'size' => 1],
227
+                    ['version' => 4992800, 'path' => 'keep', 'size' => 1],
228
+                    ['version' => 4989800, 'path' => 'delete', 'size' => 1],
229
+                    ['version' => 4989700, 'path' => 'delete', 'size' => 1],
230
+                    ['version' => 4989200, 'path' => 'keep', 'size' => 1],
231
+                    // next slice (2592000sec) start at 4913600 keep one version every 86400 seconds
232
+                    ['version' => 4913600, 'path' => 'keep', 'size' => 1],
233
+                    ['version' => 4852800, 'path' => 'delete', 'size' => 1],
234
+                    ['version' => 4827201, 'path' => 'delete', 'size' => 1],
235
+                    ['version' => 4827200, 'path' => 'keep', 'size' => 1],
236
+                    ['version' => 4777201, 'path' => 'delete', 'size' => 1],
237
+                    ['version' => 4777501, 'path' => 'delete', 'size' => 1],
238
+                    ['version' => 4740000, 'path' => 'keep', 'size' => 1],
239
+                    // final slice starts at 2408000 keep one version every 604800 secons
240
+                    ['version' => 2408000, 'path' => 'keep', 'size' => 1],
241
+                    ['version' => 1803201, 'path' => 'delete', 'size' => 1],
242
+                    ['version' => 1803200, 'path' => 'keep', 'size' => 1],
243
+                    ['version' => 1800199, 'path' => 'delete', 'size' => 1],
244
+                    ['version' => 1800100, 'path' => 'delete', 'size' => 1],
245
+                    ['version' => 1198300, 'path' => 'keep', 'size' => 1],
246
+                ],
247
+                11 // size of all deleted files (every file has the size 1)
248
+            ],
249
+            // third set of versions, with some gaps between
250
+            [
251
+                [
252
+                    // first slice (10sec) keep one version every 2 seconds
253
+                    ['version' => 4999999, 'path' => 'keep', 'size' => 1],
254
+                    ['version' => 4999998, 'path' => 'delete', 'size' => 1],
255
+                    ['version' => 4999997, 'path' => 'keep', 'size' => 1],
256
+                    ['version' => 4999995, 'path' => 'keep', 'size' => 1],
257
+                    ['version' => 4999994, 'path' => 'delete', 'size' => 1],
258
+                    //next slice (60sec) starts at 4999990 keep one version every 10 secons
259
+                    ['version' => 4999988, 'path' => 'keep', 'size' => 1],
260
+                    ['version' => 4999978, 'path' => 'keep', 'size' => 1],
261
+                    //next slice (3600sec) start at 4999940 keep one version every 60 seconds
262
+                    // next slice (86400sec) start at 4996400 keep one version every 3600 seconds
263
+                    ['version' => 4989200, 'path' => 'keep', 'size' => 1],
264
+                    // next slice (2592000sec) start at 4913600 keep one version every 86400 seconds
265
+                    ['version' => 4913600, 'path' => 'keep', 'size' => 1],
266
+                    ['version' => 4852800, 'path' => 'delete', 'size' => 1],
267
+                    ['version' => 4827201, 'path' => 'delete', 'size' => 1],
268
+                    ['version' => 4827200, 'path' => 'keep', 'size' => 1],
269
+                    ['version' => 4777201, 'path' => 'delete', 'size' => 1],
270
+                    ['version' => 4777501, 'path' => 'delete', 'size' => 1],
271
+                    ['version' => 4740000, 'path' => 'keep', 'size' => 1],
272
+                    // final slice starts at 2408000 keep one version every 604800 secons
273
+                    ['version' => 2408000, 'path' => 'keep', 'size' => 1],
274
+                    ['version' => 1803201, 'path' => 'delete', 'size' => 1],
275
+                    ['version' => 1803200, 'path' => 'keep', 'size' => 1],
276
+                    ['version' => 1800199, 'path' => 'delete', 'size' => 1],
277
+                    ['version' => 1800100, 'path' => 'delete', 'size' => 1],
278
+                    ['version' => 1198300, 'path' => 'keep', 'size' => 1],
279
+                ],
280
+                9 // size of all deleted files (every file has the size 1)
281
+            ],
282
+            // fourth set of versions: empty (see issue #19066)
283
+            [
284
+                [],
285
+                0
286
+            ]
287
+
288
+        ];
289
+    }
290
+
291
+    public function testRename(): void {
292
+        Filesystem::file_put_contents('test.txt', 'test file');
293
+
294
+        $t1 = time();
295
+        // second version is two weeks older, this way we make sure that no
296
+        // version will be expired
297
+        $t2 = $t1 - 60 * 60 * 24 * 14;
298
+
299
+        // create some versions
300
+        $v1 = self::USERS_VERSIONS_ROOT . '/test.txt.v' . $t1;
301
+        $v2 = self::USERS_VERSIONS_ROOT . '/test.txt.v' . $t2;
302
+        $v1Renamed = self::USERS_VERSIONS_ROOT . '/test2.txt.v' . $t1;
303
+        $v2Renamed = self::USERS_VERSIONS_ROOT . '/test2.txt.v' . $t2;
304
+
305
+        $this->rootView->file_put_contents($v1, 'version1');
306
+        $this->rootView->file_put_contents($v2, 'version2');
307
+
308
+        // execute rename hook of versions app
309
+        Filesystem::rename('test.txt', 'test2.txt');
310
+
311
+        $this->runCommands();
312
+
313
+        $this->assertFalse($this->rootView->file_exists($v1), 'version 1 of old file does not exist');
314
+        $this->assertFalse($this->rootView->file_exists($v2), 'version 2 of old file does not exist');
315
+
316
+        $this->assertTrue($this->rootView->file_exists($v1Renamed), 'version 1 of renamed file exists');
317
+        $this->assertTrue($this->rootView->file_exists($v2Renamed), 'version 2 of renamed file exists');
318
+    }
319
+
320
+    public function testRenameInSharedFolder(): void {
321
+        Filesystem::mkdir('folder1');
322
+        Filesystem::mkdir('folder1/folder2');
323
+        Filesystem::file_put_contents('folder1/test.txt', 'test file');
324
+
325
+        $t1 = time();
326
+        // second version is two weeks older, this way we make sure that no
327
+        // version will be expired
328
+        $t2 = $t1 - 60 * 60 * 24 * 14;
329
+
330
+        $this->rootView->mkdir(self::USERS_VERSIONS_ROOT . '/folder1');
331
+        // create some versions
332
+        $v1 = self::USERS_VERSIONS_ROOT . '/folder1/test.txt.v' . $t1;
333
+        $v2 = self::USERS_VERSIONS_ROOT . '/folder1/test.txt.v' . $t2;
334
+        $v1Renamed = self::USERS_VERSIONS_ROOT . '/folder1/folder2/test.txt.v' . $t1;
335
+        $v2Renamed = self::USERS_VERSIONS_ROOT . '/folder1/folder2/test.txt.v' . $t2;
336
+
337
+        $this->rootView->file_put_contents($v1, 'version1');
338
+        $this->rootView->file_put_contents($v2, 'version2');
339
+
340
+        $node = \OC::$server->getUserFolder(self::TEST_VERSIONS_USER)->get('folder1');
341
+        $share = Server::get(\OCP\Share\IManager::class)->newShare();
342
+        $share->setNode($node)
343
+            ->setShareType(IShare::TYPE_USER)
344
+            ->setSharedBy(self::TEST_VERSIONS_USER)
345
+            ->setSharedWith(self::TEST_VERSIONS_USER2)
346
+            ->setPermissions(Constants::PERMISSION_ALL);
347
+        $share = Server::get(\OCP\Share\IManager::class)->createShare($share);
348
+        Server::get(\OCP\Share\IManager::class)->acceptShare($share, self::TEST_VERSIONS_USER2);
349
+
350
+        self::loginHelper(self::TEST_VERSIONS_USER2);
351
+
352
+        $this->assertTrue(Filesystem::file_exists('folder1/test.txt'));
353
+
354
+        // execute rename hook of versions app
355
+        Filesystem::rename('/folder1/test.txt', '/folder1/folder2/test.txt');
356
+
357
+        $this->runCommands();
358
+
359
+        self::loginHelper(self::TEST_VERSIONS_USER);
360
+
361
+        $this->assertFalse($this->rootView->file_exists($v1), 'version 1 of old file does not exist');
362
+        $this->assertFalse($this->rootView->file_exists($v2), 'version 2 of old file does not exist');
363
+
364
+        $this->assertTrue($this->rootView->file_exists($v1Renamed), 'version 1 of renamed file exists');
365
+        $this->assertTrue($this->rootView->file_exists($v2Renamed), 'version 2 of renamed file exists');
366
+
367
+        Server::get(\OCP\Share\IManager::class)->deleteShare($share);
368
+    }
369
+
370
+    public function testMoveFolder(): void {
371
+        Filesystem::mkdir('folder1');
372
+        Filesystem::mkdir('folder2');
373
+        Filesystem::file_put_contents('folder1/test.txt', 'test file');
374
+
375
+        $t1 = time();
376
+        // second version is two weeks older, this way we make sure that no
377
+        // version will be expired
378
+        $t2 = $t1 - 60 * 60 * 24 * 14;
379
+
380
+        // create some versions
381
+        $this->rootView->mkdir(self::USERS_VERSIONS_ROOT . '/folder1');
382
+        $v1 = self::USERS_VERSIONS_ROOT . '/folder1/test.txt.v' . $t1;
383
+        $v2 = self::USERS_VERSIONS_ROOT . '/folder1/test.txt.v' . $t2;
384
+        $v1Renamed = self::USERS_VERSIONS_ROOT . '/folder2/folder1/test.txt.v' . $t1;
385
+        $v2Renamed = self::USERS_VERSIONS_ROOT . '/folder2/folder1/test.txt.v' . $t2;
386 386
 
387
-		$this->rootView->file_put_contents($v1, 'version1');
388
-		$this->rootView->file_put_contents($v2, 'version2');
387
+        $this->rootView->file_put_contents($v1, 'version1');
388
+        $this->rootView->file_put_contents($v2, 'version2');
389 389
 
390
-		// execute rename hook of versions app
391
-		Filesystem::rename('folder1', 'folder2/folder1');
390
+        // execute rename hook of versions app
391
+        Filesystem::rename('folder1', 'folder2/folder1');
392 392
 
393
-		$this->runCommands();
393
+        $this->runCommands();
394 394
 
395
-		$this->assertFalse($this->rootView->file_exists($v1));
396
-		$this->assertFalse($this->rootView->file_exists($v2));
395
+        $this->assertFalse($this->rootView->file_exists($v1));
396
+        $this->assertFalse($this->rootView->file_exists($v2));
397 397
 
398
-		$this->assertTrue($this->rootView->file_exists($v1Renamed));
399
-		$this->assertTrue($this->rootView->file_exists($v2Renamed));
400
-	}
398
+        $this->assertTrue($this->rootView->file_exists($v1Renamed));
399
+        $this->assertTrue($this->rootView->file_exists($v2Renamed));
400
+    }
401 401
 
402 402
 
403
-	public function testMoveFileIntoSharedFolderAsRecipient(): void {
404
-		Filesystem::mkdir('folder1');
405
-		$fileInfo = Filesystem::getFileInfo('folder1');
403
+    public function testMoveFileIntoSharedFolderAsRecipient(): void {
404
+        Filesystem::mkdir('folder1');
405
+        $fileInfo = Filesystem::getFileInfo('folder1');
406 406
 
407
-		$node = \OC::$server->getUserFolder(self::TEST_VERSIONS_USER)->get('folder1');
408
-		$share = Server::get(\OCP\Share\IManager::class)->newShare();
409
-		$share->setNode($node)
410
-			->setShareType(IShare::TYPE_USER)
411
-			->setSharedBy(self::TEST_VERSIONS_USER)
412
-			->setSharedWith(self::TEST_VERSIONS_USER2)
413
-			->setPermissions(Constants::PERMISSION_ALL);
414
-		$share = Server::get(\OCP\Share\IManager::class)->createShare($share);
415
-		Server::get(\OCP\Share\IManager::class)->acceptShare($share, self::TEST_VERSIONS_USER2);
407
+        $node = \OC::$server->getUserFolder(self::TEST_VERSIONS_USER)->get('folder1');
408
+        $share = Server::get(\OCP\Share\IManager::class)->newShare();
409
+        $share->setNode($node)
410
+            ->setShareType(IShare::TYPE_USER)
411
+            ->setSharedBy(self::TEST_VERSIONS_USER)
412
+            ->setSharedWith(self::TEST_VERSIONS_USER2)
413
+            ->setPermissions(Constants::PERMISSION_ALL);
414
+        $share = Server::get(\OCP\Share\IManager::class)->createShare($share);
415
+        Server::get(\OCP\Share\IManager::class)->acceptShare($share, self::TEST_VERSIONS_USER2);
416 416
 
417
-		self::loginHelper(self::TEST_VERSIONS_USER2);
418
-		$versionsFolder2 = '/' . self::TEST_VERSIONS_USER2 . '/files_versions';
419
-		Filesystem::file_put_contents('test.txt', 'test file');
417
+        self::loginHelper(self::TEST_VERSIONS_USER2);
418
+        $versionsFolder2 = '/' . self::TEST_VERSIONS_USER2 . '/files_versions';
419
+        Filesystem::file_put_contents('test.txt', 'test file');
420 420
 
421
-		$t1 = time();
422
-		// second version is two weeks older, this way we make sure that no
423
-		// version will be expired
424
-		$t2 = $t1 - 60 * 60 * 24 * 14;
421
+        $t1 = time();
422
+        // second version is two weeks older, this way we make sure that no
423
+        // version will be expired
424
+        $t2 = $t1 - 60 * 60 * 24 * 14;
425 425
 
426
-		$this->rootView->mkdir($versionsFolder2);
427
-		// create some versions
428
-		$v1 = $versionsFolder2 . '/test.txt.v' . $t1;
429
-		$v2 = $versionsFolder2 . '/test.txt.v' . $t2;
426
+        $this->rootView->mkdir($versionsFolder2);
427
+        // create some versions
428
+        $v1 = $versionsFolder2 . '/test.txt.v' . $t1;
429
+        $v2 = $versionsFolder2 . '/test.txt.v' . $t2;
430 430
 
431
-		$this->rootView->file_put_contents($v1, 'version1');
432
-		$this->rootView->file_put_contents($v2, 'version2');
431
+        $this->rootView->file_put_contents($v1, 'version1');
432
+        $this->rootView->file_put_contents($v2, 'version2');
433 433
 
434
-		// move file into the shared folder as recipient
435
-		$success = Filesystem::rename('/test.txt', '/folder1/test.txt');
434
+        // move file into the shared folder as recipient
435
+        $success = Filesystem::rename('/test.txt', '/folder1/test.txt');
436 436
 
437
-		$this->assertTrue($success);
438
-		$this->assertFalse($this->rootView->file_exists($v1));
439
-		$this->assertFalse($this->rootView->file_exists($v2));
437
+        $this->assertTrue($success);
438
+        $this->assertFalse($this->rootView->file_exists($v1));
439
+        $this->assertFalse($this->rootView->file_exists($v2));
440 440
 
441
-		self::loginHelper(self::TEST_VERSIONS_USER);
441
+        self::loginHelper(self::TEST_VERSIONS_USER);
442 442
 
443
-		$versionsFolder1 = '/' . self::TEST_VERSIONS_USER . '/files_versions';
443
+        $versionsFolder1 = '/' . self::TEST_VERSIONS_USER . '/files_versions';
444 444
 
445
-		$v1Renamed = $versionsFolder1 . '/folder1/test.txt.v' . $t1;
446
-		$v2Renamed = $versionsFolder1 . '/folder1/test.txt.v' . $t2;
445
+        $v1Renamed = $versionsFolder1 . '/folder1/test.txt.v' . $t1;
446
+        $v2Renamed = $versionsFolder1 . '/folder1/test.txt.v' . $t2;
447 447
 
448
-		$this->assertTrue($this->rootView->file_exists($v1Renamed));
449
-		$this->assertTrue($this->rootView->file_exists($v2Renamed));
448
+        $this->assertTrue($this->rootView->file_exists($v1Renamed));
449
+        $this->assertTrue($this->rootView->file_exists($v2Renamed));
450 450
 
451
-		Server::get(\OCP\Share\IManager::class)->deleteShare($share);
452
-	}
451
+        Server::get(\OCP\Share\IManager::class)->deleteShare($share);
452
+    }
453 453
 
454
-	public function testMoveFolderIntoSharedFolderAsRecipient(): void {
455
-		Filesystem::mkdir('folder1');
454
+    public function testMoveFolderIntoSharedFolderAsRecipient(): void {
455
+        Filesystem::mkdir('folder1');
456 456
 
457
-		$node = \OC::$server->getUserFolder(self::TEST_VERSIONS_USER)->get('folder1');
458
-		$share = Server::get(\OCP\Share\IManager::class)->newShare();
459
-		$share->setNode($node)
460
-			->setShareType(IShare::TYPE_USER)
461
-			->setSharedBy(self::TEST_VERSIONS_USER)
462
-			->setSharedWith(self::TEST_VERSIONS_USER2)
463
-			->setPermissions(Constants::PERMISSION_ALL);
464
-		$share = Server::get(\OCP\Share\IManager::class)->createShare($share);
465
-		Server::get(\OCP\Share\IManager::class)->acceptShare($share, self::TEST_VERSIONS_USER2);
457
+        $node = \OC::$server->getUserFolder(self::TEST_VERSIONS_USER)->get('folder1');
458
+        $share = Server::get(\OCP\Share\IManager::class)->newShare();
459
+        $share->setNode($node)
460
+            ->setShareType(IShare::TYPE_USER)
461
+            ->setSharedBy(self::TEST_VERSIONS_USER)
462
+            ->setSharedWith(self::TEST_VERSIONS_USER2)
463
+            ->setPermissions(Constants::PERMISSION_ALL);
464
+        $share = Server::get(\OCP\Share\IManager::class)->createShare($share);
465
+        Server::get(\OCP\Share\IManager::class)->acceptShare($share, self::TEST_VERSIONS_USER2);
466 466
 
467
-		self::loginHelper(self::TEST_VERSIONS_USER2);
468
-		$versionsFolder2 = '/' . self::TEST_VERSIONS_USER2 . '/files_versions';
469
-		Filesystem::mkdir('folder2');
470
-		Filesystem::file_put_contents('folder2/test.txt', 'test file');
467
+        self::loginHelper(self::TEST_VERSIONS_USER2);
468
+        $versionsFolder2 = '/' . self::TEST_VERSIONS_USER2 . '/files_versions';
469
+        Filesystem::mkdir('folder2');
470
+        Filesystem::file_put_contents('folder2/test.txt', 'test file');
471 471
 
472
-		$t1 = time();
473
-		// second version is two weeks older, this way we make sure that no
474
-		// version will be expired
475
-		$t2 = $t1 - 60 * 60 * 24 * 14;
472
+        $t1 = time();
473
+        // second version is two weeks older, this way we make sure that no
474
+        // version will be expired
475
+        $t2 = $t1 - 60 * 60 * 24 * 14;
476 476
 
477
-		$this->rootView->mkdir($versionsFolder2);
478
-		$this->rootView->mkdir($versionsFolder2 . '/folder2');
479
-		// create some versions
480
-		$v1 = $versionsFolder2 . '/folder2/test.txt.v' . $t1;
481
-		$v2 = $versionsFolder2 . '/folder2/test.txt.v' . $t2;
477
+        $this->rootView->mkdir($versionsFolder2);
478
+        $this->rootView->mkdir($versionsFolder2 . '/folder2');
479
+        // create some versions
480
+        $v1 = $versionsFolder2 . '/folder2/test.txt.v' . $t1;
481
+        $v2 = $versionsFolder2 . '/folder2/test.txt.v' . $t2;
482 482
 
483
-		$this->rootView->file_put_contents($v1, 'version1');
484
-		$this->rootView->file_put_contents($v2, 'version2');
483
+        $this->rootView->file_put_contents($v1, 'version1');
484
+        $this->rootView->file_put_contents($v2, 'version2');
485 485
 
486
-		// move file into the shared folder as recipient
487
-		Filesystem::rename('/folder2', '/folder1/folder2');
486
+        // move file into the shared folder as recipient
487
+        Filesystem::rename('/folder2', '/folder1/folder2');
488 488
 
489
-		$this->assertFalse($this->rootView->file_exists($v1));
490
-		$this->assertFalse($this->rootView->file_exists($v2));
489
+        $this->assertFalse($this->rootView->file_exists($v1));
490
+        $this->assertFalse($this->rootView->file_exists($v2));
491 491
 
492
-		self::loginHelper(self::TEST_VERSIONS_USER);
492
+        self::loginHelper(self::TEST_VERSIONS_USER);
493 493
 
494
-		$versionsFolder1 = '/' . self::TEST_VERSIONS_USER . '/files_versions';
494
+        $versionsFolder1 = '/' . self::TEST_VERSIONS_USER . '/files_versions';
495 495
 
496
-		$v1Renamed = $versionsFolder1 . '/folder1/folder2/test.txt.v' . $t1;
497
-		$v2Renamed = $versionsFolder1 . '/folder1/folder2/test.txt.v' . $t2;
496
+        $v1Renamed = $versionsFolder1 . '/folder1/folder2/test.txt.v' . $t1;
497
+        $v2Renamed = $versionsFolder1 . '/folder1/folder2/test.txt.v' . $t2;
498 498
 
499
-		$this->assertTrue($this->rootView->file_exists($v1Renamed));
500
-		$this->assertTrue($this->rootView->file_exists($v2Renamed));
499
+        $this->assertTrue($this->rootView->file_exists($v1Renamed));
500
+        $this->assertTrue($this->rootView->file_exists($v2Renamed));
501 501
 
502
-		Server::get(\OCP\Share\IManager::class)->deleteShare($share);
503
-	}
502
+        Server::get(\OCP\Share\IManager::class)->deleteShare($share);
503
+    }
504 504
 
505
-	public function testRenameSharedFile(): void {
506
-		Filesystem::file_put_contents('test.txt', 'test file');
505
+    public function testRenameSharedFile(): void {
506
+        Filesystem::file_put_contents('test.txt', 'test file');
507 507
 
508
-		$t1 = time();
509
-		// second version is two weeks older, this way we make sure that no
510
-		// version will be expired
511
-		$t2 = $t1 - 60 * 60 * 24 * 14;
508
+        $t1 = time();
509
+        // second version is two weeks older, this way we make sure that no
510
+        // version will be expired
511
+        $t2 = $t1 - 60 * 60 * 24 * 14;
512 512
 
513
-		$this->rootView->mkdir(self::USERS_VERSIONS_ROOT);
514
-		// create some versions
515
-		$v1 = self::USERS_VERSIONS_ROOT . '/test.txt.v' . $t1;
516
-		$v2 = self::USERS_VERSIONS_ROOT . '/test.txt.v' . $t2;
517
-		// the renamed versions should not exist! Because we only moved the mount point!
518
-		$v1Renamed = self::USERS_VERSIONS_ROOT . '/test2.txt.v' . $t1;
519
-		$v2Renamed = self::USERS_VERSIONS_ROOT . '/test2.txt.v' . $t2;
513
+        $this->rootView->mkdir(self::USERS_VERSIONS_ROOT);
514
+        // create some versions
515
+        $v1 = self::USERS_VERSIONS_ROOT . '/test.txt.v' . $t1;
516
+        $v2 = self::USERS_VERSIONS_ROOT . '/test.txt.v' . $t2;
517
+        // the renamed versions should not exist! Because we only moved the mount point!
518
+        $v1Renamed = self::USERS_VERSIONS_ROOT . '/test2.txt.v' . $t1;
519
+        $v2Renamed = self::USERS_VERSIONS_ROOT . '/test2.txt.v' . $t2;
520 520
 
521
-		$this->rootView->file_put_contents($v1, 'version1');
522
-		$this->rootView->file_put_contents($v2, 'version2');
521
+        $this->rootView->file_put_contents($v1, 'version1');
522
+        $this->rootView->file_put_contents($v2, 'version2');
523 523
 
524
-		$node = \OC::$server->getUserFolder(self::TEST_VERSIONS_USER)->get('test.txt');
525
-		$share = Server::get(\OCP\Share\IManager::class)->newShare();
526
-		$share->setNode($node)
527
-			->setShareType(IShare::TYPE_USER)
528
-			->setSharedBy(self::TEST_VERSIONS_USER)
529
-			->setSharedWith(self::TEST_VERSIONS_USER2)
530
-			->setPermissions(Constants::PERMISSION_READ | Constants::PERMISSION_UPDATE | Constants::PERMISSION_SHARE);
531
-		$share = Server::get(\OCP\Share\IManager::class)->createShare($share);
532
-		Server::get(\OCP\Share\IManager::class)->acceptShare($share, self::TEST_VERSIONS_USER2);
524
+        $node = \OC::$server->getUserFolder(self::TEST_VERSIONS_USER)->get('test.txt');
525
+        $share = Server::get(\OCP\Share\IManager::class)->newShare();
526
+        $share->setNode($node)
527
+            ->setShareType(IShare::TYPE_USER)
528
+            ->setSharedBy(self::TEST_VERSIONS_USER)
529
+            ->setSharedWith(self::TEST_VERSIONS_USER2)
530
+            ->setPermissions(Constants::PERMISSION_READ | Constants::PERMISSION_UPDATE | Constants::PERMISSION_SHARE);
531
+        $share = Server::get(\OCP\Share\IManager::class)->createShare($share);
532
+        Server::get(\OCP\Share\IManager::class)->acceptShare($share, self::TEST_VERSIONS_USER2);
533 533
 
534
-		self::loginHelper(self::TEST_VERSIONS_USER2);
534
+        self::loginHelper(self::TEST_VERSIONS_USER2);
535 535
 
536
-		$this->assertTrue(Filesystem::file_exists('test.txt'));
536
+        $this->assertTrue(Filesystem::file_exists('test.txt'));
537 537
 
538
-		// execute rename hook of versions app
539
-		Filesystem::rename('test.txt', 'test2.txt');
538
+        // execute rename hook of versions app
539
+        Filesystem::rename('test.txt', 'test2.txt');
540 540
 
541
-		self::loginHelper(self::TEST_VERSIONS_USER);
541
+        self::loginHelper(self::TEST_VERSIONS_USER);
542 542
 
543
-		$this->runCommands();
543
+        $this->runCommands();
544 544
 
545
-		$this->assertTrue($this->rootView->file_exists($v1));
546
-		$this->assertTrue($this->rootView->file_exists($v2));
545
+        $this->assertTrue($this->rootView->file_exists($v1));
546
+        $this->assertTrue($this->rootView->file_exists($v2));
547 547
 
548
-		$this->assertFalse($this->rootView->file_exists($v1Renamed));
549
-		$this->assertFalse($this->rootView->file_exists($v2Renamed));
548
+        $this->assertFalse($this->rootView->file_exists($v1Renamed));
549
+        $this->assertFalse($this->rootView->file_exists($v2Renamed));
550 550
 
551
-		Server::get(\OCP\Share\IManager::class)->deleteShare($share);
552
-	}
551
+        Server::get(\OCP\Share\IManager::class)->deleteShare($share);
552
+    }
553 553
 
554
-	public function testCopy(): void {
555
-		Filesystem::file_put_contents('test.txt', 'test file');
554
+    public function testCopy(): void {
555
+        Filesystem::file_put_contents('test.txt', 'test file');
556 556
 
557
-		$t1 = time();
558
-		// second version is two weeks older, this way we make sure that no
559
-		// version will be expired
560
-		$t2 = $t1 - 60 * 60 * 24 * 14;
557
+        $t1 = time();
558
+        // second version is two weeks older, this way we make sure that no
559
+        // version will be expired
560
+        $t2 = $t1 - 60 * 60 * 24 * 14;
561 561
 
562
-		// create some versions
563
-		$v1 = self::USERS_VERSIONS_ROOT . '/test.txt.v' . $t1;
564
-		$v2 = self::USERS_VERSIONS_ROOT . '/test.txt.v' . $t2;
565
-		$v1Copied = self::USERS_VERSIONS_ROOT . '/test2.txt.v' . $t1;
566
-		$v2Copied = self::USERS_VERSIONS_ROOT . '/test2.txt.v' . $t2;
562
+        // create some versions
563
+        $v1 = self::USERS_VERSIONS_ROOT . '/test.txt.v' . $t1;
564
+        $v2 = self::USERS_VERSIONS_ROOT . '/test.txt.v' . $t2;
565
+        $v1Copied = self::USERS_VERSIONS_ROOT . '/test2.txt.v' . $t1;
566
+        $v2Copied = self::USERS_VERSIONS_ROOT . '/test2.txt.v' . $t2;
567 567
 
568
-		$this->rootView->file_put_contents($v1, 'version1');
569
-		$this->rootView->file_put_contents($v2, 'version2');
568
+        $this->rootView->file_put_contents($v1, 'version1');
569
+        $this->rootView->file_put_contents($v2, 'version2');
570 570
 
571
-		// execute copy hook of versions app
572
-		Filesystem::copy('test.txt', 'test2.txt');
571
+        // execute copy hook of versions app
572
+        Filesystem::copy('test.txt', 'test2.txt');
573 573
 
574
-		$this->runCommands();
574
+        $this->runCommands();
575 575
 
576
-		$this->assertTrue($this->rootView->file_exists($v1), 'version 1 of original file exists');
577
-		$this->assertTrue($this->rootView->file_exists($v2), 'version 2 of original file exists');
576
+        $this->assertTrue($this->rootView->file_exists($v1), 'version 1 of original file exists');
577
+        $this->assertTrue($this->rootView->file_exists($v2), 'version 2 of original file exists');
578 578
 
579
-		$this->assertTrue($this->rootView->file_exists($v1Copied), 'version 1 of copied file exists');
580
-		$this->assertTrue($this->rootView->file_exists($v2Copied), 'version 2 of copied file exists');
581
-	}
579
+        $this->assertTrue($this->rootView->file_exists($v1Copied), 'version 1 of copied file exists');
580
+        $this->assertTrue($this->rootView->file_exists($v2Copied), 'version 2 of copied file exists');
581
+    }
582 582
 
583
-	/**
584
-	 * test if we find all versions and if the versions array contain
585
-	 * the correct 'path' and 'name'
586
-	 */
587
-	public function testGetVersions(): void {
588
-		$t1 = time();
589
-		// second version is two weeks older, this way we make sure that no
590
-		// version will be expired
591
-		$t2 = $t1 - 60 * 60 * 24 * 14;
583
+    /**
584
+     * test if we find all versions and if the versions array contain
585
+     * the correct 'path' and 'name'
586
+     */
587
+    public function testGetVersions(): void {
588
+        $t1 = time();
589
+        // second version is two weeks older, this way we make sure that no
590
+        // version will be expired
591
+        $t2 = $t1 - 60 * 60 * 24 * 14;
592 592
 
593
-		// create some versions
594
-		$v1 = self::USERS_VERSIONS_ROOT . '/subfolder/test.txt.v' . $t1;
595
-		$v2 = self::USERS_VERSIONS_ROOT . '/subfolder/test.txt.v' . $t2;
593
+        // create some versions
594
+        $v1 = self::USERS_VERSIONS_ROOT . '/subfolder/test.txt.v' . $t1;
595
+        $v2 = self::USERS_VERSIONS_ROOT . '/subfolder/test.txt.v' . $t2;
596 596
 
597
-		$this->rootView->mkdir(self::USERS_VERSIONS_ROOT . '/subfolder/');
597
+        $this->rootView->mkdir(self::USERS_VERSIONS_ROOT . '/subfolder/');
598 598
 
599
-		$this->rootView->file_put_contents($v1, 'version1');
600
-		$this->rootView->file_put_contents($v2, 'version2');
599
+        $this->rootView->file_put_contents($v1, 'version1');
600
+        $this->rootView->file_put_contents($v2, 'version2');
601 601
 
602
-		// execute copy hook of versions app
603
-		$versions = Storage::getVersions(self::TEST_VERSIONS_USER, '/subfolder/test.txt');
602
+        // execute copy hook of versions app
603
+        $versions = Storage::getVersions(self::TEST_VERSIONS_USER, '/subfolder/test.txt');
604 604
 
605
-		$this->assertCount(2, $versions);
605
+        $this->assertCount(2, $versions);
606 606
 
607
-		foreach ($versions as $version) {
608
-			$this->assertSame('/subfolder/test.txt', $version['path']);
609
-			$this->assertSame('test.txt', $version['name']);
610
-		}
607
+        foreach ($versions as $version) {
608
+            $this->assertSame('/subfolder/test.txt', $version['path']);
609
+            $this->assertSame('test.txt', $version['name']);
610
+        }
611 611
 
612
-		//cleanup
613
-		$this->rootView->deleteAll(self::USERS_VERSIONS_ROOT . '/subfolder');
614
-	}
612
+        //cleanup
613
+        $this->rootView->deleteAll(self::USERS_VERSIONS_ROOT . '/subfolder');
614
+    }
615 615
 
616
-	/**
617
-	 * test if we find all versions and if the versions array contain
618
-	 * the correct 'path' and 'name'
619
-	 */
620
-	public function testGetVersionsEmptyFile(): void {
621
-		// execute copy hook of versions app
622
-		$versions = Storage::getVersions(self::TEST_VERSIONS_USER, '');
623
-		$this->assertCount(0, $versions);
616
+    /**
617
+     * test if we find all versions and if the versions array contain
618
+     * the correct 'path' and 'name'
619
+     */
620
+    public function testGetVersionsEmptyFile(): void {
621
+        // execute copy hook of versions app
622
+        $versions = Storage::getVersions(self::TEST_VERSIONS_USER, '');
623
+        $this->assertCount(0, $versions);
624 624
 
625
-		$versions = Storage::getVersions(self::TEST_VERSIONS_USER, null);
626
-		$this->assertCount(0, $versions);
627
-	}
625
+        $versions = Storage::getVersions(self::TEST_VERSIONS_USER, null);
626
+        $this->assertCount(0, $versions);
627
+    }
628 628
 
629
-	public function testExpireNonexistingFile(): void {
630
-		$this->logout();
631
-		// needed to have a FS setup (the background job does this)
632
-		\OC_Util::setupFS(self::TEST_VERSIONS_USER);
629
+    public function testExpireNonexistingFile(): void {
630
+        $this->logout();
631
+        // needed to have a FS setup (the background job does this)
632
+        \OC_Util::setupFS(self::TEST_VERSIONS_USER);
633 633
 
634
-		$this->assertFalse(Storage::expire('/void/unexist.txt', self::TEST_VERSIONS_USER));
635
-	}
634
+        $this->assertFalse(Storage::expire('/void/unexist.txt', self::TEST_VERSIONS_USER));
635
+    }
636 636
 
637 637
 
638
-	public function testExpireNonexistingUser(): void {
639
-		$this->expectException(NoUserException::class);
638
+    public function testExpireNonexistingUser(): void {
639
+        $this->expectException(NoUserException::class);
640 640
 
641
-		$this->logout();
642
-		// needed to have a FS setup (the background job does this)
643
-		\OC_Util::setupFS(self::TEST_VERSIONS_USER);
644
-		Filesystem::file_put_contents('test.txt', 'test file');
641
+        $this->logout();
642
+        // needed to have a FS setup (the background job does this)
643
+        \OC_Util::setupFS(self::TEST_VERSIONS_USER);
644
+        Filesystem::file_put_contents('test.txt', 'test file');
645 645
 
646
-		$this->assertFalse(Storage::expire('test.txt', 'unexist'));
647
-	}
646
+        $this->assertFalse(Storage::expire('test.txt', 'unexist'));
647
+    }
648 648
 
649
-	public function testRestoreSameStorage(): void {
650
-		Filesystem::mkdir('sub');
651
-		$this->doTestRestore();
652
-	}
649
+    public function testRestoreSameStorage(): void {
650
+        Filesystem::mkdir('sub');
651
+        $this->doTestRestore();
652
+    }
653 653
 
654
-	public function testRestoreCrossStorage(): void {
655
-		$storage2 = new Temporary([]);
656
-		Filesystem::mount($storage2, [], self::TEST_VERSIONS_USER . '/files/sub');
654
+    public function testRestoreCrossStorage(): void {
655
+        $storage2 = new Temporary([]);
656
+        Filesystem::mount($storage2, [], self::TEST_VERSIONS_USER . '/files/sub');
657 657
 
658
-		$this->doTestRestore();
659
-	}
658
+        $this->doTestRestore();
659
+    }
660 660
 
661
-	public function testRestoreNoPermission(): void {
662
-		$this->loginAsUser(self::TEST_VERSIONS_USER);
661
+    public function testRestoreNoPermission(): void {
662
+        $this->loginAsUser(self::TEST_VERSIONS_USER);
663 663
 
664
-		$userHome = \OC::$server->getUserFolder(self::TEST_VERSIONS_USER);
665
-		$node = $userHome->newFolder('folder');
666
-		$file = $node->newFile('test.txt');
664
+        $userHome = \OC::$server->getUserFolder(self::TEST_VERSIONS_USER);
665
+        $node = $userHome->newFolder('folder');
666
+        $file = $node->newFile('test.txt');
667 667
 
668
-		$share = Server::get(\OCP\Share\IManager::class)->newShare();
669
-		$share->setNode($node)
670
-			->setShareType(IShare::TYPE_USER)
671
-			->setSharedBy(self::TEST_VERSIONS_USER)
672
-			->setSharedWith(self::TEST_VERSIONS_USER2)
673
-			->setPermissions(Constants::PERMISSION_READ);
674
-		$share = Server::get(\OCP\Share\IManager::class)->createShare($share);
675
-		Server::get(\OCP\Share\IManager::class)->acceptShare($share, self::TEST_VERSIONS_USER2);
676
-
677
-		$versions = $this->createAndCheckVersions(
678
-			Filesystem::getView(),
679
-			'folder/test.txt'
680
-		);
681
-
682
-		$file->putContent('test file');
683
-
684
-		$this->loginAsUser(self::TEST_VERSIONS_USER2);
668
+        $share = Server::get(\OCP\Share\IManager::class)->newShare();
669
+        $share->setNode($node)
670
+            ->setShareType(IShare::TYPE_USER)
671
+            ->setSharedBy(self::TEST_VERSIONS_USER)
672
+            ->setSharedWith(self::TEST_VERSIONS_USER2)
673
+            ->setPermissions(Constants::PERMISSION_READ);
674
+        $share = Server::get(\OCP\Share\IManager::class)->createShare($share);
675
+        Server::get(\OCP\Share\IManager::class)->acceptShare($share, self::TEST_VERSIONS_USER2);
676
+
677
+        $versions = $this->createAndCheckVersions(
678
+            Filesystem::getView(),
679
+            'folder/test.txt'
680
+        );
681
+
682
+        $file->putContent('test file');
683
+
684
+        $this->loginAsUser(self::TEST_VERSIONS_USER2);
685 685
 
686
-		$firstVersion = current($versions);
686
+        $firstVersion = current($versions);
687 687
 
688
-		$this->assertFalse(Storage::rollback('folder/test.txt', (int)$firstVersion['version'], $this->user2), 'Revert did not happen');
688
+        $this->assertFalse(Storage::rollback('folder/test.txt', (int)$firstVersion['version'], $this->user2), 'Revert did not happen');
689 689
 
690
-		$this->loginAsUser(self::TEST_VERSIONS_USER);
690
+        $this->loginAsUser(self::TEST_VERSIONS_USER);
691 691
 
692
-		Server::get(\OCP\Share\IManager::class)->deleteShare($share);
693
-		$this->assertEquals('test file', $file->getContent(), 'File content has not changed');
694
-	}
692
+        Server::get(\OCP\Share\IManager::class)->deleteShare($share);
693
+        $this->assertEquals('test file', $file->getContent(), 'File content has not changed');
694
+    }
695 695
 
696
-	public function testRestoreMovedShare(): void {
697
-		$this->markTestSkipped('Unreliable test');
698
-		$this->loginAsUser(self::TEST_VERSIONS_USER);
699
-
700
-		$userHome = \OC::$server->getUserFolder(self::TEST_VERSIONS_USER);
701
-		$node = $userHome->newFolder('folder');
702
-		$file = $node->newFile('test.txt');
703
-
704
-		$userHome2 = \OC::$server->getUserFolder(self::TEST_VERSIONS_USER2);
705
-		$userHome2->newFolder('subfolder');
706
-
707
-		$share = Server::get(\OCP\Share\IManager::class)->newShare();
708
-		$share->setNode($node)
709
-			->setShareType(IShare::TYPE_USER)
710
-			->setSharedBy(self::TEST_VERSIONS_USER)
711
-			->setSharedWith(self::TEST_VERSIONS_USER2)
712
-			->setPermissions(Constants::PERMISSION_ALL);
713
-		$share = Server::get(\OCP\Share\IManager::class)->createShare($share);
714
-		$shareManager = Server::get(\OCP\Share\IManager::class);
715
-		$shareManager->acceptShare($share, self::TEST_VERSIONS_USER2);
716
-
717
-		$share->setTarget('subfolder/folder');
718
-		$shareManager->moveShare($share, self::TEST_VERSIONS_USER2);
719
-
720
-		$versions = $this->createAndCheckVersions(
721
-			Filesystem::getView(),
722
-			'folder/test.txt'
723
-		);
724
-
725
-		$file->putContent('test file');
726
-
727
-		$this->loginAsUser(self::TEST_VERSIONS_USER2);
728
-
729
-		$firstVersion = current($versions);
730
-
731
-		$this->assertTrue(Storage::rollback('folder/test.txt', $firstVersion['version'], $this->user1));
732
-
733
-		$this->loginAsUser(self::TEST_VERSIONS_USER);
734
-
735
-		Server::get(\OCP\Share\IManager::class)->deleteShare($share);
736
-		$this->assertEquals('version 2', $file->getContent(), 'File content has not changed');
737
-	}
738
-
739
-	/**
740
-	 * @param string $hookName name of hook called
741
-	 * @param string $params variable to receive parameters provided by hook
742
-	 */
743
-	private function connectMockHooks($hookName, &$params) {
744
-		if ($hookName === null) {
745
-			return;
746
-		}
747
-
748
-		$eventHandler = $this->getMockBuilder(DummyHookListener::class)
749
-			->onlyMethods(['callback'])
750
-			->getMock();
751
-
752
-		$eventHandler->expects($this->any())
753
-			->method('callback')
754
-			->willReturnCallback(
755
-				function ($p) use (&$params): void {
756
-					$params = $p;
757
-				}
758
-			);
759
-
760
-		Util::connectHook(
761
-			'\OCP\Versions',
762
-			$hookName,
763
-			$eventHandler,
764
-			'callback'
765
-		);
766
-	}
767
-
768
-	private function doTestRestore(): void {
769
-		$filePath = self::TEST_VERSIONS_USER . '/files/sub/test.txt';
770
-		$this->rootView->file_put_contents($filePath, 'test file');
771
-
772
-		$fileInfo = $this->rootView->getFileInfo($filePath);
773
-		$t0 = $this->rootView->filemtime($filePath);
774
-
775
-		// not exactly the same timestamp as the file
776
-		$t1 = time() - 60;
777
-		// second version is two weeks older
778
-		$t2 = $t1 - 60 * 60 * 24 * 14;
779
-
780
-		// create some versions
781
-		$v1 = self::USERS_VERSIONS_ROOT . '/sub/test.txt.v' . $t1;
782
-		$v2 = self::USERS_VERSIONS_ROOT . '/sub/test.txt.v' . $t2;
783
-
784
-		$this->rootView->mkdir(self::USERS_VERSIONS_ROOT . '/sub');
785
-
786
-		$this->rootView->file_put_contents($v1, 'version1');
787
-		$fileInfoV1 = $this->rootView->getFileInfo($v1);
788
-		$versionEntity = new VersionEntity();
789
-		$versionEntity->setFileId($fileInfo->getId());
790
-		$versionEntity->setTimestamp($t1);
791
-		$versionEntity->setSize($fileInfoV1->getSize());
792
-		$versionEntity->setMimetype($this->mimeTypeLoader->getId($fileInfoV1->getMimetype()));
793
-		$versionEntity->setMetadata([]);
794
-		$this->versionsMapper->insert($versionEntity);
795
-
796
-		$this->rootView->file_put_contents($v2, 'version2');
797
-		$fileInfoV2 = $this->rootView->getFileInfo($v2);
798
-		$versionEntity = new VersionEntity();
799
-		$versionEntity->setFileId($fileInfo->getId());
800
-		$versionEntity->setTimestamp($t2);
801
-		$versionEntity->setSize($fileInfoV2->getSize());
802
-		$versionEntity->setMimetype($this->mimeTypeLoader->getId($fileInfoV2->getMimetype()));
803
-		$versionEntity->setMetadata([]);
804
-		$this->versionsMapper->insert($versionEntity);
805
-
806
-		$oldVersions = Storage::getVersions(
807
-			self::TEST_VERSIONS_USER, '/sub/test.txt'
808
-		);
809
-
810
-		$this->assertCount(2, $oldVersions);
811
-
812
-		$this->assertEquals('test file', $this->rootView->file_get_contents($filePath));
813
-		$info1 = $this->rootView->getFileInfo($filePath);
814
-
815
-		$eventDispatcher = Server::get(IEventDispatcher::class);
816
-		$eventFired = false;
817
-		$eventDispatcher->addListener(VersionRestoredEvent::class, function ($event) use (&$eventFired, $t2): void {
818
-			$eventFired = true;
819
-			$this->assertEquals('/sub/test.txt', $event->getVersion()->getVersionPath());
820
-			$this->assertTrue($event->getVersion()->getRevisionId() > 0);
821
-		});
822
-
823
-		$versionManager = Server::get(IVersionManager::class);
824
-		$versions = $versionManager->getVersionsForFile($this->user1, $info1);
825
-		$version = array_filter($versions, function ($version) use ($t2) {
826
-			return $version->getRevisionId() === $t2;
827
-		});
828
-		$this->assertTrue($versionManager->rollback(current($version)));
829
-
830
-		$this->assertTrue($eventFired, 'VersionRestoredEvent was not fired');
831
-
832
-		$this->assertEquals('version2', $this->rootView->file_get_contents($filePath));
833
-		$info2 = $this->rootView->getFileInfo($filePath);
834
-
835
-		$this->assertNotEquals(
836
-			$info2['etag'],
837
-			$info1['etag'],
838
-			'Etag must change after rolling back version'
839
-		);
840
-		$this->assertEquals(
841
-			$info2['fileid'],
842
-			$info1['fileid'],
843
-			'File id must not change after rolling back version'
844
-		);
845
-		$this->assertEquals(
846
-			$info2['mtime'],
847
-			$t2,
848
-			'Restored file has mtime from version'
849
-		);
850
-
851
-		$newVersions = Storage::getVersions(
852
-			self::TEST_VERSIONS_USER, '/sub/test.txt'
853
-		);
854
-
855
-		$this->assertTrue(
856
-			$this->rootView->file_exists(self::USERS_VERSIONS_ROOT . '/sub/test.txt.v' . $t0),
857
-			'A version file was created for the file before restoration'
858
-		);
859
-		$this->assertTrue(
860
-			$this->rootView->file_exists($v1),
861
-			'Untouched version file is still there'
862
-		);
863
-		$this->assertFalse(
864
-			$this->rootView->file_exists($v2),
865
-			'Restored version file gone from files_version folder'
866
-		);
867
-
868
-		$this->assertCount(2, $newVersions, 'Additional version created');
869
-
870
-		$this->assertTrue(
871
-			isset($newVersions[$t0 . '#' . 'test.txt']),
872
-			'A version was created for the file before restoration'
873
-		);
874
-		$this->assertTrue(
875
-			isset($newVersions[$t1 . '#' . 'test.txt']),
876
-			'Untouched version is still there'
877
-		);
878
-		$this->assertFalse(
879
-			isset($newVersions[$t2 . '#' . 'test.txt']),
880
-			'Restored version is not in the list any more'
881
-		);
882
-	}
883
-
884
-	/**
885
-	 * Test whether versions are created when overwriting as owner
886
-	 */
887
-	public function testStoreVersionAsOwner(): void {
888
-		$this->loginAsUser(self::TEST_VERSIONS_USER);
889
-
890
-		$this->createAndCheckVersions(
891
-			Filesystem::getView(),
892
-			'test.txt'
893
-		);
894
-	}
895
-
896
-	/**
897
-	 * Test whether versions are created when overwriting as share recipient
898
-	 */
899
-	public function testStoreVersionAsRecipient(): void {
900
-		$this->loginAsUser(self::TEST_VERSIONS_USER);
901
-
902
-		Filesystem::mkdir('folder');
903
-		Filesystem::file_put_contents('folder/test.txt', 'test file');
904
-
905
-		$node = \OC::$server->getUserFolder(self::TEST_VERSIONS_USER)->get('folder');
906
-		$share = Server::get(\OCP\Share\IManager::class)->newShare();
907
-		$share->setNode($node)
908
-			->setShareType(IShare::TYPE_USER)
909
-			->setSharedBy(self::TEST_VERSIONS_USER)
910
-			->setSharedWith(self::TEST_VERSIONS_USER2)
911
-			->setPermissions(Constants::PERMISSION_ALL);
912
-		$share = Server::get(\OCP\Share\IManager::class)->createShare($share);
913
-		Server::get(\OCP\Share\IManager::class)->acceptShare($share, self::TEST_VERSIONS_USER2);
914
-
915
-		$this->loginAsUser(self::TEST_VERSIONS_USER2);
916
-
917
-		$this->createAndCheckVersions(
918
-			Filesystem::getView(),
919
-			'folder/test.txt'
920
-		);
921
-
922
-		Server::get(\OCP\Share\IManager::class)->deleteShare($share);
923
-	}
924
-
925
-	/**
926
-	 * Test whether versions are created when overwriting anonymously.
927
-	 *
928
-	 * When uploading through a public link or publicwebdav, no user
929
-	 * is logged in. File modification must still be able to find
930
-	 * the owner and create versions.
931
-	 */
932
-	public function testStoreVersionAsAnonymous(): void {
933
-		$this->logout();
934
-
935
-		// note: public link upload does this,
936
-		// needed to make the hooks fire
937
-		\OC_Util::setupFS(self::TEST_VERSIONS_USER);
938
-
939
-		$userView = new View('/' . self::TEST_VERSIONS_USER . '/files');
940
-		$this->createAndCheckVersions(
941
-			$userView,
942
-			'test.txt'
943
-		);
944
-	}
945
-
946
-	private function createAndCheckVersions(View $view, string $path): array {
947
-		$view->file_put_contents($path, 'test file');
948
-		$view->file_put_contents($path, 'version 1');
949
-		$view->file_put_contents($path, 'version 2');
950
-
951
-		$this->loginAsUser(self::TEST_VERSIONS_USER);
952
-
953
-		// need to scan for the versions
954
-		[$rootStorage,] = $this->rootView->resolvePath(self::TEST_VERSIONS_USER . '/files_versions');
955
-		$rootStorage->getScanner()->scan('files_versions');
956
-
957
-		$versions = Storage::getVersions(
958
-			self::TEST_VERSIONS_USER, '/' . $path
959
-		);
960
-
961
-		// note: we cannot predict how many versions are created due to
962
-		// test run timing
963
-		$this->assertGreaterThan(0, count($versions));
964
-
965
-		return $versions;
966
-	}
967
-
968
-	public static function loginHelper(string $user, bool $create = false) {
969
-		if ($create) {
970
-			$backend = new \Test\Util\User\Dummy();
971
-			$backend->createUser($user, $user);
972
-			Server::get(IUserManager::class)->registerBackend($backend);
973
-		}
974
-
975
-		\OC_Util::tearDownFS();
976
-		\OC_User::setUserId('');
977
-		Filesystem::tearDown();
978
-		\OC_User::setUserId($user);
979
-		\OC_Util::setupFS($user);
980
-		\OC::$server->getUserFolder($user);
981
-	}
696
+    public function testRestoreMovedShare(): void {
697
+        $this->markTestSkipped('Unreliable test');
698
+        $this->loginAsUser(self::TEST_VERSIONS_USER);
699
+
700
+        $userHome = \OC::$server->getUserFolder(self::TEST_VERSIONS_USER);
701
+        $node = $userHome->newFolder('folder');
702
+        $file = $node->newFile('test.txt');
703
+
704
+        $userHome2 = \OC::$server->getUserFolder(self::TEST_VERSIONS_USER2);
705
+        $userHome2->newFolder('subfolder');
706
+
707
+        $share = Server::get(\OCP\Share\IManager::class)->newShare();
708
+        $share->setNode($node)
709
+            ->setShareType(IShare::TYPE_USER)
710
+            ->setSharedBy(self::TEST_VERSIONS_USER)
711
+            ->setSharedWith(self::TEST_VERSIONS_USER2)
712
+            ->setPermissions(Constants::PERMISSION_ALL);
713
+        $share = Server::get(\OCP\Share\IManager::class)->createShare($share);
714
+        $shareManager = Server::get(\OCP\Share\IManager::class);
715
+        $shareManager->acceptShare($share, self::TEST_VERSIONS_USER2);
716
+
717
+        $share->setTarget('subfolder/folder');
718
+        $shareManager->moveShare($share, self::TEST_VERSIONS_USER2);
719
+
720
+        $versions = $this->createAndCheckVersions(
721
+            Filesystem::getView(),
722
+            'folder/test.txt'
723
+        );
724
+
725
+        $file->putContent('test file');
726
+
727
+        $this->loginAsUser(self::TEST_VERSIONS_USER2);
728
+
729
+        $firstVersion = current($versions);
730
+
731
+        $this->assertTrue(Storage::rollback('folder/test.txt', $firstVersion['version'], $this->user1));
732
+
733
+        $this->loginAsUser(self::TEST_VERSIONS_USER);
734
+
735
+        Server::get(\OCP\Share\IManager::class)->deleteShare($share);
736
+        $this->assertEquals('version 2', $file->getContent(), 'File content has not changed');
737
+    }
738
+
739
+    /**
740
+     * @param string $hookName name of hook called
741
+     * @param string $params variable to receive parameters provided by hook
742
+     */
743
+    private function connectMockHooks($hookName, &$params) {
744
+        if ($hookName === null) {
745
+            return;
746
+        }
747
+
748
+        $eventHandler = $this->getMockBuilder(DummyHookListener::class)
749
+            ->onlyMethods(['callback'])
750
+            ->getMock();
751
+
752
+        $eventHandler->expects($this->any())
753
+            ->method('callback')
754
+            ->willReturnCallback(
755
+                function ($p) use (&$params): void {
756
+                    $params = $p;
757
+                }
758
+            );
759
+
760
+        Util::connectHook(
761
+            '\OCP\Versions',
762
+            $hookName,
763
+            $eventHandler,
764
+            'callback'
765
+        );
766
+    }
767
+
768
+    private function doTestRestore(): void {
769
+        $filePath = self::TEST_VERSIONS_USER . '/files/sub/test.txt';
770
+        $this->rootView->file_put_contents($filePath, 'test file');
771
+
772
+        $fileInfo = $this->rootView->getFileInfo($filePath);
773
+        $t0 = $this->rootView->filemtime($filePath);
774
+
775
+        // not exactly the same timestamp as the file
776
+        $t1 = time() - 60;
777
+        // second version is two weeks older
778
+        $t2 = $t1 - 60 * 60 * 24 * 14;
779
+
780
+        // create some versions
781
+        $v1 = self::USERS_VERSIONS_ROOT . '/sub/test.txt.v' . $t1;
782
+        $v2 = self::USERS_VERSIONS_ROOT . '/sub/test.txt.v' . $t2;
783
+
784
+        $this->rootView->mkdir(self::USERS_VERSIONS_ROOT . '/sub');
785
+
786
+        $this->rootView->file_put_contents($v1, 'version1');
787
+        $fileInfoV1 = $this->rootView->getFileInfo($v1);
788
+        $versionEntity = new VersionEntity();
789
+        $versionEntity->setFileId($fileInfo->getId());
790
+        $versionEntity->setTimestamp($t1);
791
+        $versionEntity->setSize($fileInfoV1->getSize());
792
+        $versionEntity->setMimetype($this->mimeTypeLoader->getId($fileInfoV1->getMimetype()));
793
+        $versionEntity->setMetadata([]);
794
+        $this->versionsMapper->insert($versionEntity);
795
+
796
+        $this->rootView->file_put_contents($v2, 'version2');
797
+        $fileInfoV2 = $this->rootView->getFileInfo($v2);
798
+        $versionEntity = new VersionEntity();
799
+        $versionEntity->setFileId($fileInfo->getId());
800
+        $versionEntity->setTimestamp($t2);
801
+        $versionEntity->setSize($fileInfoV2->getSize());
802
+        $versionEntity->setMimetype($this->mimeTypeLoader->getId($fileInfoV2->getMimetype()));
803
+        $versionEntity->setMetadata([]);
804
+        $this->versionsMapper->insert($versionEntity);
805
+
806
+        $oldVersions = Storage::getVersions(
807
+            self::TEST_VERSIONS_USER, '/sub/test.txt'
808
+        );
809
+
810
+        $this->assertCount(2, $oldVersions);
811
+
812
+        $this->assertEquals('test file', $this->rootView->file_get_contents($filePath));
813
+        $info1 = $this->rootView->getFileInfo($filePath);
814
+
815
+        $eventDispatcher = Server::get(IEventDispatcher::class);
816
+        $eventFired = false;
817
+        $eventDispatcher->addListener(VersionRestoredEvent::class, function ($event) use (&$eventFired, $t2): void {
818
+            $eventFired = true;
819
+            $this->assertEquals('/sub/test.txt', $event->getVersion()->getVersionPath());
820
+            $this->assertTrue($event->getVersion()->getRevisionId() > 0);
821
+        });
822
+
823
+        $versionManager = Server::get(IVersionManager::class);
824
+        $versions = $versionManager->getVersionsForFile($this->user1, $info1);
825
+        $version = array_filter($versions, function ($version) use ($t2) {
826
+            return $version->getRevisionId() === $t2;
827
+        });
828
+        $this->assertTrue($versionManager->rollback(current($version)));
829
+
830
+        $this->assertTrue($eventFired, 'VersionRestoredEvent was not fired');
831
+
832
+        $this->assertEquals('version2', $this->rootView->file_get_contents($filePath));
833
+        $info2 = $this->rootView->getFileInfo($filePath);
834
+
835
+        $this->assertNotEquals(
836
+            $info2['etag'],
837
+            $info1['etag'],
838
+            'Etag must change after rolling back version'
839
+        );
840
+        $this->assertEquals(
841
+            $info2['fileid'],
842
+            $info1['fileid'],
843
+            'File id must not change after rolling back version'
844
+        );
845
+        $this->assertEquals(
846
+            $info2['mtime'],
847
+            $t2,
848
+            'Restored file has mtime from version'
849
+        );
850
+
851
+        $newVersions = Storage::getVersions(
852
+            self::TEST_VERSIONS_USER, '/sub/test.txt'
853
+        );
854
+
855
+        $this->assertTrue(
856
+            $this->rootView->file_exists(self::USERS_VERSIONS_ROOT . '/sub/test.txt.v' . $t0),
857
+            'A version file was created for the file before restoration'
858
+        );
859
+        $this->assertTrue(
860
+            $this->rootView->file_exists($v1),
861
+            'Untouched version file is still there'
862
+        );
863
+        $this->assertFalse(
864
+            $this->rootView->file_exists($v2),
865
+            'Restored version file gone from files_version folder'
866
+        );
867
+
868
+        $this->assertCount(2, $newVersions, 'Additional version created');
869
+
870
+        $this->assertTrue(
871
+            isset($newVersions[$t0 . '#' . 'test.txt']),
872
+            'A version was created for the file before restoration'
873
+        );
874
+        $this->assertTrue(
875
+            isset($newVersions[$t1 . '#' . 'test.txt']),
876
+            'Untouched version is still there'
877
+        );
878
+        $this->assertFalse(
879
+            isset($newVersions[$t2 . '#' . 'test.txt']),
880
+            'Restored version is not in the list any more'
881
+        );
882
+    }
883
+
884
+    /**
885
+     * Test whether versions are created when overwriting as owner
886
+     */
887
+    public function testStoreVersionAsOwner(): void {
888
+        $this->loginAsUser(self::TEST_VERSIONS_USER);
889
+
890
+        $this->createAndCheckVersions(
891
+            Filesystem::getView(),
892
+            'test.txt'
893
+        );
894
+    }
895
+
896
+    /**
897
+     * Test whether versions are created when overwriting as share recipient
898
+     */
899
+    public function testStoreVersionAsRecipient(): void {
900
+        $this->loginAsUser(self::TEST_VERSIONS_USER);
901
+
902
+        Filesystem::mkdir('folder');
903
+        Filesystem::file_put_contents('folder/test.txt', 'test file');
904
+
905
+        $node = \OC::$server->getUserFolder(self::TEST_VERSIONS_USER)->get('folder');
906
+        $share = Server::get(\OCP\Share\IManager::class)->newShare();
907
+        $share->setNode($node)
908
+            ->setShareType(IShare::TYPE_USER)
909
+            ->setSharedBy(self::TEST_VERSIONS_USER)
910
+            ->setSharedWith(self::TEST_VERSIONS_USER2)
911
+            ->setPermissions(Constants::PERMISSION_ALL);
912
+        $share = Server::get(\OCP\Share\IManager::class)->createShare($share);
913
+        Server::get(\OCP\Share\IManager::class)->acceptShare($share, self::TEST_VERSIONS_USER2);
914
+
915
+        $this->loginAsUser(self::TEST_VERSIONS_USER2);
916
+
917
+        $this->createAndCheckVersions(
918
+            Filesystem::getView(),
919
+            'folder/test.txt'
920
+        );
921
+
922
+        Server::get(\OCP\Share\IManager::class)->deleteShare($share);
923
+    }
924
+
925
+    /**
926
+     * Test whether versions are created when overwriting anonymously.
927
+     *
928
+     * When uploading through a public link or publicwebdav, no user
929
+     * is logged in. File modification must still be able to find
930
+     * the owner and create versions.
931
+     */
932
+    public function testStoreVersionAsAnonymous(): void {
933
+        $this->logout();
934
+
935
+        // note: public link upload does this,
936
+        // needed to make the hooks fire
937
+        \OC_Util::setupFS(self::TEST_VERSIONS_USER);
938
+
939
+        $userView = new View('/' . self::TEST_VERSIONS_USER . '/files');
940
+        $this->createAndCheckVersions(
941
+            $userView,
942
+            'test.txt'
943
+        );
944
+    }
945
+
946
+    private function createAndCheckVersions(View $view, string $path): array {
947
+        $view->file_put_contents($path, 'test file');
948
+        $view->file_put_contents($path, 'version 1');
949
+        $view->file_put_contents($path, 'version 2');
950
+
951
+        $this->loginAsUser(self::TEST_VERSIONS_USER);
952
+
953
+        // need to scan for the versions
954
+        [$rootStorage,] = $this->rootView->resolvePath(self::TEST_VERSIONS_USER . '/files_versions');
955
+        $rootStorage->getScanner()->scan('files_versions');
956
+
957
+        $versions = Storage::getVersions(
958
+            self::TEST_VERSIONS_USER, '/' . $path
959
+        );
960
+
961
+        // note: we cannot predict how many versions are created due to
962
+        // test run timing
963
+        $this->assertGreaterThan(0, count($versions));
964
+
965
+        return $versions;
966
+    }
967
+
968
+    public static function loginHelper(string $user, bool $create = false) {
969
+        if ($create) {
970
+            $backend = new \Test\Util\User\Dummy();
971
+            $backend->createUser($user, $user);
972
+            Server::get(IUserManager::class)->registerBackend($backend);
973
+        }
974
+
975
+        \OC_Util::tearDownFS();
976
+        \OC_User::setUserId('');
977
+        Filesystem::tearDown();
978
+        \OC_User::setUserId($user);
979
+        \OC_Util::setupFS($user);
980
+        \OC::$server->getUserFolder($user);
981
+    }
982 982
 }
983 983
 
984 984
 class DummyHookListener {
985
-	public function callback() {
986
-	}
985
+    public function callback() {
986
+    }
987 987
 }
988 988
 
989 989
 // extend the original class to make it possible to test protected methods
990 990
 class VersionStorageToTest extends Storage {
991 991
 
992
-	/**
993
-	 * @param integer $time
994
-	 */
995
-	public function callProtectedGetExpireList($time, $versions) {
996
-		return self::getExpireList($time, $versions);
997
-	}
992
+    /**
993
+     * @param integer $time
994
+     */
995
+    public function callProtectedGetExpireList($time, $versions) {
996
+        return self::getExpireList($time, $versions);
997
+    }
998 998
 }
Please login to merge, or discard this patch.
apps/files_sharing/lib/AppInfo/Application.php 1 patch
Indentation   +103 added lines, -103 removed lines patch added patch discarded remove patch
@@ -64,107 +64,107 @@
 block discarded – undo
64 64
 use Symfony\Component\EventDispatcher\GenericEvent as OldGenericEvent;
65 65
 
66 66
 class Application extends App implements IBootstrap {
67
-	public const APP_ID = 'files_sharing';
68
-
69
-	public function __construct(array $urlParams = []) {
70
-		parent::__construct(self::APP_ID, $urlParams);
71
-	}
72
-
73
-	public function register(IRegistrationContext $context): void {
74
-		$context->registerService(ExternalMountProvider::class, function (ContainerInterface $c) {
75
-			return new ExternalMountProvider(
76
-				$c->get(IDBConnection::class),
77
-				function () use ($c) {
78
-					return $c->get(Manager::class);
79
-				},
80
-				$c->get(ICloudIdManager::class)
81
-			);
82
-		});
83
-
84
-		/**
85
-		 * Middleware
86
-		 */
87
-		$context->registerMiddleWare(SharingCheckMiddleware::class);
88
-		$context->registerMiddleWare(OCSShareAPIMiddleware::class);
89
-		$context->registerMiddleWare(ShareInfoMiddleware::class);
90
-
91
-		$context->registerCapability(Capabilities::class);
92
-
93
-		$context->registerNotifierService(Notifier::class);
94
-		$context->registerEventListener(UserChangedEvent::class, DisplayNameCache::class);
95
-		$context->registerEventListener(UserDeletedEvent::class, DisplayNameCache::class);
96
-		$context->registerEventListener(GroupChangedEvent::class, GroupDisplayNameCache::class);
97
-		$context->registerEventListener(GroupDeletedEvent::class, GroupDisplayNameCache::class);
98
-
99
-		// Sidebar and files scripts
100
-		$context->registerEventListener(LoadAdditionalScriptsEvent::class, LoadAdditionalListener::class);
101
-		$context->registerEventListener(LoadSidebar::class, LoadSidebarListener::class);
102
-		$context->registerEventListener(ShareCreatedEvent::class, ShareInteractionListener::class);
103
-		$context->registerEventListener(ShareCreatedEvent::class, UserShareAcceptanceListener::class);
104
-		$context->registerEventListener(UserAddedEvent::class, UserAddedToGroupListener::class);
105
-
106
-		// Publish activity for public download
107
-		$context->registerEventListener(BeforeNodeReadEvent::class, BeforeNodeReadListener::class);
108
-		$context->registerEventListener(BeforeZipCreatedEvent::class, BeforeNodeReadListener::class);
109
-
110
-		// Handle download events for view only checks. Priority higher than 0 to run early.
111
-		$context->registerEventListener(BeforeZipCreatedEvent::class, BeforeZipCreatedListener::class, 5);
112
-		$context->registerEventListener(BeforeDirectFileDownloadEvent::class, BeforeDirectFileDownloadListener::class, 5);
113
-
114
-		// File request auth
115
-		$context->registerEventListener(BeforeTemplateRenderedEvent::class, LoadPublicFileRequestAuthListener::class);
116
-
117
-		// Update mounts
118
-		$context->registerEventListener(ShareCreatedEvent::class, SharesUpdatedListener::class);
119
-		$context->registerEventListener(BeforeShareDeletedEvent::class, SharesUpdatedListener::class);
120
-		$context->registerEventListener(UserAddedEvent::class, SharesUpdatedListener::class);
121
-		$context->registerEventListener(UserRemovedEvent::class, SharesUpdatedListener::class);
122
-		$context->registerEventListener(UserShareAccessUpdatedEvent::class, SharesUpdatedListener::class);
123
-		$context->registerEventListener(FilesystemTornDownEvent::class, SharesUpdatedListener::class);
124
-
125
-		$context->registerConfigLexicon(ConfigLexicon::class);
126
-	}
127
-
128
-	public function boot(IBootContext $context): void {
129
-		$context->injectFn([$this, 'registerMountProviders']);
130
-		$context->injectFn([$this, 'registerEventsScripts']);
131
-
132
-		Helper::registerHooks();
133
-
134
-		Share::registerBackend('file', File::class);
135
-		Share::registerBackend('folder', Folder::class, 'file');
136
-	}
137
-
138
-
139
-	public function registerMountProviders(IMountProviderCollection $mountProviderCollection, MountProvider $mountProvider, ExternalMountProvider $externalMountProvider): void {
140
-		$mountProviderCollection->registerProvider($mountProvider);
141
-		$mountProviderCollection->registerProvider($externalMountProvider);
142
-	}
143
-
144
-	public function registerEventsScripts(IEventDispatcher $dispatcher): void {
145
-		$dispatcher->addListener(ResourcesLoadAdditionalScriptsEvent::class, function (): void {
146
-			Util::addScript('files_sharing', 'collaboration');
147
-		});
148
-		$dispatcher->addListener(BeforeTemplateRenderedEvent::class, function (): void {
149
-			/**
150
-			 * Always add main sharing script
151
-			 */
152
-			Util::addScript(self::APP_ID, 'main');
153
-		});
154
-
155
-		// notifications api to accept incoming user shares
156
-		$dispatcher->addListener(ShareCreatedEvent::class, function (ShareCreatedEvent $event): void {
157
-			/** @var Listener $listener */
158
-			$listener = $this->getContainer()->query(Listener::class);
159
-			$listener->shareNotification($event);
160
-		});
161
-		$dispatcher->addListener(IGroup::class . '::postAddUser', function ($event): void {
162
-			if (!$event instanceof OldGenericEvent) {
163
-				return;
164
-			}
165
-			/** @var Listener $listener */
166
-			$listener = $this->getContainer()->query(Listener::class);
167
-			$listener->userAddedToGroup($event);
168
-		});
169
-	}
67
+    public const APP_ID = 'files_sharing';
68
+
69
+    public function __construct(array $urlParams = []) {
70
+        parent::__construct(self::APP_ID, $urlParams);
71
+    }
72
+
73
+    public function register(IRegistrationContext $context): void {
74
+        $context->registerService(ExternalMountProvider::class, function (ContainerInterface $c) {
75
+            return new ExternalMountProvider(
76
+                $c->get(IDBConnection::class),
77
+                function () use ($c) {
78
+                    return $c->get(Manager::class);
79
+                },
80
+                $c->get(ICloudIdManager::class)
81
+            );
82
+        });
83
+
84
+        /**
85
+         * Middleware
86
+         */
87
+        $context->registerMiddleWare(SharingCheckMiddleware::class);
88
+        $context->registerMiddleWare(OCSShareAPIMiddleware::class);
89
+        $context->registerMiddleWare(ShareInfoMiddleware::class);
90
+
91
+        $context->registerCapability(Capabilities::class);
92
+
93
+        $context->registerNotifierService(Notifier::class);
94
+        $context->registerEventListener(UserChangedEvent::class, DisplayNameCache::class);
95
+        $context->registerEventListener(UserDeletedEvent::class, DisplayNameCache::class);
96
+        $context->registerEventListener(GroupChangedEvent::class, GroupDisplayNameCache::class);
97
+        $context->registerEventListener(GroupDeletedEvent::class, GroupDisplayNameCache::class);
98
+
99
+        // Sidebar and files scripts
100
+        $context->registerEventListener(LoadAdditionalScriptsEvent::class, LoadAdditionalListener::class);
101
+        $context->registerEventListener(LoadSidebar::class, LoadSidebarListener::class);
102
+        $context->registerEventListener(ShareCreatedEvent::class, ShareInteractionListener::class);
103
+        $context->registerEventListener(ShareCreatedEvent::class, UserShareAcceptanceListener::class);
104
+        $context->registerEventListener(UserAddedEvent::class, UserAddedToGroupListener::class);
105
+
106
+        // Publish activity for public download
107
+        $context->registerEventListener(BeforeNodeReadEvent::class, BeforeNodeReadListener::class);
108
+        $context->registerEventListener(BeforeZipCreatedEvent::class, BeforeNodeReadListener::class);
109
+
110
+        // Handle download events for view only checks. Priority higher than 0 to run early.
111
+        $context->registerEventListener(BeforeZipCreatedEvent::class, BeforeZipCreatedListener::class, 5);
112
+        $context->registerEventListener(BeforeDirectFileDownloadEvent::class, BeforeDirectFileDownloadListener::class, 5);
113
+
114
+        // File request auth
115
+        $context->registerEventListener(BeforeTemplateRenderedEvent::class, LoadPublicFileRequestAuthListener::class);
116
+
117
+        // Update mounts
118
+        $context->registerEventListener(ShareCreatedEvent::class, SharesUpdatedListener::class);
119
+        $context->registerEventListener(BeforeShareDeletedEvent::class, SharesUpdatedListener::class);
120
+        $context->registerEventListener(UserAddedEvent::class, SharesUpdatedListener::class);
121
+        $context->registerEventListener(UserRemovedEvent::class, SharesUpdatedListener::class);
122
+        $context->registerEventListener(UserShareAccessUpdatedEvent::class, SharesUpdatedListener::class);
123
+        $context->registerEventListener(FilesystemTornDownEvent::class, SharesUpdatedListener::class);
124
+
125
+        $context->registerConfigLexicon(ConfigLexicon::class);
126
+    }
127
+
128
+    public function boot(IBootContext $context): void {
129
+        $context->injectFn([$this, 'registerMountProviders']);
130
+        $context->injectFn([$this, 'registerEventsScripts']);
131
+
132
+        Helper::registerHooks();
133
+
134
+        Share::registerBackend('file', File::class);
135
+        Share::registerBackend('folder', Folder::class, 'file');
136
+    }
137
+
138
+
139
+    public function registerMountProviders(IMountProviderCollection $mountProviderCollection, MountProvider $mountProvider, ExternalMountProvider $externalMountProvider): void {
140
+        $mountProviderCollection->registerProvider($mountProvider);
141
+        $mountProviderCollection->registerProvider($externalMountProvider);
142
+    }
143
+
144
+    public function registerEventsScripts(IEventDispatcher $dispatcher): void {
145
+        $dispatcher->addListener(ResourcesLoadAdditionalScriptsEvent::class, function (): void {
146
+            Util::addScript('files_sharing', 'collaboration');
147
+        });
148
+        $dispatcher->addListener(BeforeTemplateRenderedEvent::class, function (): void {
149
+            /**
150
+             * Always add main sharing script
151
+             */
152
+            Util::addScript(self::APP_ID, 'main');
153
+        });
154
+
155
+        // notifications api to accept incoming user shares
156
+        $dispatcher->addListener(ShareCreatedEvent::class, function (ShareCreatedEvent $event): void {
157
+            /** @var Listener $listener */
158
+            $listener = $this->getContainer()->query(Listener::class);
159
+            $listener->shareNotification($event);
160
+        });
161
+        $dispatcher->addListener(IGroup::class . '::postAddUser', function ($event): void {
162
+            if (!$event instanceof OldGenericEvent) {
163
+                return;
164
+            }
165
+            /** @var Listener $listener */
166
+            $listener = $this->getContainer()->query(Listener::class);
167
+            $listener->userAddedToGroup($event);
168
+        });
169
+    }
170 170
 }
Please login to merge, or discard this patch.
apps/files_sharing/lib/SharedMount.php 2 patches
Indentation   +165 added lines, -165 removed lines patch added patch discarded remove patch
@@ -25,169 +25,169 @@
 block discarded – undo
25 25
  * Shared mount points can be moved by the user
26 26
  */
27 27
 class SharedMount extends MountPoint implements MoveableMount, ISharedMountPoint {
28
-	/**
29
-	 * @var SharedStorage $storage
30
-	 */
31
-	protected $storage = null;
32
-
33
-	/** @var IShare */
34
-	private $superShare;
35
-
36
-	/** @var IShare[] */
37
-	private $groupedShares;
38
-
39
-	public function __construct(
40
-		$storage,
41
-		$arguments,
42
-		IStorageFactory $loader,
43
-		private IEventDispatcher $eventDispatcher,
44
-		private IUser $user,
45
-	) {
46
-		$this->superShare = $arguments['superShare'];
47
-		$this->groupedShares = $arguments['groupedShares'];
48
-
49
-		$absMountPoint = '/' . $user->getUID() . '/files/' . trim($this->superShare->getTarget(), '/') . '/';
50
-
51
-		parent::__construct($storage, $absMountPoint, $arguments, $loader, null, null, MountProvider::class);
52
-	}
53
-
54
-	/**
55
-	 * update fileTarget in the database if the mount point changed
56
-	 *
57
-	 * @param string $newPath
58
-	 * @param IShare $share
59
-	 * @return bool
60
-	 */
61
-	private function updateFileTarget($newPath, &$share) {
62
-		$share->setTarget($newPath);
63
-
64
-		foreach ($this->groupedShares as $tmpShare) {
65
-			$tmpShare->setTarget($newPath);
66
-			Server::get(\OCP\Share\IManager::class)->moveShare($tmpShare, $this->user->getUID());
67
-		}
68
-
69
-		$this->eventDispatcher->dispatchTyped(new InvalidateMountCacheEvent($this->user));
70
-	}
71
-
72
-	/**
73
-	 * Format a path to be relative to the /user/files/ directory
74
-	 *
75
-	 * @param string $path the absolute path
76
-	 * @return string e.g. turns '/admin/files/test.txt' into '/test.txt'
77
-	 * @throws BrokenPath
78
-	 */
79
-	protected function stripUserFilesPath($path) {
80
-		$trimmed = ltrim($path, '/');
81
-		$split = explode('/', $trimmed);
82
-
83
-		// it is not a file relative to data/user/files
84
-		if (count($split) < 3 || $split[1] !== 'files') {
85
-			Server::get(LoggerInterface::class)->error('Can not strip userid and "files/" from path: ' . $path, ['app' => 'files_sharing']);
86
-			throw new BrokenPath('Path does not start with /user/files', 10);
87
-		}
88
-
89
-		// skip 'user' and 'files'
90
-		$sliced = array_slice($split, 2);
91
-		$relPath = implode('/', $sliced);
92
-
93
-		return '/' . $relPath;
94
-	}
95
-
96
-	/**
97
-	 * Move the mount point to $target
98
-	 *
99
-	 * @param string $target the target mount point
100
-	 * @return bool
101
-	 */
102
-	public function moveMount($target) {
103
-		$relTargetPath = $this->stripUserFilesPath($target);
104
-		$share = $this->storage->getShare();
105
-
106
-		$result = true;
107
-
108
-		try {
109
-			$this->updateFileTarget($relTargetPath, $share);
110
-			$this->setMountPoint($target);
111
-			$this->storage->setMountPoint($relTargetPath);
112
-		} catch (\Exception $e) {
113
-			Server::get(LoggerInterface::class)->error(
114
-				'Could not rename mount point for shared folder "' . $this->getMountPoint() . '" to "' . $target . '"',
115
-				[
116
-					'app' => 'files_sharing',
117
-					'exception' => $e,
118
-				]
119
-			);
120
-		}
121
-
122
-		return $result;
123
-	}
124
-
125
-	/**
126
-	 * Remove the mount points
127
-	 *
128
-	 * @return bool
129
-	 */
130
-	public function removeMount() {
131
-		$mountManager = Filesystem::getMountManager();
132
-		/** @var SharedStorage $storage */
133
-		$storage = $this->getStorage();
134
-		$result = $storage->unshareStorage();
135
-		$mountManager->removeMount($this->mountPoint);
136
-
137
-		return $result;
138
-	}
139
-
140
-	/**
141
-	 * @return IShare
142
-	 */
143
-	public function getShare() {
144
-		return $this->superShare;
145
-	}
146
-
147
-	/**
148
-	 * @return IShare[]
149
-	 */
150
-	public function getGroupedShares(): array {
151
-		return $this->groupedShares;
152
-	}
153
-
154
-	/**
155
-	 * Get the file id of the root of the storage
156
-	 *
157
-	 * @return int
158
-	 */
159
-	public function getStorageRootId() {
160
-		return $this->getShare()->getNodeId();
161
-	}
162
-
163
-	/**
164
-	 * @return int
165
-	 */
166
-	public function getNumericStorageId() {
167
-		if (!is_null($this->getShare()->getNodeCacheEntry())) {
168
-			return $this->getShare()->getNodeCacheEntry()->getStorageId();
169
-		} else {
170
-			$builder = Server::get(IDBConnection::class)->getQueryBuilder();
171
-
172
-			$query = $builder->select('storage')
173
-				->from('filecache')
174
-				->where($builder->expr()->eq('fileid', $builder->createNamedParameter($this->getStorageRootId())));
175
-
176
-			$result = $query->executeQuery();
177
-			$row = $result->fetchAssociative();
178
-			$result->closeCursor();
179
-			if ($row) {
180
-				return (int)$row['storage'];
181
-			}
182
-			return -1;
183
-		}
184
-	}
185
-
186
-	public function getMountType() {
187
-		return 'shared';
188
-	}
189
-
190
-	public function getUser(): IUser {
191
-		return $this->user;
192
-	}
28
+    /**
29
+     * @var SharedStorage $storage
30
+     */
31
+    protected $storage = null;
32
+
33
+    /** @var IShare */
34
+    private $superShare;
35
+
36
+    /** @var IShare[] */
37
+    private $groupedShares;
38
+
39
+    public function __construct(
40
+        $storage,
41
+        $arguments,
42
+        IStorageFactory $loader,
43
+        private IEventDispatcher $eventDispatcher,
44
+        private IUser $user,
45
+    ) {
46
+        $this->superShare = $arguments['superShare'];
47
+        $this->groupedShares = $arguments['groupedShares'];
48
+
49
+        $absMountPoint = '/' . $user->getUID() . '/files/' . trim($this->superShare->getTarget(), '/') . '/';
50
+
51
+        parent::__construct($storage, $absMountPoint, $arguments, $loader, null, null, MountProvider::class);
52
+    }
53
+
54
+    /**
55
+     * update fileTarget in the database if the mount point changed
56
+     *
57
+     * @param string $newPath
58
+     * @param IShare $share
59
+     * @return bool
60
+     */
61
+    private function updateFileTarget($newPath, &$share) {
62
+        $share->setTarget($newPath);
63
+
64
+        foreach ($this->groupedShares as $tmpShare) {
65
+            $tmpShare->setTarget($newPath);
66
+            Server::get(\OCP\Share\IManager::class)->moveShare($tmpShare, $this->user->getUID());
67
+        }
68
+
69
+        $this->eventDispatcher->dispatchTyped(new InvalidateMountCacheEvent($this->user));
70
+    }
71
+
72
+    /**
73
+     * Format a path to be relative to the /user/files/ directory
74
+     *
75
+     * @param string $path the absolute path
76
+     * @return string e.g. turns '/admin/files/test.txt' into '/test.txt'
77
+     * @throws BrokenPath
78
+     */
79
+    protected function stripUserFilesPath($path) {
80
+        $trimmed = ltrim($path, '/');
81
+        $split = explode('/', $trimmed);
82
+
83
+        // it is not a file relative to data/user/files
84
+        if (count($split) < 3 || $split[1] !== 'files') {
85
+            Server::get(LoggerInterface::class)->error('Can not strip userid and "files/" from path: ' . $path, ['app' => 'files_sharing']);
86
+            throw new BrokenPath('Path does not start with /user/files', 10);
87
+        }
88
+
89
+        // skip 'user' and 'files'
90
+        $sliced = array_slice($split, 2);
91
+        $relPath = implode('/', $sliced);
92
+
93
+        return '/' . $relPath;
94
+    }
95
+
96
+    /**
97
+     * Move the mount point to $target
98
+     *
99
+     * @param string $target the target mount point
100
+     * @return bool
101
+     */
102
+    public function moveMount($target) {
103
+        $relTargetPath = $this->stripUserFilesPath($target);
104
+        $share = $this->storage->getShare();
105
+
106
+        $result = true;
107
+
108
+        try {
109
+            $this->updateFileTarget($relTargetPath, $share);
110
+            $this->setMountPoint($target);
111
+            $this->storage->setMountPoint($relTargetPath);
112
+        } catch (\Exception $e) {
113
+            Server::get(LoggerInterface::class)->error(
114
+                'Could not rename mount point for shared folder "' . $this->getMountPoint() . '" to "' . $target . '"',
115
+                [
116
+                    'app' => 'files_sharing',
117
+                    'exception' => $e,
118
+                ]
119
+            );
120
+        }
121
+
122
+        return $result;
123
+    }
124
+
125
+    /**
126
+     * Remove the mount points
127
+     *
128
+     * @return bool
129
+     */
130
+    public function removeMount() {
131
+        $mountManager = Filesystem::getMountManager();
132
+        /** @var SharedStorage $storage */
133
+        $storage = $this->getStorage();
134
+        $result = $storage->unshareStorage();
135
+        $mountManager->removeMount($this->mountPoint);
136
+
137
+        return $result;
138
+    }
139
+
140
+    /**
141
+     * @return IShare
142
+     */
143
+    public function getShare() {
144
+        return $this->superShare;
145
+    }
146
+
147
+    /**
148
+     * @return IShare[]
149
+     */
150
+    public function getGroupedShares(): array {
151
+        return $this->groupedShares;
152
+    }
153
+
154
+    /**
155
+     * Get the file id of the root of the storage
156
+     *
157
+     * @return int
158
+     */
159
+    public function getStorageRootId() {
160
+        return $this->getShare()->getNodeId();
161
+    }
162
+
163
+    /**
164
+     * @return int
165
+     */
166
+    public function getNumericStorageId() {
167
+        if (!is_null($this->getShare()->getNodeCacheEntry())) {
168
+            return $this->getShare()->getNodeCacheEntry()->getStorageId();
169
+        } else {
170
+            $builder = Server::get(IDBConnection::class)->getQueryBuilder();
171
+
172
+            $query = $builder->select('storage')
173
+                ->from('filecache')
174
+                ->where($builder->expr()->eq('fileid', $builder->createNamedParameter($this->getStorageRootId())));
175
+
176
+            $result = $query->executeQuery();
177
+            $row = $result->fetchAssociative();
178
+            $result->closeCursor();
179
+            if ($row) {
180
+                return (int)$row['storage'];
181
+            }
182
+            return -1;
183
+        }
184
+    }
185
+
186
+    public function getMountType() {
187
+        return 'shared';
188
+    }
189
+
190
+    public function getUser(): IUser {
191
+        return $this->user;
192
+    }
193 193
 }
Please login to merge, or discard this patch.
Spacing   +5 added lines, -5 removed lines patch added patch discarded remove patch
@@ -46,7 +46,7 @@  discard block
 block discarded – undo
46 46
 		$this->superShare = $arguments['superShare'];
47 47
 		$this->groupedShares = $arguments['groupedShares'];
48 48
 
49
-		$absMountPoint = '/' . $user->getUID() . '/files/' . trim($this->superShare->getTarget(), '/') . '/';
49
+		$absMountPoint = '/'.$user->getUID().'/files/'.trim($this->superShare->getTarget(), '/').'/';
50 50
 
51 51
 		parent::__construct($storage, $absMountPoint, $arguments, $loader, null, null, MountProvider::class);
52 52
 	}
@@ -82,7 +82,7 @@  discard block
 block discarded – undo
82 82
 
83 83
 		// it is not a file relative to data/user/files
84 84
 		if (count($split) < 3 || $split[1] !== 'files') {
85
-			Server::get(LoggerInterface::class)->error('Can not strip userid and "files/" from path: ' . $path, ['app' => 'files_sharing']);
85
+			Server::get(LoggerInterface::class)->error('Can not strip userid and "files/" from path: '.$path, ['app' => 'files_sharing']);
86 86
 			throw new BrokenPath('Path does not start with /user/files', 10);
87 87
 		}
88 88
 
@@ -90,7 +90,7 @@  discard block
 block discarded – undo
90 90
 		$sliced = array_slice($split, 2);
91 91
 		$relPath = implode('/', $sliced);
92 92
 
93
-		return '/' . $relPath;
93
+		return '/'.$relPath;
94 94
 	}
95 95
 
96 96
 	/**
@@ -111,7 +111,7 @@  discard block
 block discarded – undo
111 111
 			$this->storage->setMountPoint($relTargetPath);
112 112
 		} catch (\Exception $e) {
113 113
 			Server::get(LoggerInterface::class)->error(
114
-				'Could not rename mount point for shared folder "' . $this->getMountPoint() . '" to "' . $target . '"',
114
+				'Could not rename mount point for shared folder "'.$this->getMountPoint().'" to "'.$target.'"',
115 115
 				[
116 116
 					'app' => 'files_sharing',
117 117
 					'exception' => $e,
@@ -177,7 +177,7 @@  discard block
 block discarded – undo
177 177
 			$row = $result->fetchAssociative();
178 178
 			$result->closeCursor();
179 179
 			if ($row) {
180
-				return (int)$row['storage'];
180
+				return (int) $row['storage'];
181 181
 			}
182 182
 			return -1;
183 183
 		}
Please login to merge, or discard this patch.
apps/files_sharing/lib/Updater.php 1 patch
Indentation   +128 added lines, -128 removed lines patch added patch discarded remove patch
@@ -19,132 +19,132 @@
 block discarded – undo
19 19
 
20 20
 class Updater {
21 21
 
22
-	/**
23
-	 * @param array $params
24
-	 */
25
-	public static function renameHook($params) {
26
-		self::renameChildren($params['oldpath'], $params['newpath']);
27
-		self::moveShareInOrOutOfShare($params['newpath']);
28
-	}
29
-
30
-	/**
31
-	 * Fix for https://github.com/owncloud/core/issues/20769
32
-	 *
33
-	 * The owner is allowed to move their files (if they are shared) into a receiving folder
34
-	 * In this case we need to update the parent of the moved share. Since they are
35
-	 * effectively handing over ownership of the file the rest of the code needs to know
36
-	 * they need to build up the reshare tree.
37
-	 *
38
-	 * @param string $path
39
-	 */
40
-	private static function moveShareInOrOutOfShare($path): void {
41
-		$userFolder = \OC::$server->getUserFolder();
42
-
43
-		// If the user folder can't be constructed (e.g. link share) just return.
44
-		if ($userFolder === null) {
45
-			return;
46
-		}
47
-		$user = $userFolder->getOwner();
48
-		if (!$user) {
49
-			throw new \Exception('user folder has no owner');
50
-		}
51
-
52
-		try {
53
-			$src = $userFolder->get($path);
54
-		} catch (NotFoundException) {
55
-			return;
56
-		}
57
-
58
-		$shareManager = Server::get(\OCP\Share\IManager::class);
59
-
60
-		// We intentionally include invalid shares, as they have been automatically invalidated due to the node no longer
61
-		// being accessible for the user. Only in this case where we adjust the share after it was moved we want to ignore
62
-		// this to be able to still adjust it.
63
-
64
-		// FIXME: should CIRCLES be included here ??
65
-		$shares = $shareManager->getSharesBy($user->getUID(), IShare::TYPE_USER, $src, false, -1, onlyValid: false);
66
-		$shares = array_merge($shares, $shareManager->getSharesBy($user->getUID(), IShare::TYPE_GROUP, $src, false, -1, onlyValid: false));
67
-		$shares = array_merge($shares, $shareManager->getSharesBy($user->getUID(), IShare::TYPE_ROOM, $src, false, -1, onlyValid: false));
68
-
69
-		if ($src instanceof Folder) {
70
-			$cacheAccess = Server::get(FileAccess::class);
71
-
72
-			$sourceStorageId = $src->getStorage()->getCache()->getNumericStorageId();
73
-			$sourceInternalPath = $src->getInternalPath();
74
-			$subShares = array_merge(
75
-				$shareManager->getSharesBy($user->getUID(), IShare::TYPE_USER, onlyValid: false),
76
-				$shareManager->getSharesBy($user->getUID(), IShare::TYPE_GROUP, onlyValid: false),
77
-				$shareManager->getSharesBy($user->getUID(), IShare::TYPE_ROOM, onlyValid: false),
78
-			);
79
-			$shareSourceIds = array_map(fn (IShare $share) => $share->getNodeId(), $subShares);
80
-			$shareSources = $cacheAccess->getByFileIdsInStorage($shareSourceIds, $sourceStorageId);
81
-			foreach ($subShares as $subShare) {
82
-				$shareCacheEntry = $shareSources[$subShare->getNodeId()] ?? null;
83
-				if (
84
-					$shareCacheEntry
85
-					&& str_starts_with($shareCacheEntry->getPath(), $sourceInternalPath . '/')
86
-				) {
87
-					$shares[] = $subShare;
88
-				}
89
-			}
90
-		}
91
-
92
-		// If the path we move is not a share we don't care
93
-		if (empty($shares)) {
94
-			return;
95
-		}
96
-
97
-		// Check if the destination is inside a share
98
-		$mountManager = Server::get(IMountManager::class);
99
-		$dstMount = $mountManager->find($src->getPath());
100
-
101
-		//Ownership is moved over
102
-		foreach ($shares as $share) {
103
-			if (
104
-				$share->getShareType() !== IShare::TYPE_USER
105
-				&& $share->getShareType() !== IShare::TYPE_GROUP
106
-				&& $share->getShareType() !== IShare::TYPE_ROOM
107
-			) {
108
-				continue;
109
-			}
110
-
111
-			if ($dstMount instanceof SharedMount) {
112
-				if (!($dstMount->getShare()->getPermissions() & Constants::PERMISSION_SHARE)) {
113
-					$shareManager->deleteShare($share);
114
-					continue;
115
-				}
116
-				$newOwner = $dstMount->getShare()->getShareOwner();
117
-				$newPermissions = $share->getPermissions() & $dstMount->getShare()->getPermissions();
118
-			} else {
119
-				$newOwner = $userFolder->getOwner()->getUID();
120
-				$newPermissions = $share->getPermissions();
121
-			}
122
-
123
-			$share->setShareOwner($newOwner);
124
-			$share->setPermissions($newPermissions);
125
-			$shareManager->updateShare($share, onlyValid: false);
126
-		}
127
-	}
128
-
129
-	/**
130
-	 * rename mount point from the children if the parent was renamed
131
-	 *
132
-	 * @param string $oldPath old path relative to data/user/files
133
-	 * @param string $newPath new path relative to data/user/files
134
-	 */
135
-	private static function renameChildren($oldPath, $newPath) {
136
-		$absNewPath = Filesystem::normalizePath('/' . \OC_User::getUser() . '/files/' . $newPath);
137
-		$absOldPath = Filesystem::normalizePath('/' . \OC_User::getUser() . '/files/' . $oldPath);
138
-
139
-		$mountManager = Filesystem::getMountManager();
140
-		$mountedShares = $mountManager->findIn('/' . \OC_User::getUser() . '/files/' . $oldPath);
141
-		foreach ($mountedShares as $mount) {
142
-			/** @var MountPoint $mount */
143
-			if ($mount->getStorage()->instanceOfStorage(ISharedStorage::class)) {
144
-				$mountPoint = $mount->getMountPoint();
145
-				$target = str_replace($absOldPath, $absNewPath, $mountPoint);
146
-				$mount->moveMount($target);
147
-			}
148
-		}
149
-	}
22
+    /**
23
+     * @param array $params
24
+     */
25
+    public static function renameHook($params) {
26
+        self::renameChildren($params['oldpath'], $params['newpath']);
27
+        self::moveShareInOrOutOfShare($params['newpath']);
28
+    }
29
+
30
+    /**
31
+     * Fix for https://github.com/owncloud/core/issues/20769
32
+     *
33
+     * The owner is allowed to move their files (if they are shared) into a receiving folder
34
+     * In this case we need to update the parent of the moved share. Since they are
35
+     * effectively handing over ownership of the file the rest of the code needs to know
36
+     * they need to build up the reshare tree.
37
+     *
38
+     * @param string $path
39
+     */
40
+    private static function moveShareInOrOutOfShare($path): void {
41
+        $userFolder = \OC::$server->getUserFolder();
42
+
43
+        // If the user folder can't be constructed (e.g. link share) just return.
44
+        if ($userFolder === null) {
45
+            return;
46
+        }
47
+        $user = $userFolder->getOwner();
48
+        if (!$user) {
49
+            throw new \Exception('user folder has no owner');
50
+        }
51
+
52
+        try {
53
+            $src = $userFolder->get($path);
54
+        } catch (NotFoundException) {
55
+            return;
56
+        }
57
+
58
+        $shareManager = Server::get(\OCP\Share\IManager::class);
59
+
60
+        // We intentionally include invalid shares, as they have been automatically invalidated due to the node no longer
61
+        // being accessible for the user. Only in this case where we adjust the share after it was moved we want to ignore
62
+        // this to be able to still adjust it.
63
+
64
+        // FIXME: should CIRCLES be included here ??
65
+        $shares = $shareManager->getSharesBy($user->getUID(), IShare::TYPE_USER, $src, false, -1, onlyValid: false);
66
+        $shares = array_merge($shares, $shareManager->getSharesBy($user->getUID(), IShare::TYPE_GROUP, $src, false, -1, onlyValid: false));
67
+        $shares = array_merge($shares, $shareManager->getSharesBy($user->getUID(), IShare::TYPE_ROOM, $src, false, -1, onlyValid: false));
68
+
69
+        if ($src instanceof Folder) {
70
+            $cacheAccess = Server::get(FileAccess::class);
71
+
72
+            $sourceStorageId = $src->getStorage()->getCache()->getNumericStorageId();
73
+            $sourceInternalPath = $src->getInternalPath();
74
+            $subShares = array_merge(
75
+                $shareManager->getSharesBy($user->getUID(), IShare::TYPE_USER, onlyValid: false),
76
+                $shareManager->getSharesBy($user->getUID(), IShare::TYPE_GROUP, onlyValid: false),
77
+                $shareManager->getSharesBy($user->getUID(), IShare::TYPE_ROOM, onlyValid: false),
78
+            );
79
+            $shareSourceIds = array_map(fn (IShare $share) => $share->getNodeId(), $subShares);
80
+            $shareSources = $cacheAccess->getByFileIdsInStorage($shareSourceIds, $sourceStorageId);
81
+            foreach ($subShares as $subShare) {
82
+                $shareCacheEntry = $shareSources[$subShare->getNodeId()] ?? null;
83
+                if (
84
+                    $shareCacheEntry
85
+                    && str_starts_with($shareCacheEntry->getPath(), $sourceInternalPath . '/')
86
+                ) {
87
+                    $shares[] = $subShare;
88
+                }
89
+            }
90
+        }
91
+
92
+        // If the path we move is not a share we don't care
93
+        if (empty($shares)) {
94
+            return;
95
+        }
96
+
97
+        // Check if the destination is inside a share
98
+        $mountManager = Server::get(IMountManager::class);
99
+        $dstMount = $mountManager->find($src->getPath());
100
+
101
+        //Ownership is moved over
102
+        foreach ($shares as $share) {
103
+            if (
104
+                $share->getShareType() !== IShare::TYPE_USER
105
+                && $share->getShareType() !== IShare::TYPE_GROUP
106
+                && $share->getShareType() !== IShare::TYPE_ROOM
107
+            ) {
108
+                continue;
109
+            }
110
+
111
+            if ($dstMount instanceof SharedMount) {
112
+                if (!($dstMount->getShare()->getPermissions() & Constants::PERMISSION_SHARE)) {
113
+                    $shareManager->deleteShare($share);
114
+                    continue;
115
+                }
116
+                $newOwner = $dstMount->getShare()->getShareOwner();
117
+                $newPermissions = $share->getPermissions() & $dstMount->getShare()->getPermissions();
118
+            } else {
119
+                $newOwner = $userFolder->getOwner()->getUID();
120
+                $newPermissions = $share->getPermissions();
121
+            }
122
+
123
+            $share->setShareOwner($newOwner);
124
+            $share->setPermissions($newPermissions);
125
+            $shareManager->updateShare($share, onlyValid: false);
126
+        }
127
+    }
128
+
129
+    /**
130
+     * rename mount point from the children if the parent was renamed
131
+     *
132
+     * @param string $oldPath old path relative to data/user/files
133
+     * @param string $newPath new path relative to data/user/files
134
+     */
135
+    private static function renameChildren($oldPath, $newPath) {
136
+        $absNewPath = Filesystem::normalizePath('/' . \OC_User::getUser() . '/files/' . $newPath);
137
+        $absOldPath = Filesystem::normalizePath('/' . \OC_User::getUser() . '/files/' . $oldPath);
138
+
139
+        $mountManager = Filesystem::getMountManager();
140
+        $mountedShares = $mountManager->findIn('/' . \OC_User::getUser() . '/files/' . $oldPath);
141
+        foreach ($mountedShares as $mount) {
142
+            /** @var MountPoint $mount */
143
+            if ($mount->getStorage()->instanceOfStorage(ISharedStorage::class)) {
144
+                $mountPoint = $mount->getMountPoint();
145
+                $target = str_replace($absOldPath, $absNewPath, $mountPoint);
146
+                $mount->moveMount($target);
147
+            }
148
+        }
149
+    }
150 150
 }
Please login to merge, or discard this patch.
apps/files_sharing/lib/Listener/SharesUpdatedListener.php 2 patches
Indentation   +45 added lines, -45 removed lines patch added patch discarded remove patch
@@ -30,54 +30,54 @@
 block discarded – undo
30 30
  * @template-implements IEventListener<UserAddedEvent|UserRemovedEvent|ShareCreatedEvent|BeforeShareDeletedEvent|UserShareAccessUpdatedEvent|FilesystemTornDownEvent>
31 31
  */
32 32
 class SharesUpdatedListener implements IEventListener {
33
-	private CappedMemoryCache $updatedUsers;
33
+    private CappedMemoryCache $updatedUsers;
34 34
 
35
-	public function __construct(
36
-		private readonly IManager $shareManager,
37
-		private readonly IUserMountCache $userMountCache,
38
-		private readonly MountProvider $shareMountProvider,
39
-		private readonly ShareTargetValidator $shareTargetValidator,
40
-	) {
41
-		$this->updatedUsers = new CappedMemoryCache();
42
-	}
43
-	public function handle(Event $event): void {
44
-		if ($event instanceof FilesystemTornDownEvent) {
45
-			$this->updatedUsers = new CappedMemoryCache();
46
-		}
47
-		if ($event instanceof UserShareAccessUpdatedEvent) {
48
-			foreach ($event->getUsers() as $user) {
49
-				$this->updateForUser($user);
50
-			}
51
-		}
52
-		if ($event instanceof UserAddedEvent || $event instanceof UserRemovedEvent) {
53
-			$this->updateForUser($event->getUser());
54
-		}
55
-		if ($event instanceof ShareCreatedEvent || $event instanceof BeforeShareDeletedEvent) {
56
-			foreach ($this->shareManager->getUsersForShare($event->getShare()) as $user) {
57
-				$this->updateForUser($user);
58
-			}
59
-		}
60
-	}
35
+    public function __construct(
36
+        private readonly IManager $shareManager,
37
+        private readonly IUserMountCache $userMountCache,
38
+        private readonly MountProvider $shareMountProvider,
39
+        private readonly ShareTargetValidator $shareTargetValidator,
40
+    ) {
41
+        $this->updatedUsers = new CappedMemoryCache();
42
+    }
43
+    public function handle(Event $event): void {
44
+        if ($event instanceof FilesystemTornDownEvent) {
45
+            $this->updatedUsers = new CappedMemoryCache();
46
+        }
47
+        if ($event instanceof UserShareAccessUpdatedEvent) {
48
+            foreach ($event->getUsers() as $user) {
49
+                $this->updateForUser($user);
50
+            }
51
+        }
52
+        if ($event instanceof UserAddedEvent || $event instanceof UserRemovedEvent) {
53
+            $this->updateForUser($event->getUser());
54
+        }
55
+        if ($event instanceof ShareCreatedEvent || $event instanceof BeforeShareDeletedEvent) {
56
+            foreach ($this->shareManager->getUsersForShare($event->getShare()) as $user) {
57
+                $this->updateForUser($user);
58
+            }
59
+        }
60
+    }
61 61
 
62
-	private function updateForUser(IUser $user): void {
63
-		if (isset($this->updatedUsers[$user->getUID()])) {
64
-			return;
65
-		}
66
-		$this->updatedUsers[$user->getUID()] = true;
62
+    private function updateForUser(IUser $user): void {
63
+        if (isset($this->updatedUsers[$user->getUID()])) {
64
+            return;
65
+        }
66
+        $this->updatedUsers[$user->getUID()] = true;
67 67
 
68
-		$cachedMounts = $this->userMountCache->getMountsForUser($user);
69
-		$mountPoints = array_map(fn (ICachedMountInfo $mount) => $mount->getMountPoint(), $cachedMounts);
70
-		$mountsByPath = array_combine($mountPoints, $cachedMounts);
68
+        $cachedMounts = $this->userMountCache->getMountsForUser($user);
69
+        $mountPoints = array_map(fn (ICachedMountInfo $mount) => $mount->getMountPoint(), $cachedMounts);
70
+        $mountsByPath = array_combine($mountPoints, $cachedMounts);
71 71
 
72
-		$shares = $this->shareMountProvider->getSuperSharesForUser($user);
72
+        $shares = $this->shareMountProvider->getSuperSharesForUser($user);
73 73
 
74
-		foreach ($shares as &$share) {
75
-			[$parentShare, $groupedShares] = $share;
76
-			$mountPoint = '/' . $user->getUID() . '/files/' . trim($parentShare->getTarget(), '/') . '/';
77
-			$mountKey = $parentShare->getNodeId() . '::' . $mountPoint;
78
-			if (!isset($cachedMounts[$mountKey])) {
79
-				$this->shareTargetValidator->verifyMountPoint($user, $parentShare, $mountsByPath, $groupedShares);
80
-			}
81
-		}
82
-	}
74
+        foreach ($shares as &$share) {
75
+            [$parentShare, $groupedShares] = $share;
76
+            $mountPoint = '/' . $user->getUID() . '/files/' . trim($parentShare->getTarget(), '/') . '/';
77
+            $mountKey = $parentShare->getNodeId() . '::' . $mountPoint;
78
+            if (!isset($cachedMounts[$mountKey])) {
79
+                $this->shareTargetValidator->verifyMountPoint($user, $parentShare, $mountsByPath, $groupedShares);
80
+            }
81
+        }
82
+    }
83 83
 }
Please login to merge, or discard this patch.
Spacing   +2 added lines, -2 removed lines patch added patch discarded remove patch
@@ -73,8 +73,8 @@
 block discarded – undo
73 73
 
74 74
 		foreach ($shares as &$share) {
75 75
 			[$parentShare, $groupedShares] = $share;
76
-			$mountPoint = '/' . $user->getUID() . '/files/' . trim($parentShare->getTarget(), '/') . '/';
77
-			$mountKey = $parentShare->getNodeId() . '::' . $mountPoint;
76
+			$mountPoint = '/'.$user->getUID().'/files/'.trim($parentShare->getTarget(), '/').'/';
77
+			$mountKey = $parentShare->getNodeId().'::'.$mountPoint;
78 78
 			if (!isset($cachedMounts[$mountKey])) {
79 79
 				$this->shareTargetValidator->verifyMountPoint($user, $parentShare, $mountsByPath, $groupedShares);
80 80
 			}
Please login to merge, or discard this patch.
apps/files_sharing/lib/MountProvider.php 2 patches
Indentation   +373 added lines, -373 removed lines patch added patch discarded remove patch
@@ -29,377 +29,377 @@
 block discarded – undo
29 29
 use function count;
30 30
 
31 31
 class MountProvider implements IMountProvider, IPartialMountProvider {
32
-	/**
33
-	 * @param IConfig $config
34
-	 * @param IManager $shareManager
35
-	 * @param LoggerInterface $logger
36
-	 */
37
-	public function __construct(
38
-		protected IConfig $config,
39
-		protected IManager $shareManager,
40
-		protected LoggerInterface $logger,
41
-		protected IEventDispatcher $eventDispatcher,
42
-		protected ICacheFactory $cacheFactory,
43
-		protected IMountManager $mountManager,
44
-	) {
45
-	}
46
-
47
-	/**
48
-	 * Get all mountpoints applicable for the user and check for shares where we need to update the etags
49
-	 *
50
-	 * @param IUser $user
51
-	 * @param IStorageFactory $loader
52
-	 * @return IMountPoint[]
53
-	 */
54
-	public function getMountsForUser(IUser $user, IStorageFactory $loader) {
55
-		return array_values($this->getMountsFromSuperShares($user, $this->getSuperSharesForUser($user), $loader));
56
-	}
57
-
58
-	/**
59
-	 * @param IUser $user
60
-	 * @return list<array{IShare, array<IShare>}> Tuple of [superShare, groupedShares]
61
-	 */
62
-	public function getSuperSharesForUser(IUser $user): array {
63
-		$userId = $user->getUID();
64
-		$shares = $this->mergeIterables(
65
-			$this->shareManager->getSharedWith($userId, IShare::TYPE_USER, null, -1),
66
-			$this->shareManager->getSharedWith($userId, IShare::TYPE_GROUP, null, -1),
67
-			$this->shareManager->getSharedWith($userId, IShare::TYPE_CIRCLE, null, -1),
68
-			$this->shareManager->getSharedWith($userId, IShare::TYPE_ROOM, null, -1),
69
-			$this->shareManager->getSharedWith($userId, IShare::TYPE_DECK, null, -1),
70
-		);
71
-
72
-		$shares = $this->filterShares($shares, $userId);
73
-		return $this->buildSuperShares($shares, $user);
74
-	}
75
-
76
-	/**
77
-	 * Groups shares by path (nodeId) and target path
78
-	 *
79
-	 * @param iterable<IShare> $shares
80
-	 * @return IShare[][] array of grouped shares, each element in the
81
-	 *                    array is a group which itself is an array of shares
82
-	 */
83
-	private function groupShares(iterable $shares): array {
84
-		$tmp = [];
85
-
86
-		foreach ($shares as $share) {
87
-			$nodeId = $share->getNodeId();
88
-			if (!isset($tmp[$nodeId])) {
89
-				$tmp[$nodeId] = [];
90
-			}
91
-			$tmp[$nodeId][] = $share;
92
-		}
93
-
94
-		$result = [];
95
-		// sort by stime, the super share will be based on the least recent share
96
-		foreach ($tmp as &$tmp2) {
97
-			@usort($tmp2, function ($a, $b) {
98
-				$aTime = $a->getShareTime()->getTimestamp();
99
-				$bTime = $b->getShareTime()->getTimestamp();
100
-				if ($aTime === $bTime) {
101
-					return $a->getId() < $b->getId() ? -1 : 1;
102
-				}
103
-				return $aTime < $bTime ? -1 : 1;
104
-			});
105
-			$result[] = $tmp2;
106
-		}
107
-
108
-		return $result;
109
-	}
110
-
111
-	/**
112
-	 * Groups shares by node ID and builds a new share object (super share)
113
-	 * which represents a summarized version of all the shares in the group.
114
-	 *
115
-	 * The permissions and attributes of the super share are accumulated from
116
-	 * the shares in the group, forming the most permissive combination
117
-	 * possible.
118
-	 *
119
-	 * @param iterable<IShare> $allShares
120
-	 * @param IUser $user user
121
-	 * @return list<array{IShare, array<IShare>}> Tuple of [superShare, groupedShares]
122
-	 */
123
-	private function buildSuperShares(iterable $allShares, IUser $user): array {
124
-		$result = [];
125
-
126
-		$groupedShares = $this->groupShares($allShares);
127
-
128
-		foreach ($groupedShares as $shares) {
129
-			if (count($shares) === 0) {
130
-				continue;
131
-			}
132
-
133
-			$superShare = $this->shareManager->newShare();
134
-
135
-			// compute super share based on first entry of the group
136
-			$superShare->setId($shares[0]->getId())
137
-				->setShareOwner($shares[0]->getShareOwner())
138
-				->setNodeId($shares[0]->getNodeId())
139
-				->setShareType($shares[0]->getShareType())
140
-				->setTarget($shares[0]->getTarget());
141
-
142
-			$this->combineNotes($shares, $superShare);
143
-
144
-			// use most permissive permissions
145
-			// this covers the case where there are multiple shares for the same
146
-			// file e.g. from different groups and different permissions
147
-			$superPermissions = 0;
148
-			$superAttributes = $this->shareManager->newShare()->newAttributes();
149
-			$status = IShare::STATUS_PENDING;
150
-			foreach ($shares as $share) {
151
-				$status = max($status, $share->getStatus());
152
-				// update permissions
153
-				$superPermissions |= $share->getPermissions();
154
-
155
-				// update share permission attributes
156
-				$attributes = $share->getAttributes();
157
-				if ($attributes !== null) {
158
-					$this->mergeAttributes($attributes, $superAttributes);
159
-				}
160
-
161
-				$this->adjustTarget($share, $superShare, $user);
162
-				if ($share->getNodeCacheEntry() !== null) {
163
-					$superShare->setNodeCacheEntry($share->getNodeCacheEntry());
164
-				}
165
-			}
166
-
167
-			$superShare->setPermissions($superPermissions);
168
-			$superShare->setStatus($status);
169
-			$superShare->setAttributes($superAttributes);
170
-
171
-			$result[] = [$superShare, $shares];
172
-		}
173
-
174
-		return $result;
175
-	}
176
-
177
-	/**
178
-	 * Combines $attributes into the most permissive set of attributes and
179
-	 * sets them in $superAttributes.
180
-	 */
181
-	private function mergeAttributes(
182
-		IAttributes $attributes,
183
-		IAttributes $superAttributes,
184
-	): void {
185
-		foreach ($attributes->toArray() as $attribute) {
186
-			if ($superAttributes->getAttribute(
187
-				$attribute['scope'],
188
-				$attribute['key']
189
-			) === true) {
190
-				// if super share attribute is already enabled, it is most permissive
191
-				continue;
192
-			}
193
-			// update super share attributes with subshare attribute
194
-			$superAttributes->setAttribute(
195
-				$attribute['scope'],
196
-				$attribute['key'],
197
-				$attribute['value']
198
-			);
199
-		}
200
-	}
201
-
202
-	/**
203
-	 * Gather notes from all the shares. Since these are readily available
204
-	 * here, storing them enables the DAV FilesPlugin to avoid executing many
205
-	 * DB queries to retrieve the same information.
206
-	 *
207
-	 * @param array<IShare> $shares
208
-	 * @param IShare $superShare
209
-	 * @return void
210
-	 */
211
-	private function combineNotes(
212
-		array &$shares,
213
-		IShare $superShare,
214
-	): void {
215
-		$allNotes = implode(
216
-			"\n",
217
-			array_map(static fn ($sh) => $sh->getNote(), $shares)
218
-		);
219
-		$superShare->setNote($allNotes);
220
-	}
221
-
222
-	/**
223
-	 * Adjusts the target in $share for DB consistency, if needed.
224
-	 */
225
-	private function adjustTarget(
226
-		IShare $share,
227
-		IShare $superShare,
228
-		IUser $user,
229
-	): void {
230
-		if ($share->getTarget() === $superShare->getTarget()) {
231
-			return;
232
-		}
233
-
234
-		$share->setTarget($superShare->getTarget());
235
-		try {
236
-			$this->shareManager->moveShare($share, $user->getUID());
237
-		} catch (InvalidArgumentException $e) {
238
-			// ignore as it is not important and we don't want to
239
-			// block FS setup
240
-
241
-			// the subsequent code anyway only uses the target of the
242
-			// super share
243
-
244
-			// such issue can usually happen when dealing with
245
-			// null groups which usually appear with group backend
246
-			// caching inconsistencies
247
-			$this->logger->debug(
248
-				'Could not adjust share target for share ' . $share->getId() . ' to make it consistent: ' . $e->getMessage(),
249
-				['app' => 'files_sharing']
250
-			);
251
-		}
252
-	}
253
-	/**
254
-	 * @param string $userId
255
-	 * @param list<array{IShare, array<IShare>}> $superShares
256
-	 * @param IStorageFactory $loader
257
-	 * @param IUser $user
258
-	 * @return array IMountPoint indexed by mount point
259
-	 * @throws Exception
260
-	 */
261
-	public function getMountsFromSuperShares(
262
-		IUser $user,
263
-		array $superShares,
264
-		IStorageFactory $loader,
265
-	): array {
266
-		$userId = $user->getUID();
267
-		$allMounts = $this->mountManager->getAll();
268
-		$mounts = [];
269
-		$view = new View('/' . $userId . '/files');
270
-		$ownerViews = [];
271
-		$sharingDisabledForUser = $this->shareManager->sharingDisabledForUser($userId);
272
-		/** @var CappedMemoryCache<bool> $folderExistCache */
273
-		$foldersExistCache = new CappedMemoryCache();
274
-
275
-		$validShareCache = $this->cacheFactory->createLocal('share-valid-mountpoint-max');
276
-		$maxValidatedShare = $validShareCache->get($userId) ?? 0;
277
-		$newMaxValidatedShare = $maxValidatedShare;
278
-
279
-		foreach ($superShares as $share) {
280
-			[$parentShare, $groupedShares] = $share;
281
-			try {
282
-				if ($parentShare->getStatus() !== IShare::STATUS_ACCEPTED
283
-					&& ($parentShare->getShareType() === IShare::TYPE_GROUP
284
-						|| $parentShare->getShareType() === IShare::TYPE_USERGROUP
285
-						|| $parentShare->getShareType() === IShare::TYPE_USER)
286
-				) {
287
-					continue;
288
-				}
289
-
290
-				$owner = $parentShare->getShareOwner();
291
-				if (!isset($ownerViews[$owner])) {
292
-					$ownerViews[$owner] = new View('/' . $owner . '/files');
293
-				}
294
-				$shareId = (int)$parentShare->getId();
295
-				$mount = new SharedMount(
296
-					'\OCA\Files_Sharing\SharedStorage',
297
-					[
298
-						'user' => $userId,
299
-						// parent share
300
-						'superShare' => $parentShare,
301
-						// children/component of the superShare
302
-						'groupedShares' => $groupedShares,
303
-						'ownerView' => $ownerViews[$owner],
304
-						'sharingDisabledForUser' => $sharingDisabledForUser
305
-					],
306
-					$loader,
307
-					$this->eventDispatcher,
308
-					$user,
309
-				);
310
-
311
-				$newMaxValidatedShare = max($shareId, $newMaxValidatedShare);
312
-
313
-				$event = new ShareMountedEvent($mount);
314
-				$this->eventDispatcher->dispatchTyped($event);
315
-
316
-				$mounts[$mount->getMountPoint()] = $allMounts[$mount->getMountPoint()] = $mount;
317
-				foreach ($event->getAdditionalMounts() as $additionalMount) {
318
-					$mounts[$additionalMount->getMountPoint()] = $additionalMount;
319
-					$allMounts[$additionalMount->getMountPoint()] = $additionalMount;
320
-				}
321
-			} catch (Exception $e) {
322
-				$this->logger->error(
323
-					'Error while trying to create shared mount',
324
-					[
325
-						'app' => 'files_sharing',
326
-						'exception' => $e,
327
-					],
328
-				);
329
-			}
330
-		}
331
-
332
-		$validShareCache->set($userId, $newMaxValidatedShare, 24 * 60 * 60);
333
-
334
-		// array_filter removes the null values from the array
335
-		return array_filter($mounts);
336
-	}
337
-
338
-	/**
339
-	 * Filters out shares owned or shared by the user and ones for which the
340
-	 * user has no permissions.
341
-	 *
342
-	 * @param iterable<IShare> $shares
343
-	 * @return iterable<IShare>
344
-	 */
345
-	private function filterShares(iterable $shares, string $userId): iterable {
346
-		foreach ($shares as $share) {
347
-			if (
348
-				$share->getPermissions() > 0
349
-				&& $share->getShareOwner() !== $userId
350
-				&& $share->getSharedBy() !== $userId
351
-			) {
352
-				yield $share;
353
-			}
354
-		}
355
-	}
356
-
357
-	public function getMountsForPath(
358
-		string $setupPathHint,
359
-		bool $forChildren,
360
-		array $mountProviderArgs,
361
-		IStorageFactory $loader,
362
-	): array {
363
-		$limit = -1;
364
-		$user = $mountProviderArgs[0]->mountInfo->getUser();
365
-		$userId = $user->getUID();
366
-
367
-		if (!$forChildren) {
368
-			// override path with mount point when fetching without children
369
-			$setupPathHint = $mountProviderArgs[0]->mountInfo->getMountPoint();
370
-		}
371
-
372
-		// remove /uid/files as the target is stored without
373
-		$setupPathHint = \substr($setupPathHint, \strlen('/' . $userId . '/files'));
374
-		// remove trailing slash
375
-		$setupPathHint = \rtrim($setupPathHint, '/');
376
-
377
-		// make sure trailing slash is present when loading children
378
-		if ($forChildren || $setupPathHint === '') {
379
-			$setupPathHint .= '/';
380
-		}
381
-
382
-		$shares = $this->mergeIterables(
383
-			$this->shareManager->getSharedWithByPath($userId, IShare::TYPE_USER, $setupPathHint, $forChildren, $limit),
384
-			$this->shareManager->getSharedWithByPath($userId, IShare::TYPE_GROUP, $setupPathHint, $forChildren, $limit),
385
-			$this->shareManager->getSharedWithByPath($userId, IShare::TYPE_CIRCLE, $setupPathHint, $forChildren, $limit),
386
-			$this->shareManager->getSharedWithByPath($userId, IShare::TYPE_ROOM, $setupPathHint, $forChildren, $limit),
387
-			$this->shareManager->getSharedWithByPath($userId, IShare::TYPE_DECK, $setupPathHint, $forChildren, $limit),
388
-		);
389
-
390
-		$shares = $this->filterShares($shares, $userId);
391
-		$superShares = $this->buildSuperShares($shares, $user);
392
-
393
-		return $this->getMountsFromSuperShares($user, $superShares, $loader);
394
-	}
395
-
396
-	/**
397
-	 * @param iterable ...$iterables
398
-	 * @return iterable
399
-	 */
400
-	private function mergeIterables(...$iterables): iterable {
401
-		foreach ($iterables as $iterable) {
402
-			yield from $iterable;
403
-		}
404
-	}
32
+    /**
33
+     * @param IConfig $config
34
+     * @param IManager $shareManager
35
+     * @param LoggerInterface $logger
36
+     */
37
+    public function __construct(
38
+        protected IConfig $config,
39
+        protected IManager $shareManager,
40
+        protected LoggerInterface $logger,
41
+        protected IEventDispatcher $eventDispatcher,
42
+        protected ICacheFactory $cacheFactory,
43
+        protected IMountManager $mountManager,
44
+    ) {
45
+    }
46
+
47
+    /**
48
+     * Get all mountpoints applicable for the user and check for shares where we need to update the etags
49
+     *
50
+     * @param IUser $user
51
+     * @param IStorageFactory $loader
52
+     * @return IMountPoint[]
53
+     */
54
+    public function getMountsForUser(IUser $user, IStorageFactory $loader) {
55
+        return array_values($this->getMountsFromSuperShares($user, $this->getSuperSharesForUser($user), $loader));
56
+    }
57
+
58
+    /**
59
+     * @param IUser $user
60
+     * @return list<array{IShare, array<IShare>}> Tuple of [superShare, groupedShares]
61
+     */
62
+    public function getSuperSharesForUser(IUser $user): array {
63
+        $userId = $user->getUID();
64
+        $shares = $this->mergeIterables(
65
+            $this->shareManager->getSharedWith($userId, IShare::TYPE_USER, null, -1),
66
+            $this->shareManager->getSharedWith($userId, IShare::TYPE_GROUP, null, -1),
67
+            $this->shareManager->getSharedWith($userId, IShare::TYPE_CIRCLE, null, -1),
68
+            $this->shareManager->getSharedWith($userId, IShare::TYPE_ROOM, null, -1),
69
+            $this->shareManager->getSharedWith($userId, IShare::TYPE_DECK, null, -1),
70
+        );
71
+
72
+        $shares = $this->filterShares($shares, $userId);
73
+        return $this->buildSuperShares($shares, $user);
74
+    }
75
+
76
+    /**
77
+     * Groups shares by path (nodeId) and target path
78
+     *
79
+     * @param iterable<IShare> $shares
80
+     * @return IShare[][] array of grouped shares, each element in the
81
+     *                    array is a group which itself is an array of shares
82
+     */
83
+    private function groupShares(iterable $shares): array {
84
+        $tmp = [];
85
+
86
+        foreach ($shares as $share) {
87
+            $nodeId = $share->getNodeId();
88
+            if (!isset($tmp[$nodeId])) {
89
+                $tmp[$nodeId] = [];
90
+            }
91
+            $tmp[$nodeId][] = $share;
92
+        }
93
+
94
+        $result = [];
95
+        // sort by stime, the super share will be based on the least recent share
96
+        foreach ($tmp as &$tmp2) {
97
+            @usort($tmp2, function ($a, $b) {
98
+                $aTime = $a->getShareTime()->getTimestamp();
99
+                $bTime = $b->getShareTime()->getTimestamp();
100
+                if ($aTime === $bTime) {
101
+                    return $a->getId() < $b->getId() ? -1 : 1;
102
+                }
103
+                return $aTime < $bTime ? -1 : 1;
104
+            });
105
+            $result[] = $tmp2;
106
+        }
107
+
108
+        return $result;
109
+    }
110
+
111
+    /**
112
+     * Groups shares by node ID and builds a new share object (super share)
113
+     * which represents a summarized version of all the shares in the group.
114
+     *
115
+     * The permissions and attributes of the super share are accumulated from
116
+     * the shares in the group, forming the most permissive combination
117
+     * possible.
118
+     *
119
+     * @param iterable<IShare> $allShares
120
+     * @param IUser $user user
121
+     * @return list<array{IShare, array<IShare>}> Tuple of [superShare, groupedShares]
122
+     */
123
+    private function buildSuperShares(iterable $allShares, IUser $user): array {
124
+        $result = [];
125
+
126
+        $groupedShares = $this->groupShares($allShares);
127
+
128
+        foreach ($groupedShares as $shares) {
129
+            if (count($shares) === 0) {
130
+                continue;
131
+            }
132
+
133
+            $superShare = $this->shareManager->newShare();
134
+
135
+            // compute super share based on first entry of the group
136
+            $superShare->setId($shares[0]->getId())
137
+                ->setShareOwner($shares[0]->getShareOwner())
138
+                ->setNodeId($shares[0]->getNodeId())
139
+                ->setShareType($shares[0]->getShareType())
140
+                ->setTarget($shares[0]->getTarget());
141
+
142
+            $this->combineNotes($shares, $superShare);
143
+
144
+            // use most permissive permissions
145
+            // this covers the case where there are multiple shares for the same
146
+            // file e.g. from different groups and different permissions
147
+            $superPermissions = 0;
148
+            $superAttributes = $this->shareManager->newShare()->newAttributes();
149
+            $status = IShare::STATUS_PENDING;
150
+            foreach ($shares as $share) {
151
+                $status = max($status, $share->getStatus());
152
+                // update permissions
153
+                $superPermissions |= $share->getPermissions();
154
+
155
+                // update share permission attributes
156
+                $attributes = $share->getAttributes();
157
+                if ($attributes !== null) {
158
+                    $this->mergeAttributes($attributes, $superAttributes);
159
+                }
160
+
161
+                $this->adjustTarget($share, $superShare, $user);
162
+                if ($share->getNodeCacheEntry() !== null) {
163
+                    $superShare->setNodeCacheEntry($share->getNodeCacheEntry());
164
+                }
165
+            }
166
+
167
+            $superShare->setPermissions($superPermissions);
168
+            $superShare->setStatus($status);
169
+            $superShare->setAttributes($superAttributes);
170
+
171
+            $result[] = [$superShare, $shares];
172
+        }
173
+
174
+        return $result;
175
+    }
176
+
177
+    /**
178
+     * Combines $attributes into the most permissive set of attributes and
179
+     * sets them in $superAttributes.
180
+     */
181
+    private function mergeAttributes(
182
+        IAttributes $attributes,
183
+        IAttributes $superAttributes,
184
+    ): void {
185
+        foreach ($attributes->toArray() as $attribute) {
186
+            if ($superAttributes->getAttribute(
187
+                $attribute['scope'],
188
+                $attribute['key']
189
+            ) === true) {
190
+                // if super share attribute is already enabled, it is most permissive
191
+                continue;
192
+            }
193
+            // update super share attributes with subshare attribute
194
+            $superAttributes->setAttribute(
195
+                $attribute['scope'],
196
+                $attribute['key'],
197
+                $attribute['value']
198
+            );
199
+        }
200
+    }
201
+
202
+    /**
203
+     * Gather notes from all the shares. Since these are readily available
204
+     * here, storing them enables the DAV FilesPlugin to avoid executing many
205
+     * DB queries to retrieve the same information.
206
+     *
207
+     * @param array<IShare> $shares
208
+     * @param IShare $superShare
209
+     * @return void
210
+     */
211
+    private function combineNotes(
212
+        array &$shares,
213
+        IShare $superShare,
214
+    ): void {
215
+        $allNotes = implode(
216
+            "\n",
217
+            array_map(static fn ($sh) => $sh->getNote(), $shares)
218
+        );
219
+        $superShare->setNote($allNotes);
220
+    }
221
+
222
+    /**
223
+     * Adjusts the target in $share for DB consistency, if needed.
224
+     */
225
+    private function adjustTarget(
226
+        IShare $share,
227
+        IShare $superShare,
228
+        IUser $user,
229
+    ): void {
230
+        if ($share->getTarget() === $superShare->getTarget()) {
231
+            return;
232
+        }
233
+
234
+        $share->setTarget($superShare->getTarget());
235
+        try {
236
+            $this->shareManager->moveShare($share, $user->getUID());
237
+        } catch (InvalidArgumentException $e) {
238
+            // ignore as it is not important and we don't want to
239
+            // block FS setup
240
+
241
+            // the subsequent code anyway only uses the target of the
242
+            // super share
243
+
244
+            // such issue can usually happen when dealing with
245
+            // null groups which usually appear with group backend
246
+            // caching inconsistencies
247
+            $this->logger->debug(
248
+                'Could not adjust share target for share ' . $share->getId() . ' to make it consistent: ' . $e->getMessage(),
249
+                ['app' => 'files_sharing']
250
+            );
251
+        }
252
+    }
253
+    /**
254
+     * @param string $userId
255
+     * @param list<array{IShare, array<IShare>}> $superShares
256
+     * @param IStorageFactory $loader
257
+     * @param IUser $user
258
+     * @return array IMountPoint indexed by mount point
259
+     * @throws Exception
260
+     */
261
+    public function getMountsFromSuperShares(
262
+        IUser $user,
263
+        array $superShares,
264
+        IStorageFactory $loader,
265
+    ): array {
266
+        $userId = $user->getUID();
267
+        $allMounts = $this->mountManager->getAll();
268
+        $mounts = [];
269
+        $view = new View('/' . $userId . '/files');
270
+        $ownerViews = [];
271
+        $sharingDisabledForUser = $this->shareManager->sharingDisabledForUser($userId);
272
+        /** @var CappedMemoryCache<bool> $folderExistCache */
273
+        $foldersExistCache = new CappedMemoryCache();
274
+
275
+        $validShareCache = $this->cacheFactory->createLocal('share-valid-mountpoint-max');
276
+        $maxValidatedShare = $validShareCache->get($userId) ?? 0;
277
+        $newMaxValidatedShare = $maxValidatedShare;
278
+
279
+        foreach ($superShares as $share) {
280
+            [$parentShare, $groupedShares] = $share;
281
+            try {
282
+                if ($parentShare->getStatus() !== IShare::STATUS_ACCEPTED
283
+                    && ($parentShare->getShareType() === IShare::TYPE_GROUP
284
+                        || $parentShare->getShareType() === IShare::TYPE_USERGROUP
285
+                        || $parentShare->getShareType() === IShare::TYPE_USER)
286
+                ) {
287
+                    continue;
288
+                }
289
+
290
+                $owner = $parentShare->getShareOwner();
291
+                if (!isset($ownerViews[$owner])) {
292
+                    $ownerViews[$owner] = new View('/' . $owner . '/files');
293
+                }
294
+                $shareId = (int)$parentShare->getId();
295
+                $mount = new SharedMount(
296
+                    '\OCA\Files_Sharing\SharedStorage',
297
+                    [
298
+                        'user' => $userId,
299
+                        // parent share
300
+                        'superShare' => $parentShare,
301
+                        // children/component of the superShare
302
+                        'groupedShares' => $groupedShares,
303
+                        'ownerView' => $ownerViews[$owner],
304
+                        'sharingDisabledForUser' => $sharingDisabledForUser
305
+                    ],
306
+                    $loader,
307
+                    $this->eventDispatcher,
308
+                    $user,
309
+                );
310
+
311
+                $newMaxValidatedShare = max($shareId, $newMaxValidatedShare);
312
+
313
+                $event = new ShareMountedEvent($mount);
314
+                $this->eventDispatcher->dispatchTyped($event);
315
+
316
+                $mounts[$mount->getMountPoint()] = $allMounts[$mount->getMountPoint()] = $mount;
317
+                foreach ($event->getAdditionalMounts() as $additionalMount) {
318
+                    $mounts[$additionalMount->getMountPoint()] = $additionalMount;
319
+                    $allMounts[$additionalMount->getMountPoint()] = $additionalMount;
320
+                }
321
+            } catch (Exception $e) {
322
+                $this->logger->error(
323
+                    'Error while trying to create shared mount',
324
+                    [
325
+                        'app' => 'files_sharing',
326
+                        'exception' => $e,
327
+                    ],
328
+                );
329
+            }
330
+        }
331
+
332
+        $validShareCache->set($userId, $newMaxValidatedShare, 24 * 60 * 60);
333
+
334
+        // array_filter removes the null values from the array
335
+        return array_filter($mounts);
336
+    }
337
+
338
+    /**
339
+     * Filters out shares owned or shared by the user and ones for which the
340
+     * user has no permissions.
341
+     *
342
+     * @param iterable<IShare> $shares
343
+     * @return iterable<IShare>
344
+     */
345
+    private function filterShares(iterable $shares, string $userId): iterable {
346
+        foreach ($shares as $share) {
347
+            if (
348
+                $share->getPermissions() > 0
349
+                && $share->getShareOwner() !== $userId
350
+                && $share->getSharedBy() !== $userId
351
+            ) {
352
+                yield $share;
353
+            }
354
+        }
355
+    }
356
+
357
+    public function getMountsForPath(
358
+        string $setupPathHint,
359
+        bool $forChildren,
360
+        array $mountProviderArgs,
361
+        IStorageFactory $loader,
362
+    ): array {
363
+        $limit = -1;
364
+        $user = $mountProviderArgs[0]->mountInfo->getUser();
365
+        $userId = $user->getUID();
366
+
367
+        if (!$forChildren) {
368
+            // override path with mount point when fetching without children
369
+            $setupPathHint = $mountProviderArgs[0]->mountInfo->getMountPoint();
370
+        }
371
+
372
+        // remove /uid/files as the target is stored without
373
+        $setupPathHint = \substr($setupPathHint, \strlen('/' . $userId . '/files'));
374
+        // remove trailing slash
375
+        $setupPathHint = \rtrim($setupPathHint, '/');
376
+
377
+        // make sure trailing slash is present when loading children
378
+        if ($forChildren || $setupPathHint === '') {
379
+            $setupPathHint .= '/';
380
+        }
381
+
382
+        $shares = $this->mergeIterables(
383
+            $this->shareManager->getSharedWithByPath($userId, IShare::TYPE_USER, $setupPathHint, $forChildren, $limit),
384
+            $this->shareManager->getSharedWithByPath($userId, IShare::TYPE_GROUP, $setupPathHint, $forChildren, $limit),
385
+            $this->shareManager->getSharedWithByPath($userId, IShare::TYPE_CIRCLE, $setupPathHint, $forChildren, $limit),
386
+            $this->shareManager->getSharedWithByPath($userId, IShare::TYPE_ROOM, $setupPathHint, $forChildren, $limit),
387
+            $this->shareManager->getSharedWithByPath($userId, IShare::TYPE_DECK, $setupPathHint, $forChildren, $limit),
388
+        );
389
+
390
+        $shares = $this->filterShares($shares, $userId);
391
+        $superShares = $this->buildSuperShares($shares, $user);
392
+
393
+        return $this->getMountsFromSuperShares($user, $superShares, $loader);
394
+    }
395
+
396
+    /**
397
+     * @param iterable ...$iterables
398
+     * @return iterable
399
+     */
400
+    private function mergeIterables(...$iterables): iterable {
401
+        foreach ($iterables as $iterable) {
402
+            yield from $iterable;
403
+        }
404
+    }
405 405
 }
Please login to merge, or discard this patch.
Spacing   +6 added lines, -6 removed lines patch added patch discarded remove patch
@@ -94,7 +94,7 @@  discard block
 block discarded – undo
94 94
 		$result = [];
95 95
 		// sort by stime, the super share will be based on the least recent share
96 96
 		foreach ($tmp as &$tmp2) {
97
-			@usort($tmp2, function ($a, $b) {
97
+			@usort($tmp2, function($a, $b) {
98 98
 				$aTime = $a->getShareTime()->getTimestamp();
99 99
 				$bTime = $b->getShareTime()->getTimestamp();
100 100
 				if ($aTime === $bTime) {
@@ -245,7 +245,7 @@  discard block
 block discarded – undo
245 245
 			// null groups which usually appear with group backend
246 246
 			// caching inconsistencies
247 247
 			$this->logger->debug(
248
-				'Could not adjust share target for share ' . $share->getId() . ' to make it consistent: ' . $e->getMessage(),
248
+				'Could not adjust share target for share '.$share->getId().' to make it consistent: '.$e->getMessage(),
249 249
 				['app' => 'files_sharing']
250 250
 			);
251 251
 		}
@@ -266,7 +266,7 @@  discard block
 block discarded – undo
266 266
 		$userId = $user->getUID();
267 267
 		$allMounts = $this->mountManager->getAll();
268 268
 		$mounts = [];
269
-		$view = new View('/' . $userId . '/files');
269
+		$view = new View('/'.$userId.'/files');
270 270
 		$ownerViews = [];
271 271
 		$sharingDisabledForUser = $this->shareManager->sharingDisabledForUser($userId);
272 272
 		/** @var CappedMemoryCache<bool> $folderExistCache */
@@ -289,9 +289,9 @@  discard block
 block discarded – undo
289 289
 
290 290
 				$owner = $parentShare->getShareOwner();
291 291
 				if (!isset($ownerViews[$owner])) {
292
-					$ownerViews[$owner] = new View('/' . $owner . '/files');
292
+					$ownerViews[$owner] = new View('/'.$owner.'/files');
293 293
 				}
294
-				$shareId = (int)$parentShare->getId();
294
+				$shareId = (int) $parentShare->getId();
295 295
 				$mount = new SharedMount(
296 296
 					'\OCA\Files_Sharing\SharedStorage',
297 297
 					[
@@ -370,7 +370,7 @@  discard block
 block discarded – undo
370 370
 		}
371 371
 
372 372
 		// remove /uid/files as the target is stored without
373
-		$setupPathHint = \substr($setupPathHint, \strlen('/' . $userId . '/files'));
373
+		$setupPathHint = \substr($setupPathHint, \strlen('/'.$userId.'/files'));
374 374
 		// remove trailing slash
375 375
 		$setupPathHint = \rtrim($setupPathHint, '/');
376 376
 
Please login to merge, or discard this patch.
apps/files_sharing/lib/ShareTargetValidator.php 2 patches
Indentation   +136 added lines, -136 removed lines patch added patch discarded remove patch
@@ -25,140 +25,140 @@
 block discarded – undo
25 25
  * Validate that mount target is valid
26 26
  */
27 27
 class ShareTargetValidator {
28
-	private CappedMemoryCache $folderExistsCache;
29
-
30
-	public function __construct(
31
-		private readonly IManager $shareManager,
32
-		private readonly IEventDispatcher $eventDispatcher,
33
-		private readonly SetupManager $setupManager,
34
-		private readonly IMountManager $mountManager,
35
-	) {
36
-		$this->folderExistsCache = new CappedMemoryCache();
37
-	}
38
-
39
-	private function getViewForUser(IUser $user): View {
40
-		/**
41
-		 * @psalm-suppress InternalClass
42
-		 * @psalm-suppress InternalMethod
43
-		 */
44
-		return new View('/' . $user->getUID() . '/files');
45
-	}
46
-
47
-	/**
48
-	 * check if the parent folder exists otherwise move the mount point up
49
-	 *
50
-	 * @param array<string, ICachedMountInfo> $allCachedMounts Other mounts for the user, indexed by path
51
-	 * @param IShare[] $childShares
52
-	 * @return string
53
-	 */
54
-	public function verifyMountPoint(
55
-		IUser $user,
56
-		IShare &$share,
57
-		array $allCachedMounts,
58
-		array $childShares,
59
-	): string {
60
-		$mountPoint = basename($share->getTarget());
61
-		$parent = dirname($share->getTarget());
62
-
63
-		$recipientView = $this->getViewForUser($user);
64
-		$event = new VerifyMountPointEvent($share, $recipientView, $parent);
65
-		$this->eventDispatcher->dispatchTyped($event);
66
-		$parent = $event->getParent();
67
-
68
-		/** @psalm-suppress InternalMethod */
69
-		$absoluteParent = $recipientView->getAbsolutePath($parent);
70
-		$this->setupManager->setupForPath($absoluteParent);
71
-		$parentMount = $this->mountManager->find($absoluteParent);
72
-
73
-		$cached = $this->folderExistsCache->get($parent);
74
-		if ($cached) {
75
-			$parentExists = $cached;
76
-		} else {
77
-			$parentCache = $parentMount->getStorage()->getCache();
78
-			$parentExists = $parentCache->inCache($parentMount->getInternalPath($absoluteParent));
79
-			$this->folderExistsCache->set($parent, $parentExists);
80
-		}
81
-		if (!$parentExists) {
82
-			$parent = Helper::getShareFolder($recipientView, $user->getUID());
83
-			/** @psalm-suppress InternalMethod */
84
-			$absoluteParent = $recipientView->getAbsolutePath($parent);
85
-		}
86
-
87
-		$newAbsoluteMountPoint = $this->generateUniqueTarget(
88
-			$share,
89
-			Filesystem::normalizePath($absoluteParent . '/' . $mountPoint),
90
-			$parentMount,
91
-			$allCachedMounts,
92
-		);
93
-
94
-		/** @psalm-suppress InternalMethod */
95
-		$newMountPoint = $recipientView->getRelativePath($newAbsoluteMountPoint);
96
-		if ($newMountPoint === null) {
97
-			return $share->getTarget();
98
-		}
99
-
100
-		if ($newMountPoint !== $share->getTarget()) {
101
-			$this->updateFileTarget($user, $newMountPoint, $share, $childShares);
102
-		}
103
-
104
-		return $newMountPoint;
105
-	}
106
-
107
-
108
-	/**
109
-	 * @param ICachedMountInfo[] $allCachedMounts
110
-	 */
111
-	private function generateUniqueTarget(
112
-		IShare $share,
113
-		string $absolutePath,
114
-		IMountPoint $parentMount,
115
-		array $allCachedMounts,
116
-	): string {
117
-		$pathInfo = pathinfo($absolutePath);
118
-		$ext = isset($pathInfo['extension']) ? '.' . $pathInfo['extension'] : '';
119
-		$name = $pathInfo['filename'];
120
-		$dir = $pathInfo['dirname'];
121
-
122
-		$i = 2;
123
-		$parentCache = $parentMount->getStorage()->getCache();
124
-		$internalPath = $parentMount->getInternalPath($absolutePath);
125
-		while ($parentCache->inCache($internalPath) || $this->hasConflictingMount($share, $allCachedMounts, $absolutePath)) {
126
-			$absolutePath = Filesystem::normalizePath($dir . '/' . $name . ' (' . $i . ')' . $ext);
127
-			$internalPath = $parentMount->getInternalPath($absolutePath);
128
-			$i++;
129
-		}
130
-
131
-		return $absolutePath;
132
-	}
133
-
134
-	/**
135
-	 * @param ICachedMountInfo[] $allCachedMounts
136
-	 */
137
-	private function hasConflictingMount(IShare $share, array $allCachedMounts, string $absolutePath): bool {
138
-		if (!isset($allCachedMounts[$absolutePath . '/'])) {
139
-			return false;
140
-		}
141
-
142
-		$mount = $allCachedMounts[$absolutePath . '/'];
143
-		if ($mount->getMountProvider() === MountProvider::class && $mount->getRootId() === $share->getNodeId()) {
144
-			// "conflicting" mount is a mount for the current share
145
-			return false;
146
-		}
147
-
148
-		return true;
149
-	}
150
-
151
-	/**
152
-	 * update fileTarget in the database if the mount point changed
153
-	 *
154
-	 * @param IShare[] $childShares
155
-	 */
156
-	private function updateFileTarget(IUser $user, string $newPath, IShare &$share, array $childShares) {
157
-		$share->setTarget($newPath);
158
-
159
-		foreach ($childShares as $tmpShare) {
160
-			$tmpShare->setTarget($newPath);
161
-			$this->shareManager->moveShare($tmpShare, $user->getUID());
162
-		}
163
-	}
28
+    private CappedMemoryCache $folderExistsCache;
29
+
30
+    public function __construct(
31
+        private readonly IManager $shareManager,
32
+        private readonly IEventDispatcher $eventDispatcher,
33
+        private readonly SetupManager $setupManager,
34
+        private readonly IMountManager $mountManager,
35
+    ) {
36
+        $this->folderExistsCache = new CappedMemoryCache();
37
+    }
38
+
39
+    private function getViewForUser(IUser $user): View {
40
+        /**
41
+         * @psalm-suppress InternalClass
42
+         * @psalm-suppress InternalMethod
43
+         */
44
+        return new View('/' . $user->getUID() . '/files');
45
+    }
46
+
47
+    /**
48
+     * check if the parent folder exists otherwise move the mount point up
49
+     *
50
+     * @param array<string, ICachedMountInfo> $allCachedMounts Other mounts for the user, indexed by path
51
+     * @param IShare[] $childShares
52
+     * @return string
53
+     */
54
+    public function verifyMountPoint(
55
+        IUser $user,
56
+        IShare &$share,
57
+        array $allCachedMounts,
58
+        array $childShares,
59
+    ): string {
60
+        $mountPoint = basename($share->getTarget());
61
+        $parent = dirname($share->getTarget());
62
+
63
+        $recipientView = $this->getViewForUser($user);
64
+        $event = new VerifyMountPointEvent($share, $recipientView, $parent);
65
+        $this->eventDispatcher->dispatchTyped($event);
66
+        $parent = $event->getParent();
67
+
68
+        /** @psalm-suppress InternalMethod */
69
+        $absoluteParent = $recipientView->getAbsolutePath($parent);
70
+        $this->setupManager->setupForPath($absoluteParent);
71
+        $parentMount = $this->mountManager->find($absoluteParent);
72
+
73
+        $cached = $this->folderExistsCache->get($parent);
74
+        if ($cached) {
75
+            $parentExists = $cached;
76
+        } else {
77
+            $parentCache = $parentMount->getStorage()->getCache();
78
+            $parentExists = $parentCache->inCache($parentMount->getInternalPath($absoluteParent));
79
+            $this->folderExistsCache->set($parent, $parentExists);
80
+        }
81
+        if (!$parentExists) {
82
+            $parent = Helper::getShareFolder($recipientView, $user->getUID());
83
+            /** @psalm-suppress InternalMethod */
84
+            $absoluteParent = $recipientView->getAbsolutePath($parent);
85
+        }
86
+
87
+        $newAbsoluteMountPoint = $this->generateUniqueTarget(
88
+            $share,
89
+            Filesystem::normalizePath($absoluteParent . '/' . $mountPoint),
90
+            $parentMount,
91
+            $allCachedMounts,
92
+        );
93
+
94
+        /** @psalm-suppress InternalMethod */
95
+        $newMountPoint = $recipientView->getRelativePath($newAbsoluteMountPoint);
96
+        if ($newMountPoint === null) {
97
+            return $share->getTarget();
98
+        }
99
+
100
+        if ($newMountPoint !== $share->getTarget()) {
101
+            $this->updateFileTarget($user, $newMountPoint, $share, $childShares);
102
+        }
103
+
104
+        return $newMountPoint;
105
+    }
106
+
107
+
108
+    /**
109
+     * @param ICachedMountInfo[] $allCachedMounts
110
+     */
111
+    private function generateUniqueTarget(
112
+        IShare $share,
113
+        string $absolutePath,
114
+        IMountPoint $parentMount,
115
+        array $allCachedMounts,
116
+    ): string {
117
+        $pathInfo = pathinfo($absolutePath);
118
+        $ext = isset($pathInfo['extension']) ? '.' . $pathInfo['extension'] : '';
119
+        $name = $pathInfo['filename'];
120
+        $dir = $pathInfo['dirname'];
121
+
122
+        $i = 2;
123
+        $parentCache = $parentMount->getStorage()->getCache();
124
+        $internalPath = $parentMount->getInternalPath($absolutePath);
125
+        while ($parentCache->inCache($internalPath) || $this->hasConflictingMount($share, $allCachedMounts, $absolutePath)) {
126
+            $absolutePath = Filesystem::normalizePath($dir . '/' . $name . ' (' . $i . ')' . $ext);
127
+            $internalPath = $parentMount->getInternalPath($absolutePath);
128
+            $i++;
129
+        }
130
+
131
+        return $absolutePath;
132
+    }
133
+
134
+    /**
135
+     * @param ICachedMountInfo[] $allCachedMounts
136
+     */
137
+    private function hasConflictingMount(IShare $share, array $allCachedMounts, string $absolutePath): bool {
138
+        if (!isset($allCachedMounts[$absolutePath . '/'])) {
139
+            return false;
140
+        }
141
+
142
+        $mount = $allCachedMounts[$absolutePath . '/'];
143
+        if ($mount->getMountProvider() === MountProvider::class && $mount->getRootId() === $share->getNodeId()) {
144
+            // "conflicting" mount is a mount for the current share
145
+            return false;
146
+        }
147
+
148
+        return true;
149
+    }
150
+
151
+    /**
152
+     * update fileTarget in the database if the mount point changed
153
+     *
154
+     * @param IShare[] $childShares
155
+     */
156
+    private function updateFileTarget(IUser $user, string $newPath, IShare &$share, array $childShares) {
157
+        $share->setTarget($newPath);
158
+
159
+        foreach ($childShares as $tmpShare) {
160
+            $tmpShare->setTarget($newPath);
161
+            $this->shareManager->moveShare($tmpShare, $user->getUID());
162
+        }
163
+    }
164 164
 }
Please login to merge, or discard this patch.
Spacing   +8 added lines, -8 removed lines patch added patch discarded remove patch
@@ -41,7 +41,7 @@  discard block
 block discarded – undo
41 41
 		 * @psalm-suppress InternalClass
42 42
 		 * @psalm-suppress InternalMethod
43 43
 		 */
44
-		return new View('/' . $user->getUID() . '/files');
44
+		return new View('/'.$user->getUID().'/files');
45 45
 	}
46 46
 
47 47
 	/**
@@ -53,7 +53,7 @@  discard block
 block discarded – undo
53 53
 	 */
54 54
 	public function verifyMountPoint(
55 55
 		IUser $user,
56
-		IShare &$share,
56
+		IShare & $share,
57 57
 		array $allCachedMounts,
58 58
 		array $childShares,
59 59
 	): string {
@@ -86,7 +86,7 @@  discard block
 block discarded – undo
86 86
 
87 87
 		$newAbsoluteMountPoint = $this->generateUniqueTarget(
88 88
 			$share,
89
-			Filesystem::normalizePath($absoluteParent . '/' . $mountPoint),
89
+			Filesystem::normalizePath($absoluteParent.'/'.$mountPoint),
90 90
 			$parentMount,
91 91
 			$allCachedMounts,
92 92
 		);
@@ -115,7 +115,7 @@  discard block
 block discarded – undo
115 115
 		array $allCachedMounts,
116 116
 	): string {
117 117
 		$pathInfo = pathinfo($absolutePath);
118
-		$ext = isset($pathInfo['extension']) ? '.' . $pathInfo['extension'] : '';
118
+		$ext = isset($pathInfo['extension']) ? '.'.$pathInfo['extension'] : '';
119 119
 		$name = $pathInfo['filename'];
120 120
 		$dir = $pathInfo['dirname'];
121 121
 
@@ -123,7 +123,7 @@  discard block
 block discarded – undo
123 123
 		$parentCache = $parentMount->getStorage()->getCache();
124 124
 		$internalPath = $parentMount->getInternalPath($absolutePath);
125 125
 		while ($parentCache->inCache($internalPath) || $this->hasConflictingMount($share, $allCachedMounts, $absolutePath)) {
126
-			$absolutePath = Filesystem::normalizePath($dir . '/' . $name . ' (' . $i . ')' . $ext);
126
+			$absolutePath = Filesystem::normalizePath($dir.'/'.$name.' ('.$i.')'.$ext);
127 127
 			$internalPath = $parentMount->getInternalPath($absolutePath);
128 128
 			$i++;
129 129
 		}
@@ -135,11 +135,11 @@  discard block
 block discarded – undo
135 135
 	 * @param ICachedMountInfo[] $allCachedMounts
136 136
 	 */
137 137
 	private function hasConflictingMount(IShare $share, array $allCachedMounts, string $absolutePath): bool {
138
-		if (!isset($allCachedMounts[$absolutePath . '/'])) {
138
+		if (!isset($allCachedMounts[$absolutePath.'/'])) {
139 139
 			return false;
140 140
 		}
141 141
 
142
-		$mount = $allCachedMounts[$absolutePath . '/'];
142
+		$mount = $allCachedMounts[$absolutePath.'/'];
143 143
 		if ($mount->getMountProvider() === MountProvider::class && $mount->getRootId() === $share->getNodeId()) {
144 144
 			// "conflicting" mount is a mount for the current share
145 145
 			return false;
@@ -153,7 +153,7 @@  discard block
 block discarded – undo
153 153
 	 *
154 154
 	 * @param IShare[] $childShares
155 155
 	 */
156
-	private function updateFileTarget(IUser $user, string $newPath, IShare &$share, array $childShares) {
156
+	private function updateFileTarget(IUser $user, string $newPath, IShare & $share, array $childShares) {
157 157
 		$share->setTarget($newPath);
158 158
 
159 159
 		foreach ($childShares as $tmpShare) {
Please login to merge, or discard this patch.
apps/files_sharing/composer/composer/autoload_static.php 1 patch
Spacing   +104 added lines, -104 removed lines patch added patch discarded remove patch
@@ -6,123 +6,123 @@
 block discarded – undo
6 6
 
7 7
 class ComposerStaticInitFiles_Sharing
8 8
 {
9
-    public static $prefixLengthsPsr4 = array (
9
+    public static $prefixLengthsPsr4 = array(
10 10
         'O' =>
11
-        array (
11
+        array(
12 12
             'OCA\\Files_Sharing\\' => 18,
13 13
         ),
14 14
     );
15 15
 
16
-    public static $prefixDirsPsr4 = array (
16
+    public static $prefixDirsPsr4 = array(
17 17
         'OCA\\Files_Sharing\\' =>
18
-        array (
19
-            0 => __DIR__ . '/..' . '/../lib',
18
+        array(
19
+            0 => __DIR__.'/..'.'/../lib',
20 20
         ),
21 21
     );
22 22
 
23
-    public static $classMap = array (
24
-        'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php',
25
-        'OCA\\Files_Sharing\\Activity\\Filter' => __DIR__ . '/..' . '/../lib/Activity/Filter.php',
26
-        'OCA\\Files_Sharing\\Activity\\Providers\\Base' => __DIR__ . '/..' . '/../lib/Activity/Providers/Base.php',
27
-        'OCA\\Files_Sharing\\Activity\\Providers\\Downloads' => __DIR__ . '/..' . '/../lib/Activity/Providers/Downloads.php',
28
-        'OCA\\Files_Sharing\\Activity\\Providers\\Groups' => __DIR__ . '/..' . '/../lib/Activity/Providers/Groups.php',
29
-        'OCA\\Files_Sharing\\Activity\\Providers\\PublicLinks' => __DIR__ . '/..' . '/../lib/Activity/Providers/PublicLinks.php',
30
-        'OCA\\Files_Sharing\\Activity\\Providers\\RemoteShares' => __DIR__ . '/..' . '/../lib/Activity/Providers/RemoteShares.php',
31
-        'OCA\\Files_Sharing\\Activity\\Providers\\Users' => __DIR__ . '/..' . '/../lib/Activity/Providers/Users.php',
32
-        'OCA\\Files_Sharing\\Activity\\Settings\\PublicLinks' => __DIR__ . '/..' . '/../lib/Activity/Settings/PublicLinks.php',
33
-        'OCA\\Files_Sharing\\Activity\\Settings\\PublicLinksUpload' => __DIR__ . '/..' . '/../lib/Activity/Settings/PublicLinksUpload.php',
34
-        'OCA\\Files_Sharing\\Activity\\Settings\\RemoteShare' => __DIR__ . '/..' . '/../lib/Activity/Settings/RemoteShare.php',
35
-        'OCA\\Files_Sharing\\Activity\\Settings\\ShareActivitySettings' => __DIR__ . '/..' . '/../lib/Activity/Settings/ShareActivitySettings.php',
36
-        'OCA\\Files_Sharing\\Activity\\Settings\\Shared' => __DIR__ . '/..' . '/../lib/Activity/Settings/Shared.php',
37
-        'OCA\\Files_Sharing\\AppInfo\\Application' => __DIR__ . '/..' . '/../lib/AppInfo/Application.php',
38
-        'OCA\\Files_Sharing\\BackgroundJob\\FederatedSharesDiscoverJob' => __DIR__ . '/..' . '/../lib/BackgroundJob/FederatedSharesDiscoverJob.php',
39
-        'OCA\\Files_Sharing\\Cache' => __DIR__ . '/..' . '/../lib/Cache.php',
40
-        'OCA\\Files_Sharing\\Capabilities' => __DIR__ . '/..' . '/../lib/Capabilities.php',
41
-        'OCA\\Files_Sharing\\Collaboration\\ShareRecipientSorter' => __DIR__ . '/..' . '/../lib/Collaboration/ShareRecipientSorter.php',
42
-        'OCA\\Files_Sharing\\Command\\CleanupRemoteStorages' => __DIR__ . '/..' . '/../lib/Command/CleanupRemoteStorages.php',
43
-        'OCA\\Files_Sharing\\Command\\DeleteOrphanShares' => __DIR__ . '/..' . '/../lib/Command/DeleteOrphanShares.php',
44
-        'OCA\\Files_Sharing\\Command\\ExiprationNotification' => __DIR__ . '/..' . '/../lib/Command/ExiprationNotification.php',
45
-        'OCA\\Files_Sharing\\Command\\FixShareOwners' => __DIR__ . '/..' . '/../lib/Command/FixShareOwners.php',
46
-        'OCA\\Files_Sharing\\Command\\ListShares' => __DIR__ . '/..' . '/../lib/Command/ListShares.php',
47
-        'OCA\\Files_Sharing\\Config\\ConfigLexicon' => __DIR__ . '/..' . '/../lib/Config/ConfigLexicon.php',
48
-        'OCA\\Files_Sharing\\Controller\\AcceptController' => __DIR__ . '/..' . '/../lib/Controller/AcceptController.php',
49
-        'OCA\\Files_Sharing\\Controller\\DeletedShareAPIController' => __DIR__ . '/..' . '/../lib/Controller/DeletedShareAPIController.php',
50
-        'OCA\\Files_Sharing\\Controller\\ExternalSharesController' => __DIR__ . '/..' . '/../lib/Controller/ExternalSharesController.php',
51
-        'OCA\\Files_Sharing\\Controller\\PublicPreviewController' => __DIR__ . '/..' . '/../lib/Controller/PublicPreviewController.php',
52
-        'OCA\\Files_Sharing\\Controller\\RemoteController' => __DIR__ . '/..' . '/../lib/Controller/RemoteController.php',
53
-        'OCA\\Files_Sharing\\Controller\\SettingsController' => __DIR__ . '/..' . '/../lib/Controller/SettingsController.php',
54
-        'OCA\\Files_Sharing\\Controller\\ShareAPIController' => __DIR__ . '/..' . '/../lib/Controller/ShareAPIController.php',
55
-        'OCA\\Files_Sharing\\Controller\\ShareController' => __DIR__ . '/..' . '/../lib/Controller/ShareController.php',
56
-        'OCA\\Files_Sharing\\Controller\\ShareInfoController' => __DIR__ . '/..' . '/../lib/Controller/ShareInfoController.php',
57
-        'OCA\\Files_Sharing\\Controller\\ShareesAPIController' => __DIR__ . '/..' . '/../lib/Controller/ShareesAPIController.php',
58
-        'OCA\\Files_Sharing\\DefaultPublicShareTemplateProvider' => __DIR__ . '/..' . '/../lib/DefaultPublicShareTemplateProvider.php',
59
-        'OCA\\Files_Sharing\\DeleteOrphanedSharesJob' => __DIR__ . '/..' . '/../lib/DeleteOrphanedSharesJob.php',
60
-        'OCA\\Files_Sharing\\Event\\BeforeTemplateRenderedEvent' => __DIR__ . '/..' . '/../lib/Event/BeforeTemplateRenderedEvent.php',
61
-        'OCA\\Files_Sharing\\Event\\ShareLinkAccessedEvent' => __DIR__ . '/..' . '/../lib/Event/ShareLinkAccessedEvent.php',
62
-        'OCA\\Files_Sharing\\Event\\ShareMountedEvent' => __DIR__ . '/..' . '/../lib/Event/ShareMountedEvent.php',
63
-        'OCA\\Files_Sharing\\Event\\UserShareAccessUpdatedEvent' => __DIR__ . '/..' . '/../lib/Event/UserShareAccessUpdatedEvent.php',
64
-        'OCA\\Files_Sharing\\Exceptions\\BrokenPath' => __DIR__ . '/..' . '/../lib/Exceptions/BrokenPath.php',
65
-        'OCA\\Files_Sharing\\Exceptions\\S2SException' => __DIR__ . '/..' . '/../lib/Exceptions/S2SException.php',
66
-        'OCA\\Files_Sharing\\Exceptions\\SharingRightsException' => __DIR__ . '/..' . '/../lib/Exceptions/SharingRightsException.php',
67
-        'OCA\\Files_Sharing\\ExpireSharesJob' => __DIR__ . '/..' . '/../lib/ExpireSharesJob.php',
68
-        'OCA\\Files_Sharing\\External\\Cache' => __DIR__ . '/..' . '/../lib/External/Cache.php',
69
-        'OCA\\Files_Sharing\\External\\ExternalShare' => __DIR__ . '/..' . '/../lib/External/ExternalShare.php',
70
-        'OCA\\Files_Sharing\\External\\ExternalShareMapper' => __DIR__ . '/..' . '/../lib/External/ExternalShareMapper.php',
71
-        'OCA\\Files_Sharing\\External\\Manager' => __DIR__ . '/..' . '/../lib/External/Manager.php',
72
-        'OCA\\Files_Sharing\\External\\Mount' => __DIR__ . '/..' . '/../lib/External/Mount.php',
73
-        'OCA\\Files_Sharing\\External\\MountProvider' => __DIR__ . '/..' . '/../lib/External/MountProvider.php',
74
-        'OCA\\Files_Sharing\\External\\Scanner' => __DIR__ . '/..' . '/../lib/External/Scanner.php',
75
-        'OCA\\Files_Sharing\\External\\Storage' => __DIR__ . '/..' . '/../lib/External/Storage.php',
76
-        'OCA\\Files_Sharing\\External\\Watcher' => __DIR__ . '/..' . '/../lib/External/Watcher.php',
77
-        'OCA\\Files_Sharing\\Helper' => __DIR__ . '/..' . '/../lib/Helper.php',
78
-        'OCA\\Files_Sharing\\Hooks' => __DIR__ . '/..' . '/../lib/Hooks.php',
79
-        'OCA\\Files_Sharing\\ISharedMountPoint' => __DIR__ . '/..' . '/../lib/ISharedMountPoint.php',
80
-        'OCA\\Files_Sharing\\ISharedStorage' => __DIR__ . '/..' . '/../lib/ISharedStorage.php',
81
-        'OCA\\Files_Sharing\\Listener\\BeforeDirectFileDownloadListener' => __DIR__ . '/..' . '/../lib/Listener/BeforeDirectFileDownloadListener.php',
82
-        'OCA\\Files_Sharing\\Listener\\BeforeNodeReadListener' => __DIR__ . '/..' . '/../lib/Listener/BeforeNodeReadListener.php',
83
-        'OCA\\Files_Sharing\\Listener\\BeforeZipCreatedListener' => __DIR__ . '/..' . '/../lib/Listener/BeforeZipCreatedListener.php',
84
-        'OCA\\Files_Sharing\\Listener\\LoadAdditionalListener' => __DIR__ . '/..' . '/../lib/Listener/LoadAdditionalListener.php',
85
-        'OCA\\Files_Sharing\\Listener\\LoadPublicFileRequestAuthListener' => __DIR__ . '/..' . '/../lib/Listener/LoadPublicFileRequestAuthListener.php',
86
-        'OCA\\Files_Sharing\\Listener\\LoadSidebarListener' => __DIR__ . '/..' . '/../lib/Listener/LoadSidebarListener.php',
87
-        'OCA\\Files_Sharing\\Listener\\ShareInteractionListener' => __DIR__ . '/..' . '/../lib/Listener/ShareInteractionListener.php',
88
-        'OCA\\Files_Sharing\\Listener\\SharesUpdatedListener' => __DIR__ . '/..' . '/../lib/Listener/SharesUpdatedListener.php',
89
-        'OCA\\Files_Sharing\\Listener\\UserAddedToGroupListener' => __DIR__ . '/..' . '/../lib/Listener/UserAddedToGroupListener.php',
90
-        'OCA\\Files_Sharing\\Listener\\UserShareAcceptanceListener' => __DIR__ . '/..' . '/../lib/Listener/UserShareAcceptanceListener.php',
91
-        'OCA\\Files_Sharing\\Middleware\\OCSShareAPIMiddleware' => __DIR__ . '/..' . '/../lib/Middleware/OCSShareAPIMiddleware.php',
92
-        'OCA\\Files_Sharing\\Middleware\\ShareInfoMiddleware' => __DIR__ . '/..' . '/../lib/Middleware/ShareInfoMiddleware.php',
93
-        'OCA\\Files_Sharing\\Middleware\\SharingCheckMiddleware' => __DIR__ . '/..' . '/../lib/Middleware/SharingCheckMiddleware.php',
94
-        'OCA\\Files_Sharing\\Migration\\OwncloudGuestShareType' => __DIR__ . '/..' . '/../lib/Migration/OwncloudGuestShareType.php',
95
-        'OCA\\Files_Sharing\\Migration\\SetAcceptedStatus' => __DIR__ . '/..' . '/../lib/Migration/SetAcceptedStatus.php',
96
-        'OCA\\Files_Sharing\\Migration\\SetPasswordColumn' => __DIR__ . '/..' . '/../lib/Migration/SetPasswordColumn.php',
97
-        'OCA\\Files_Sharing\\Migration\\Version11300Date20201120141438' => __DIR__ . '/..' . '/../lib/Migration/Version11300Date20201120141438.php',
98
-        'OCA\\Files_Sharing\\Migration\\Version21000Date20201223143245' => __DIR__ . '/..' . '/../lib/Migration/Version21000Date20201223143245.php',
99
-        'OCA\\Files_Sharing\\Migration\\Version22000Date20210216084241' => __DIR__ . '/..' . '/../lib/Migration/Version22000Date20210216084241.php',
100
-        'OCA\\Files_Sharing\\Migration\\Version24000Date20220208195521' => __DIR__ . '/..' . '/../lib/Migration/Version24000Date20220208195521.php',
101
-        'OCA\\Files_Sharing\\Migration\\Version24000Date20220404142216' => __DIR__ . '/..' . '/../lib/Migration/Version24000Date20220404142216.php',
102
-        'OCA\\Files_Sharing\\Migration\\Version31000Date20240821142813' => __DIR__ . '/..' . '/../lib/Migration/Version31000Date20240821142813.php',
103
-        'OCA\\Files_Sharing\\Migration\\Version32000Date20251017081948' => __DIR__ . '/..' . '/../lib/Migration/Version32000Date20251017081948.php',
104
-        'OCA\\Files_Sharing\\Migration\\Version33000Date20251030081948' => __DIR__ . '/..' . '/../lib/Migration/Version33000Date20251030081948.php',
105
-        'OCA\\Files_Sharing\\MountProvider' => __DIR__ . '/..' . '/../lib/MountProvider.php',
106
-        'OCA\\Files_Sharing\\Notification\\Listener' => __DIR__ . '/..' . '/../lib/Notification/Listener.php',
107
-        'OCA\\Files_Sharing\\Notification\\Notifier' => __DIR__ . '/..' . '/../lib/Notification/Notifier.php',
108
-        'OCA\\Files_Sharing\\OpenMetrics\\SharesCountMetric' => __DIR__ . '/..' . '/../lib/OpenMetrics/SharesCountMetric.php',
109
-        'OCA\\Files_Sharing\\OrphanHelper' => __DIR__ . '/..' . '/../lib/OrphanHelper.php',
110
-        'OCA\\Files_Sharing\\ResponseDefinitions' => __DIR__ . '/..' . '/../lib/ResponseDefinitions.php',
111
-        'OCA\\Files_Sharing\\Scanner' => __DIR__ . '/..' . '/../lib/Scanner.php',
112
-        'OCA\\Files_Sharing\\Settings\\Personal' => __DIR__ . '/..' . '/../lib/Settings/Personal.php',
113
-        'OCA\\Files_Sharing\\ShareBackend\\File' => __DIR__ . '/..' . '/../lib/ShareBackend/File.php',
114
-        'OCA\\Files_Sharing\\ShareBackend\\Folder' => __DIR__ . '/..' . '/../lib/ShareBackend/Folder.php',
115
-        'OCA\\Files_Sharing\\ShareTargetValidator' => __DIR__ . '/..' . '/../lib/ShareTargetValidator.php',
116
-        'OCA\\Files_Sharing\\SharedMount' => __DIR__ . '/..' . '/../lib/SharedMount.php',
117
-        'OCA\\Files_Sharing\\SharedStorage' => __DIR__ . '/..' . '/../lib/SharedStorage.php',
118
-        'OCA\\Files_Sharing\\SharesReminderJob' => __DIR__ . '/..' . '/../lib/SharesReminderJob.php',
119
-        'OCA\\Files_Sharing\\Updater' => __DIR__ . '/..' . '/../lib/Updater.php',
120
-        'OCA\\Files_Sharing\\ViewOnly' => __DIR__ . '/..' . '/../lib/ViewOnly.php',
23
+    public static $classMap = array(
24
+        'Composer\\InstalledVersions' => __DIR__.'/..'.'/composer/InstalledVersions.php',
25
+        'OCA\\Files_Sharing\\Activity\\Filter' => __DIR__.'/..'.'/../lib/Activity/Filter.php',
26
+        'OCA\\Files_Sharing\\Activity\\Providers\\Base' => __DIR__.'/..'.'/../lib/Activity/Providers/Base.php',
27
+        'OCA\\Files_Sharing\\Activity\\Providers\\Downloads' => __DIR__.'/..'.'/../lib/Activity/Providers/Downloads.php',
28
+        'OCA\\Files_Sharing\\Activity\\Providers\\Groups' => __DIR__.'/..'.'/../lib/Activity/Providers/Groups.php',
29
+        'OCA\\Files_Sharing\\Activity\\Providers\\PublicLinks' => __DIR__.'/..'.'/../lib/Activity/Providers/PublicLinks.php',
30
+        'OCA\\Files_Sharing\\Activity\\Providers\\RemoteShares' => __DIR__.'/..'.'/../lib/Activity/Providers/RemoteShares.php',
31
+        'OCA\\Files_Sharing\\Activity\\Providers\\Users' => __DIR__.'/..'.'/../lib/Activity/Providers/Users.php',
32
+        'OCA\\Files_Sharing\\Activity\\Settings\\PublicLinks' => __DIR__.'/..'.'/../lib/Activity/Settings/PublicLinks.php',
33
+        'OCA\\Files_Sharing\\Activity\\Settings\\PublicLinksUpload' => __DIR__.'/..'.'/../lib/Activity/Settings/PublicLinksUpload.php',
34
+        'OCA\\Files_Sharing\\Activity\\Settings\\RemoteShare' => __DIR__.'/..'.'/../lib/Activity/Settings/RemoteShare.php',
35
+        'OCA\\Files_Sharing\\Activity\\Settings\\ShareActivitySettings' => __DIR__.'/..'.'/../lib/Activity/Settings/ShareActivitySettings.php',
36
+        'OCA\\Files_Sharing\\Activity\\Settings\\Shared' => __DIR__.'/..'.'/../lib/Activity/Settings/Shared.php',
37
+        'OCA\\Files_Sharing\\AppInfo\\Application' => __DIR__.'/..'.'/../lib/AppInfo/Application.php',
38
+        'OCA\\Files_Sharing\\BackgroundJob\\FederatedSharesDiscoverJob' => __DIR__.'/..'.'/../lib/BackgroundJob/FederatedSharesDiscoverJob.php',
39
+        'OCA\\Files_Sharing\\Cache' => __DIR__.'/..'.'/../lib/Cache.php',
40
+        'OCA\\Files_Sharing\\Capabilities' => __DIR__.'/..'.'/../lib/Capabilities.php',
41
+        'OCA\\Files_Sharing\\Collaboration\\ShareRecipientSorter' => __DIR__.'/..'.'/../lib/Collaboration/ShareRecipientSorter.php',
42
+        'OCA\\Files_Sharing\\Command\\CleanupRemoteStorages' => __DIR__.'/..'.'/../lib/Command/CleanupRemoteStorages.php',
43
+        'OCA\\Files_Sharing\\Command\\DeleteOrphanShares' => __DIR__.'/..'.'/../lib/Command/DeleteOrphanShares.php',
44
+        'OCA\\Files_Sharing\\Command\\ExiprationNotification' => __DIR__.'/..'.'/../lib/Command/ExiprationNotification.php',
45
+        'OCA\\Files_Sharing\\Command\\FixShareOwners' => __DIR__.'/..'.'/../lib/Command/FixShareOwners.php',
46
+        'OCA\\Files_Sharing\\Command\\ListShares' => __DIR__.'/..'.'/../lib/Command/ListShares.php',
47
+        'OCA\\Files_Sharing\\Config\\ConfigLexicon' => __DIR__.'/..'.'/../lib/Config/ConfigLexicon.php',
48
+        'OCA\\Files_Sharing\\Controller\\AcceptController' => __DIR__.'/..'.'/../lib/Controller/AcceptController.php',
49
+        'OCA\\Files_Sharing\\Controller\\DeletedShareAPIController' => __DIR__.'/..'.'/../lib/Controller/DeletedShareAPIController.php',
50
+        'OCA\\Files_Sharing\\Controller\\ExternalSharesController' => __DIR__.'/..'.'/../lib/Controller/ExternalSharesController.php',
51
+        'OCA\\Files_Sharing\\Controller\\PublicPreviewController' => __DIR__.'/..'.'/../lib/Controller/PublicPreviewController.php',
52
+        'OCA\\Files_Sharing\\Controller\\RemoteController' => __DIR__.'/..'.'/../lib/Controller/RemoteController.php',
53
+        'OCA\\Files_Sharing\\Controller\\SettingsController' => __DIR__.'/..'.'/../lib/Controller/SettingsController.php',
54
+        'OCA\\Files_Sharing\\Controller\\ShareAPIController' => __DIR__.'/..'.'/../lib/Controller/ShareAPIController.php',
55
+        'OCA\\Files_Sharing\\Controller\\ShareController' => __DIR__.'/..'.'/../lib/Controller/ShareController.php',
56
+        'OCA\\Files_Sharing\\Controller\\ShareInfoController' => __DIR__.'/..'.'/../lib/Controller/ShareInfoController.php',
57
+        'OCA\\Files_Sharing\\Controller\\ShareesAPIController' => __DIR__.'/..'.'/../lib/Controller/ShareesAPIController.php',
58
+        'OCA\\Files_Sharing\\DefaultPublicShareTemplateProvider' => __DIR__.'/..'.'/../lib/DefaultPublicShareTemplateProvider.php',
59
+        'OCA\\Files_Sharing\\DeleteOrphanedSharesJob' => __DIR__.'/..'.'/../lib/DeleteOrphanedSharesJob.php',
60
+        'OCA\\Files_Sharing\\Event\\BeforeTemplateRenderedEvent' => __DIR__.'/..'.'/../lib/Event/BeforeTemplateRenderedEvent.php',
61
+        'OCA\\Files_Sharing\\Event\\ShareLinkAccessedEvent' => __DIR__.'/..'.'/../lib/Event/ShareLinkAccessedEvent.php',
62
+        'OCA\\Files_Sharing\\Event\\ShareMountedEvent' => __DIR__.'/..'.'/../lib/Event/ShareMountedEvent.php',
63
+        'OCA\\Files_Sharing\\Event\\UserShareAccessUpdatedEvent' => __DIR__.'/..'.'/../lib/Event/UserShareAccessUpdatedEvent.php',
64
+        'OCA\\Files_Sharing\\Exceptions\\BrokenPath' => __DIR__.'/..'.'/../lib/Exceptions/BrokenPath.php',
65
+        'OCA\\Files_Sharing\\Exceptions\\S2SException' => __DIR__.'/..'.'/../lib/Exceptions/S2SException.php',
66
+        'OCA\\Files_Sharing\\Exceptions\\SharingRightsException' => __DIR__.'/..'.'/../lib/Exceptions/SharingRightsException.php',
67
+        'OCA\\Files_Sharing\\ExpireSharesJob' => __DIR__.'/..'.'/../lib/ExpireSharesJob.php',
68
+        'OCA\\Files_Sharing\\External\\Cache' => __DIR__.'/..'.'/../lib/External/Cache.php',
69
+        'OCA\\Files_Sharing\\External\\ExternalShare' => __DIR__.'/..'.'/../lib/External/ExternalShare.php',
70
+        'OCA\\Files_Sharing\\External\\ExternalShareMapper' => __DIR__.'/..'.'/../lib/External/ExternalShareMapper.php',
71
+        'OCA\\Files_Sharing\\External\\Manager' => __DIR__.'/..'.'/../lib/External/Manager.php',
72
+        'OCA\\Files_Sharing\\External\\Mount' => __DIR__.'/..'.'/../lib/External/Mount.php',
73
+        'OCA\\Files_Sharing\\External\\MountProvider' => __DIR__.'/..'.'/../lib/External/MountProvider.php',
74
+        'OCA\\Files_Sharing\\External\\Scanner' => __DIR__.'/..'.'/../lib/External/Scanner.php',
75
+        'OCA\\Files_Sharing\\External\\Storage' => __DIR__.'/..'.'/../lib/External/Storage.php',
76
+        'OCA\\Files_Sharing\\External\\Watcher' => __DIR__.'/..'.'/../lib/External/Watcher.php',
77
+        'OCA\\Files_Sharing\\Helper' => __DIR__.'/..'.'/../lib/Helper.php',
78
+        'OCA\\Files_Sharing\\Hooks' => __DIR__.'/..'.'/../lib/Hooks.php',
79
+        'OCA\\Files_Sharing\\ISharedMountPoint' => __DIR__.'/..'.'/../lib/ISharedMountPoint.php',
80
+        'OCA\\Files_Sharing\\ISharedStorage' => __DIR__.'/..'.'/../lib/ISharedStorage.php',
81
+        'OCA\\Files_Sharing\\Listener\\BeforeDirectFileDownloadListener' => __DIR__.'/..'.'/../lib/Listener/BeforeDirectFileDownloadListener.php',
82
+        'OCA\\Files_Sharing\\Listener\\BeforeNodeReadListener' => __DIR__.'/..'.'/../lib/Listener/BeforeNodeReadListener.php',
83
+        'OCA\\Files_Sharing\\Listener\\BeforeZipCreatedListener' => __DIR__.'/..'.'/../lib/Listener/BeforeZipCreatedListener.php',
84
+        'OCA\\Files_Sharing\\Listener\\LoadAdditionalListener' => __DIR__.'/..'.'/../lib/Listener/LoadAdditionalListener.php',
85
+        'OCA\\Files_Sharing\\Listener\\LoadPublicFileRequestAuthListener' => __DIR__.'/..'.'/../lib/Listener/LoadPublicFileRequestAuthListener.php',
86
+        'OCA\\Files_Sharing\\Listener\\LoadSidebarListener' => __DIR__.'/..'.'/../lib/Listener/LoadSidebarListener.php',
87
+        'OCA\\Files_Sharing\\Listener\\ShareInteractionListener' => __DIR__.'/..'.'/../lib/Listener/ShareInteractionListener.php',
88
+        'OCA\\Files_Sharing\\Listener\\SharesUpdatedListener' => __DIR__.'/..'.'/../lib/Listener/SharesUpdatedListener.php',
89
+        'OCA\\Files_Sharing\\Listener\\UserAddedToGroupListener' => __DIR__.'/..'.'/../lib/Listener/UserAddedToGroupListener.php',
90
+        'OCA\\Files_Sharing\\Listener\\UserShareAcceptanceListener' => __DIR__.'/..'.'/../lib/Listener/UserShareAcceptanceListener.php',
91
+        'OCA\\Files_Sharing\\Middleware\\OCSShareAPIMiddleware' => __DIR__.'/..'.'/../lib/Middleware/OCSShareAPIMiddleware.php',
92
+        'OCA\\Files_Sharing\\Middleware\\ShareInfoMiddleware' => __DIR__.'/..'.'/../lib/Middleware/ShareInfoMiddleware.php',
93
+        'OCA\\Files_Sharing\\Middleware\\SharingCheckMiddleware' => __DIR__.'/..'.'/../lib/Middleware/SharingCheckMiddleware.php',
94
+        'OCA\\Files_Sharing\\Migration\\OwncloudGuestShareType' => __DIR__.'/..'.'/../lib/Migration/OwncloudGuestShareType.php',
95
+        'OCA\\Files_Sharing\\Migration\\SetAcceptedStatus' => __DIR__.'/..'.'/../lib/Migration/SetAcceptedStatus.php',
96
+        'OCA\\Files_Sharing\\Migration\\SetPasswordColumn' => __DIR__.'/..'.'/../lib/Migration/SetPasswordColumn.php',
97
+        'OCA\\Files_Sharing\\Migration\\Version11300Date20201120141438' => __DIR__.'/..'.'/../lib/Migration/Version11300Date20201120141438.php',
98
+        'OCA\\Files_Sharing\\Migration\\Version21000Date20201223143245' => __DIR__.'/..'.'/../lib/Migration/Version21000Date20201223143245.php',
99
+        'OCA\\Files_Sharing\\Migration\\Version22000Date20210216084241' => __DIR__.'/..'.'/../lib/Migration/Version22000Date20210216084241.php',
100
+        'OCA\\Files_Sharing\\Migration\\Version24000Date20220208195521' => __DIR__.'/..'.'/../lib/Migration/Version24000Date20220208195521.php',
101
+        'OCA\\Files_Sharing\\Migration\\Version24000Date20220404142216' => __DIR__.'/..'.'/../lib/Migration/Version24000Date20220404142216.php',
102
+        'OCA\\Files_Sharing\\Migration\\Version31000Date20240821142813' => __DIR__.'/..'.'/../lib/Migration/Version31000Date20240821142813.php',
103
+        'OCA\\Files_Sharing\\Migration\\Version32000Date20251017081948' => __DIR__.'/..'.'/../lib/Migration/Version32000Date20251017081948.php',
104
+        'OCA\\Files_Sharing\\Migration\\Version33000Date20251030081948' => __DIR__.'/..'.'/../lib/Migration/Version33000Date20251030081948.php',
105
+        'OCA\\Files_Sharing\\MountProvider' => __DIR__.'/..'.'/../lib/MountProvider.php',
106
+        'OCA\\Files_Sharing\\Notification\\Listener' => __DIR__.'/..'.'/../lib/Notification/Listener.php',
107
+        'OCA\\Files_Sharing\\Notification\\Notifier' => __DIR__.'/..'.'/../lib/Notification/Notifier.php',
108
+        'OCA\\Files_Sharing\\OpenMetrics\\SharesCountMetric' => __DIR__.'/..'.'/../lib/OpenMetrics/SharesCountMetric.php',
109
+        'OCA\\Files_Sharing\\OrphanHelper' => __DIR__.'/..'.'/../lib/OrphanHelper.php',
110
+        'OCA\\Files_Sharing\\ResponseDefinitions' => __DIR__.'/..'.'/../lib/ResponseDefinitions.php',
111
+        'OCA\\Files_Sharing\\Scanner' => __DIR__.'/..'.'/../lib/Scanner.php',
112
+        'OCA\\Files_Sharing\\Settings\\Personal' => __DIR__.'/..'.'/../lib/Settings/Personal.php',
113
+        'OCA\\Files_Sharing\\ShareBackend\\File' => __DIR__.'/..'.'/../lib/ShareBackend/File.php',
114
+        'OCA\\Files_Sharing\\ShareBackend\\Folder' => __DIR__.'/..'.'/../lib/ShareBackend/Folder.php',
115
+        'OCA\\Files_Sharing\\ShareTargetValidator' => __DIR__.'/..'.'/../lib/ShareTargetValidator.php',
116
+        'OCA\\Files_Sharing\\SharedMount' => __DIR__.'/..'.'/../lib/SharedMount.php',
117
+        'OCA\\Files_Sharing\\SharedStorage' => __DIR__.'/..'.'/../lib/SharedStorage.php',
118
+        'OCA\\Files_Sharing\\SharesReminderJob' => __DIR__.'/..'.'/../lib/SharesReminderJob.php',
119
+        'OCA\\Files_Sharing\\Updater' => __DIR__.'/..'.'/../lib/Updater.php',
120
+        'OCA\\Files_Sharing\\ViewOnly' => __DIR__.'/..'.'/../lib/ViewOnly.php',
121 121
     );
122 122
 
123 123
     public static function getInitializer(ClassLoader $loader)
124 124
     {
125
-        return \Closure::bind(function () use ($loader) {
125
+        return \Closure::bind(function() use ($loader) {
126 126
             $loader->prefixLengthsPsr4 = ComposerStaticInitFiles_Sharing::$prefixLengthsPsr4;
127 127
             $loader->prefixDirsPsr4 = ComposerStaticInitFiles_Sharing::$prefixDirsPsr4;
128 128
             $loader->classMap = ComposerStaticInitFiles_Sharing::$classMap;
Please login to merge, or discard this patch.