Completed
Push — master ( 807958...0c51b0 )
by
unknown
40:31 queued 42s
created
lib/private/Files/Config/UserMountCache.php 1 patch
Indentation   +457 added lines, -457 removed lines patch added patch discarded remove patch
@@ -25,461 +25,461 @@
 block discarded – undo
25 25
  */
26 26
 class UserMountCache implements IUserMountCache {
27 27
 
28
-	/**
29
-	 * Cached mount info.
30
-	 * @var CappedMemoryCache<ICachedMountInfo[]>
31
-	 **/
32
-	private CappedMemoryCache $mountsForUsers;
33
-	/**
34
-	 * fileid => internal path mapping for cached mount info.
35
-	 * @var CappedMemoryCache<string>
36
-	 **/
37
-	private CappedMemoryCache $internalPathCache;
38
-	/** @var CappedMemoryCache<array> */
39
-	private CappedMemoryCache $cacheInfoCache;
40
-
41
-	/**
42
-	 * UserMountCache constructor.
43
-	 */
44
-	public function __construct(
45
-		private IDBConnection $connection,
46
-		private IUserManager $userManager,
47
-		private LoggerInterface $logger,
48
-		private IEventLogger $eventLogger,
49
-	) {
50
-		$this->cacheInfoCache = new CappedMemoryCache();
51
-		$this->internalPathCache = new CappedMemoryCache();
52
-		$this->mountsForUsers = new CappedMemoryCache();
53
-	}
54
-
55
-	public function registerMounts(IUser $user, array $mounts, ?array $mountProviderClasses = null) {
56
-		$this->eventLogger->start('fs:setup:user:register', 'Registering mounts for user');
57
-		/** @var array<string, ICachedMountInfo> $newMounts */
58
-		$newMounts = [];
59
-		foreach ($mounts as $mount) {
60
-			// filter out any storages which aren't scanned yet since we aren't interested in files from those storages (yet)
61
-			if ($mount->getStorageRootId() !== -1) {
62
-				$mountInfo = new LazyStorageMountInfo($user, $mount);
63
-				$newMounts[$mountInfo->getKey()] = $mountInfo;
64
-			}
65
-		}
66
-
67
-		$cachedMounts = $this->getMountsForUser($user);
68
-		if (is_array($mountProviderClasses)) {
69
-			$cachedMounts = array_filter($cachedMounts, function (ICachedMountInfo $mountInfo) use ($mountProviderClasses, $newMounts) {
70
-				// for existing mounts that didn't have a mount provider set
71
-				// we still want the ones that map to new mounts
72
-				if ($mountInfo->getMountProvider() === '' && isset($newMounts[$mountInfo->getKey()])) {
73
-					return true;
74
-				}
75
-				return in_array($mountInfo->getMountProvider(), $mountProviderClasses);
76
-			});
77
-		}
78
-
79
-		$addedMounts = [];
80
-		$removedMounts = [];
81
-
82
-		foreach ($newMounts as $mountKey => $newMount) {
83
-			if (!isset($cachedMounts[$mountKey])) {
84
-				$addedMounts[] = $newMount;
85
-			}
86
-		}
87
-
88
-		foreach ($cachedMounts as $mountKey => $cachedMount) {
89
-			if (!isset($newMounts[$mountKey])) {
90
-				$removedMounts[] = $cachedMount;
91
-			}
92
-		}
93
-
94
-		$changedMounts = $this->findChangedMounts($newMounts, $cachedMounts);
95
-
96
-		if ($addedMounts || $removedMounts || $changedMounts) {
97
-			$this->connection->beginTransaction();
98
-			$userUID = $user->getUID();
99
-			try {
100
-				foreach ($addedMounts as $mount) {
101
-					$this->logger->debug("Adding mount '{$mount->getKey()}' for user '$userUID'", ['app' => 'files', 'mount_provider' => $mount->getMountProvider()]);
102
-					$this->addToCache($mount);
103
-					/** @psalm-suppress InvalidArgument */
104
-					$this->mountsForUsers[$userUID][$mount->getKey()] = $mount;
105
-				}
106
-				foreach ($removedMounts as $mount) {
107
-					$this->logger->debug("Removing mount '{$mount->getKey()}' for user '$userUID'", ['app' => 'files', 'mount_provider' => $mount->getMountProvider()]);
108
-					$this->removeFromCache($mount);
109
-					unset($this->mountsForUsers[$userUID][$mount->getKey()]);
110
-				}
111
-				foreach ($changedMounts as $mount) {
112
-					$this->logger->debug("Updating mount '{$mount->getKey()}' for user '$userUID'", ['app' => 'files', 'mount_provider' => $mount->getMountProvider()]);
113
-					$this->updateCachedMount($mount);
114
-					/** @psalm-suppress InvalidArgument */
115
-					$this->mountsForUsers[$userUID][$mount->getKey()] = $mount;
116
-				}
117
-				$this->connection->commit();
118
-			} catch (\Throwable $e) {
119
-				$this->connection->rollBack();
120
-				throw $e;
121
-			}
122
-		}
123
-		$this->eventLogger->end('fs:setup:user:register');
124
-	}
125
-
126
-	/**
127
-	 * @param array<string, ICachedMountInfo> $newMounts
128
-	 * @param array<string, ICachedMountInfo> $cachedMounts
129
-	 * @return ICachedMountInfo[]
130
-	 */
131
-	private function findChangedMounts(array $newMounts, array $cachedMounts) {
132
-		$changed = [];
133
-		foreach ($cachedMounts as $key => $cachedMount) {
134
-			if (isset($newMounts[$key])) {
135
-				$newMount = $newMounts[$key];
136
-				if (
137
-					$newMount->getStorageId() !== $cachedMount->getStorageId() ||
138
-					$newMount->getMountId() !== $cachedMount->getMountId() ||
139
-					$newMount->getMountProvider() !== $cachedMount->getMountProvider()
140
-				) {
141
-					$changed[] = $newMount;
142
-				}
143
-			}
144
-		}
145
-		return $changed;
146
-	}
147
-
148
-	private function addToCache(ICachedMountInfo $mount) {
149
-		if ($mount->getStorageId() !== -1) {
150
-			$this->connection->insertIfNotExist('*PREFIX*mounts', [
151
-				'storage_id' => $mount->getStorageId(),
152
-				'root_id' => $mount->getRootId(),
153
-				'user_id' => $mount->getUser()->getUID(),
154
-				'mount_point' => $mount->getMountPoint(),
155
-				'mount_id' => $mount->getMountId(),
156
-				'mount_provider_class' => $mount->getMountProvider(),
157
-			], ['root_id', 'user_id', 'mount_point']);
158
-		} else {
159
-			// in some cases this is legitimate, like orphaned shares
160
-			$this->logger->debug('Could not get storage info for mount at ' . $mount->getMountPoint());
161
-		}
162
-	}
163
-
164
-	private function updateCachedMount(ICachedMountInfo $mount) {
165
-		$builder = $this->connection->getQueryBuilder();
166
-
167
-		$query = $builder->update('mounts')
168
-			->set('storage_id', $builder->createNamedParameter($mount->getStorageId()))
169
-			->set('mount_point', $builder->createNamedParameter($mount->getMountPoint()))
170
-			->set('mount_id', $builder->createNamedParameter($mount->getMountId(), IQueryBuilder::PARAM_INT))
171
-			->set('mount_provider_class', $builder->createNamedParameter($mount->getMountProvider()))
172
-			->where($builder->expr()->eq('user_id', $builder->createNamedParameter($mount->getUser()->getUID())))
173
-			->andWhere($builder->expr()->eq('root_id', $builder->createNamedParameter($mount->getRootId(), IQueryBuilder::PARAM_INT)));
174
-
175
-		$query->executeStatement();
176
-	}
177
-
178
-	private function removeFromCache(ICachedMountInfo $mount) {
179
-		$builder = $this->connection->getQueryBuilder();
180
-
181
-		$query = $builder->delete('mounts')
182
-			->where($builder->expr()->eq('user_id', $builder->createNamedParameter($mount->getUser()->getUID())))
183
-			->andWhere($builder->expr()->eq('root_id', $builder->createNamedParameter($mount->getRootId(), IQueryBuilder::PARAM_INT)))
184
-			->andWhere($builder->expr()->eq('mount_point', $builder->createNamedParameter($mount->getMountPoint())));
185
-		$query->executeStatement();
186
-	}
187
-
188
-	/**
189
-	 * @param array $row
190
-	 * @param (callable(CachedMountInfo): string)|null $pathCallback
191
-	 * @return CachedMountInfo
192
-	 */
193
-	private function dbRowToMountInfo(array $row, ?callable $pathCallback = null): ICachedMountInfo {
194
-		$user = new LazyUser($row['user_id'], $this->userManager);
195
-		$mount_id = $row['mount_id'];
196
-		if (!is_null($mount_id)) {
197
-			$mount_id = (int)$mount_id;
198
-		}
199
-		if ($pathCallback) {
200
-			return new LazyPathCachedMountInfo(
201
-				$user,
202
-				(int)$row['storage_id'],
203
-				(int)$row['root_id'],
204
-				$row['mount_point'],
205
-				$row['mount_provider_class'] ?? '',
206
-				$mount_id,
207
-				$pathCallback,
208
-			);
209
-		} else {
210
-			return new CachedMountInfo(
211
-				$user,
212
-				(int)$row['storage_id'],
213
-				(int)$row['root_id'],
214
-				$row['mount_point'],
215
-				$row['mount_provider_class'] ?? '',
216
-				$mount_id,
217
-				$row['path'] ?? '',
218
-			);
219
-		}
220
-	}
221
-
222
-	/**
223
-	 * @param IUser $user
224
-	 * @return ICachedMountInfo[]
225
-	 */
226
-	public function getMountsForUser(IUser $user) {
227
-		$userUID = $user->getUID();
228
-		if (!$this->userManager->userExists($userUID)) {
229
-			return [];
230
-		}
231
-		if (!isset($this->mountsForUsers[$userUID])) {
232
-			$builder = $this->connection->getQueryBuilder();
233
-			$query = $builder->select('storage_id', 'root_id', 'user_id', 'mount_point', 'mount_id', 'mount_provider_class')
234
-				->from('mounts', 'm')
235
-				->where($builder->expr()->eq('user_id', $builder->createNamedParameter($userUID)));
236
-
237
-			$result = $query->executeQuery();
238
-			$rows = $result->fetchAll();
239
-			$result->closeCursor();
240
-
241
-			/** @var array<string, ICachedMountInfo> $mounts */
242
-			$mounts = [];
243
-			foreach ($rows as $row) {
244
-				$mount = $this->dbRowToMountInfo($row, [$this, 'getInternalPathForMountInfo']);
245
-				if ($mount !== null) {
246
-					$mounts[$mount->getKey()] = $mount;
247
-				}
248
-			}
249
-			$this->mountsForUsers[$userUID] = $mounts;
250
-		}
251
-		return $this->mountsForUsers[$userUID];
252
-	}
253
-
254
-	public function getInternalPathForMountInfo(CachedMountInfo $info): string {
255
-		$cached = $this->internalPathCache->get($info->getRootId());
256
-		if ($cached !== null) {
257
-			return $cached;
258
-		}
259
-		$builder = $this->connection->getQueryBuilder();
260
-		$query = $builder->select('path')
261
-			->from('filecache')
262
-			->where($builder->expr()->eq('fileid', $builder->createNamedParameter($info->getRootId())));
263
-		return $query->executeQuery()->fetchOne() ?: '';
264
-	}
265
-
266
-	/**
267
-	 * @param int $numericStorageId
268
-	 * @param string|null $user limit the results to a single user
269
-	 * @return CachedMountInfo[]
270
-	 */
271
-	public function getMountsForStorageId($numericStorageId, $user = null) {
272
-		$builder = $this->connection->getQueryBuilder();
273
-		$query = $builder->select('storage_id', 'root_id', 'user_id', 'mount_point', 'mount_id', 'f.path', 'mount_provider_class')
274
-			->from('mounts', 'm')
275
-			->innerJoin('m', 'filecache', 'f', $builder->expr()->eq('m.root_id', 'f.fileid'))
276
-			->where($builder->expr()->eq('storage_id', $builder->createNamedParameter($numericStorageId, IQueryBuilder::PARAM_INT)));
277
-
278
-		if ($user) {
279
-			$query->andWhere($builder->expr()->eq('user_id', $builder->createNamedParameter($user)));
280
-		}
281
-
282
-		$result = $query->executeQuery();
283
-		$rows = $result->fetchAll();
284
-		$result->closeCursor();
285
-
286
-		return array_filter(array_map([$this, 'dbRowToMountInfo'], $rows));
287
-	}
288
-
289
-	/**
290
-	 * @param int $rootFileId
291
-	 * @return CachedMountInfo[]
292
-	 */
293
-	public function getMountsForRootId($rootFileId) {
294
-		$builder = $this->connection->getQueryBuilder();
295
-		$query = $builder->select('storage_id', 'root_id', 'user_id', 'mount_point', 'mount_id', 'f.path', 'mount_provider_class')
296
-			->from('mounts', 'm')
297
-			->innerJoin('m', 'filecache', 'f', $builder->expr()->eq('m.root_id', 'f.fileid'))
298
-			->where($builder->expr()->eq('root_id', $builder->createNamedParameter($rootFileId, IQueryBuilder::PARAM_INT)));
299
-
300
-		$result = $query->executeQuery();
301
-		$rows = $result->fetchAll();
302
-		$result->closeCursor();
303
-
304
-		return array_filter(array_map([$this, 'dbRowToMountInfo'], $rows));
305
-	}
306
-
307
-	/**
308
-	 * @param $fileId
309
-	 * @return array{int, string, int}
310
-	 * @throws \OCP\Files\NotFoundException
311
-	 */
312
-	private function getCacheInfoFromFileId($fileId): array {
313
-		if (!isset($this->cacheInfoCache[$fileId])) {
314
-			$builder = $this->connection->getQueryBuilder();
315
-			$query = $builder->select('storage', 'path', 'mimetype')
316
-				->from('filecache')
317
-				->where($builder->expr()->eq('fileid', $builder->createNamedParameter($fileId, IQueryBuilder::PARAM_INT)));
318
-
319
-			$result = $query->executeQuery();
320
-			$row = $result->fetch();
321
-			$result->closeCursor();
322
-
323
-			if (is_array($row)) {
324
-				$this->cacheInfoCache[$fileId] = [
325
-					(int)$row['storage'],
326
-					(string)$row['path'],
327
-					(int)$row['mimetype']
328
-				];
329
-			} else {
330
-				throw new NotFoundException('File with id "' . $fileId . '" not found');
331
-			}
332
-		}
333
-		return $this->cacheInfoCache[$fileId];
334
-	}
335
-
336
-	/**
337
-	 * @param int $fileId
338
-	 * @param string|null $user optionally restrict the results to a single user
339
-	 * @return ICachedMountFileInfo[]
340
-	 * @since 9.0.0
341
-	 */
342
-	public function getMountsForFileId($fileId, $user = null) {
343
-		try {
344
-			[$storageId, $internalPath] = $this->getCacheInfoFromFileId($fileId);
345
-		} catch (NotFoundException $e) {
346
-			return [];
347
-		}
348
-		$mountsForStorage = $this->getMountsForStorageId($storageId, $user);
349
-
350
-		// filter mounts that are from the same storage but not a parent of the file we care about
351
-		$filteredMounts = array_filter($mountsForStorage, function (ICachedMountInfo $mount) use ($internalPath, $fileId) {
352
-			if ($fileId === $mount->getRootId()) {
353
-				return true;
354
-			}
355
-			$internalMountPath = $mount->getRootInternalPath();
356
-
357
-			return $internalMountPath === '' || str_starts_with($internalPath, $internalMountPath . '/');
358
-		});
359
-
360
-		$filteredMounts = array_values(array_filter($filteredMounts, function (ICachedMountInfo $mount) {
361
-			return $this->userManager->userExists($mount->getUser()->getUID());
362
-		}));
363
-
364
-		return array_map(function (ICachedMountInfo $mount) use ($internalPath) {
365
-			return new CachedMountFileInfo(
366
-				$mount->getUser(),
367
-				$mount->getStorageId(),
368
-				$mount->getRootId(),
369
-				$mount->getMountPoint(),
370
-				$mount->getMountId(),
371
-				$mount->getMountProvider(),
372
-				$mount->getRootInternalPath(),
373
-				$internalPath
374
-			);
375
-		}, $filteredMounts);
376
-	}
377
-
378
-	/**
379
-	 * Remove all cached mounts for a user
380
-	 *
381
-	 * @param IUser $user
382
-	 */
383
-	public function removeUserMounts(IUser $user) {
384
-		$builder = $this->connection->getQueryBuilder();
385
-
386
-		$query = $builder->delete('mounts')
387
-			->where($builder->expr()->eq('user_id', $builder->createNamedParameter($user->getUID())));
388
-		$query->executeStatement();
389
-	}
390
-
391
-	public function removeUserStorageMount($storageId, $userId) {
392
-		$builder = $this->connection->getQueryBuilder();
393
-
394
-		$query = $builder->delete('mounts')
395
-			->where($builder->expr()->eq('user_id', $builder->createNamedParameter($userId)))
396
-			->andWhere($builder->expr()->eq('storage_id', $builder->createNamedParameter($storageId, IQueryBuilder::PARAM_INT)));
397
-		$query->executeStatement();
398
-	}
399
-
400
-	public function remoteStorageMounts($storageId) {
401
-		$builder = $this->connection->getQueryBuilder();
402
-
403
-		$query = $builder->delete('mounts')
404
-			->where($builder->expr()->eq('storage_id', $builder->createNamedParameter($storageId, IQueryBuilder::PARAM_INT)));
405
-		$query->executeStatement();
406
-	}
407
-
408
-	/**
409
-	 * @param array $users
410
-	 * @return array
411
-	 */
412
-	public function getUsedSpaceForUsers(array $users) {
413
-		$builder = $this->connection->getQueryBuilder();
414
-
415
-		$slash = $builder->createNamedParameter('/');
416
-
417
-		$mountPoint = $builder->func()->concat(
418
-			$builder->func()->concat($slash, 'user_id'),
419
-			$slash
420
-		);
421
-
422
-		$userIds = array_map(function (IUser $user) {
423
-			return $user->getUID();
424
-		}, $users);
425
-
426
-		$query = $builder->select('m.user_id', 'f.size')
427
-			->from('mounts', 'm')
428
-			->innerJoin('m', 'filecache', 'f',
429
-				$builder->expr()->andX(
430
-					$builder->expr()->eq('m.storage_id', 'f.storage'),
431
-					$builder->expr()->eq('f.path_hash', $builder->createNamedParameter(md5('files')))
432
-				))
433
-			->where($builder->expr()->eq('m.mount_point', $mountPoint))
434
-			->andWhere($builder->expr()->in('m.user_id', $builder->createNamedParameter($userIds, IQueryBuilder::PARAM_STR_ARRAY)));
435
-
436
-		$result = $query->executeQuery();
437
-
438
-		$results = [];
439
-		while ($row = $result->fetch()) {
440
-			$results[$row['user_id']] = $row['size'];
441
-		}
442
-		$result->closeCursor();
443
-		return $results;
444
-	}
445
-
446
-	public function clear(): void {
447
-		$this->cacheInfoCache = new CappedMemoryCache();
448
-		$this->mountsForUsers = new CappedMemoryCache();
449
-	}
450
-
451
-	public function getMountForPath(IUser $user, string $path): ICachedMountInfo {
452
-		$mounts = $this->getMountsForUser($user);
453
-		$mountPoints = array_map(function (ICachedMountInfo $mount) {
454
-			return $mount->getMountPoint();
455
-		}, $mounts);
456
-		$mounts = array_combine($mountPoints, $mounts);
457
-
458
-		$current = rtrim($path, '/');
459
-		// walk up the directory tree until we find a path that has a mountpoint set
460
-		// the loop will return if a mountpoint is found or break if none are found
461
-		while (true) {
462
-			$mountPoint = $current . '/';
463
-			if (isset($mounts[$mountPoint])) {
464
-				return $mounts[$mountPoint];
465
-			} elseif ($current === '') {
466
-				break;
467
-			}
468
-
469
-			$current = dirname($current);
470
-			if ($current === '.' || $current === '/') {
471
-				$current = '';
472
-			}
473
-		}
474
-
475
-		throw new NotFoundException('No cached mount for path ' . $path);
476
-	}
477
-
478
-	public function getMountsInPath(IUser $user, string $path): array {
479
-		$path = rtrim($path, '/') . '/';
480
-		$mounts = $this->getMountsForUser($user);
481
-		return array_filter($mounts, function (ICachedMountInfo $mount) use ($path) {
482
-			return $mount->getMountPoint() !== $path && str_starts_with($mount->getMountPoint(), $path);
483
-		});
484
-	}
28
+    /**
29
+     * Cached mount info.
30
+     * @var CappedMemoryCache<ICachedMountInfo[]>
31
+     **/
32
+    private CappedMemoryCache $mountsForUsers;
33
+    /**
34
+     * fileid => internal path mapping for cached mount info.
35
+     * @var CappedMemoryCache<string>
36
+     **/
37
+    private CappedMemoryCache $internalPathCache;
38
+    /** @var CappedMemoryCache<array> */
39
+    private CappedMemoryCache $cacheInfoCache;
40
+
41
+    /**
42
+     * UserMountCache constructor.
43
+     */
44
+    public function __construct(
45
+        private IDBConnection $connection,
46
+        private IUserManager $userManager,
47
+        private LoggerInterface $logger,
48
+        private IEventLogger $eventLogger,
49
+    ) {
50
+        $this->cacheInfoCache = new CappedMemoryCache();
51
+        $this->internalPathCache = new CappedMemoryCache();
52
+        $this->mountsForUsers = new CappedMemoryCache();
53
+    }
54
+
55
+    public function registerMounts(IUser $user, array $mounts, ?array $mountProviderClasses = null) {
56
+        $this->eventLogger->start('fs:setup:user:register', 'Registering mounts for user');
57
+        /** @var array<string, ICachedMountInfo> $newMounts */
58
+        $newMounts = [];
59
+        foreach ($mounts as $mount) {
60
+            // filter out any storages which aren't scanned yet since we aren't interested in files from those storages (yet)
61
+            if ($mount->getStorageRootId() !== -1) {
62
+                $mountInfo = new LazyStorageMountInfo($user, $mount);
63
+                $newMounts[$mountInfo->getKey()] = $mountInfo;
64
+            }
65
+        }
66
+
67
+        $cachedMounts = $this->getMountsForUser($user);
68
+        if (is_array($mountProviderClasses)) {
69
+            $cachedMounts = array_filter($cachedMounts, function (ICachedMountInfo $mountInfo) use ($mountProviderClasses, $newMounts) {
70
+                // for existing mounts that didn't have a mount provider set
71
+                // we still want the ones that map to new mounts
72
+                if ($mountInfo->getMountProvider() === '' && isset($newMounts[$mountInfo->getKey()])) {
73
+                    return true;
74
+                }
75
+                return in_array($mountInfo->getMountProvider(), $mountProviderClasses);
76
+            });
77
+        }
78
+
79
+        $addedMounts = [];
80
+        $removedMounts = [];
81
+
82
+        foreach ($newMounts as $mountKey => $newMount) {
83
+            if (!isset($cachedMounts[$mountKey])) {
84
+                $addedMounts[] = $newMount;
85
+            }
86
+        }
87
+
88
+        foreach ($cachedMounts as $mountKey => $cachedMount) {
89
+            if (!isset($newMounts[$mountKey])) {
90
+                $removedMounts[] = $cachedMount;
91
+            }
92
+        }
93
+
94
+        $changedMounts = $this->findChangedMounts($newMounts, $cachedMounts);
95
+
96
+        if ($addedMounts || $removedMounts || $changedMounts) {
97
+            $this->connection->beginTransaction();
98
+            $userUID = $user->getUID();
99
+            try {
100
+                foreach ($addedMounts as $mount) {
101
+                    $this->logger->debug("Adding mount '{$mount->getKey()}' for user '$userUID'", ['app' => 'files', 'mount_provider' => $mount->getMountProvider()]);
102
+                    $this->addToCache($mount);
103
+                    /** @psalm-suppress InvalidArgument */
104
+                    $this->mountsForUsers[$userUID][$mount->getKey()] = $mount;
105
+                }
106
+                foreach ($removedMounts as $mount) {
107
+                    $this->logger->debug("Removing mount '{$mount->getKey()}' for user '$userUID'", ['app' => 'files', 'mount_provider' => $mount->getMountProvider()]);
108
+                    $this->removeFromCache($mount);
109
+                    unset($this->mountsForUsers[$userUID][$mount->getKey()]);
110
+                }
111
+                foreach ($changedMounts as $mount) {
112
+                    $this->logger->debug("Updating mount '{$mount->getKey()}' for user '$userUID'", ['app' => 'files', 'mount_provider' => $mount->getMountProvider()]);
113
+                    $this->updateCachedMount($mount);
114
+                    /** @psalm-suppress InvalidArgument */
115
+                    $this->mountsForUsers[$userUID][$mount->getKey()] = $mount;
116
+                }
117
+                $this->connection->commit();
118
+            } catch (\Throwable $e) {
119
+                $this->connection->rollBack();
120
+                throw $e;
121
+            }
122
+        }
123
+        $this->eventLogger->end('fs:setup:user:register');
124
+    }
125
+
126
+    /**
127
+     * @param array<string, ICachedMountInfo> $newMounts
128
+     * @param array<string, ICachedMountInfo> $cachedMounts
129
+     * @return ICachedMountInfo[]
130
+     */
131
+    private function findChangedMounts(array $newMounts, array $cachedMounts) {
132
+        $changed = [];
133
+        foreach ($cachedMounts as $key => $cachedMount) {
134
+            if (isset($newMounts[$key])) {
135
+                $newMount = $newMounts[$key];
136
+                if (
137
+                    $newMount->getStorageId() !== $cachedMount->getStorageId() ||
138
+                    $newMount->getMountId() !== $cachedMount->getMountId() ||
139
+                    $newMount->getMountProvider() !== $cachedMount->getMountProvider()
140
+                ) {
141
+                    $changed[] = $newMount;
142
+                }
143
+            }
144
+        }
145
+        return $changed;
146
+    }
147
+
148
+    private function addToCache(ICachedMountInfo $mount) {
149
+        if ($mount->getStorageId() !== -1) {
150
+            $this->connection->insertIfNotExist('*PREFIX*mounts', [
151
+                'storage_id' => $mount->getStorageId(),
152
+                'root_id' => $mount->getRootId(),
153
+                'user_id' => $mount->getUser()->getUID(),
154
+                'mount_point' => $mount->getMountPoint(),
155
+                'mount_id' => $mount->getMountId(),
156
+                'mount_provider_class' => $mount->getMountProvider(),
157
+            ], ['root_id', 'user_id', 'mount_point']);
158
+        } else {
159
+            // in some cases this is legitimate, like orphaned shares
160
+            $this->logger->debug('Could not get storage info for mount at ' . $mount->getMountPoint());
161
+        }
162
+    }
163
+
164
+    private function updateCachedMount(ICachedMountInfo $mount) {
165
+        $builder = $this->connection->getQueryBuilder();
166
+
167
+        $query = $builder->update('mounts')
168
+            ->set('storage_id', $builder->createNamedParameter($mount->getStorageId()))
169
+            ->set('mount_point', $builder->createNamedParameter($mount->getMountPoint()))
170
+            ->set('mount_id', $builder->createNamedParameter($mount->getMountId(), IQueryBuilder::PARAM_INT))
171
+            ->set('mount_provider_class', $builder->createNamedParameter($mount->getMountProvider()))
172
+            ->where($builder->expr()->eq('user_id', $builder->createNamedParameter($mount->getUser()->getUID())))
173
+            ->andWhere($builder->expr()->eq('root_id', $builder->createNamedParameter($mount->getRootId(), IQueryBuilder::PARAM_INT)));
174
+
175
+        $query->executeStatement();
176
+    }
177
+
178
+    private function removeFromCache(ICachedMountInfo $mount) {
179
+        $builder = $this->connection->getQueryBuilder();
180
+
181
+        $query = $builder->delete('mounts')
182
+            ->where($builder->expr()->eq('user_id', $builder->createNamedParameter($mount->getUser()->getUID())))
183
+            ->andWhere($builder->expr()->eq('root_id', $builder->createNamedParameter($mount->getRootId(), IQueryBuilder::PARAM_INT)))
184
+            ->andWhere($builder->expr()->eq('mount_point', $builder->createNamedParameter($mount->getMountPoint())));
185
+        $query->executeStatement();
186
+    }
187
+
188
+    /**
189
+     * @param array $row
190
+     * @param (callable(CachedMountInfo): string)|null $pathCallback
191
+     * @return CachedMountInfo
192
+     */
193
+    private function dbRowToMountInfo(array $row, ?callable $pathCallback = null): ICachedMountInfo {
194
+        $user = new LazyUser($row['user_id'], $this->userManager);
195
+        $mount_id = $row['mount_id'];
196
+        if (!is_null($mount_id)) {
197
+            $mount_id = (int)$mount_id;
198
+        }
199
+        if ($pathCallback) {
200
+            return new LazyPathCachedMountInfo(
201
+                $user,
202
+                (int)$row['storage_id'],
203
+                (int)$row['root_id'],
204
+                $row['mount_point'],
205
+                $row['mount_provider_class'] ?? '',
206
+                $mount_id,
207
+                $pathCallback,
208
+            );
209
+        } else {
210
+            return new CachedMountInfo(
211
+                $user,
212
+                (int)$row['storage_id'],
213
+                (int)$row['root_id'],
214
+                $row['mount_point'],
215
+                $row['mount_provider_class'] ?? '',
216
+                $mount_id,
217
+                $row['path'] ?? '',
218
+            );
219
+        }
220
+    }
221
+
222
+    /**
223
+     * @param IUser $user
224
+     * @return ICachedMountInfo[]
225
+     */
226
+    public function getMountsForUser(IUser $user) {
227
+        $userUID = $user->getUID();
228
+        if (!$this->userManager->userExists($userUID)) {
229
+            return [];
230
+        }
231
+        if (!isset($this->mountsForUsers[$userUID])) {
232
+            $builder = $this->connection->getQueryBuilder();
233
+            $query = $builder->select('storage_id', 'root_id', 'user_id', 'mount_point', 'mount_id', 'mount_provider_class')
234
+                ->from('mounts', 'm')
235
+                ->where($builder->expr()->eq('user_id', $builder->createNamedParameter($userUID)));
236
+
237
+            $result = $query->executeQuery();
238
+            $rows = $result->fetchAll();
239
+            $result->closeCursor();
240
+
241
+            /** @var array<string, ICachedMountInfo> $mounts */
242
+            $mounts = [];
243
+            foreach ($rows as $row) {
244
+                $mount = $this->dbRowToMountInfo($row, [$this, 'getInternalPathForMountInfo']);
245
+                if ($mount !== null) {
246
+                    $mounts[$mount->getKey()] = $mount;
247
+                }
248
+            }
249
+            $this->mountsForUsers[$userUID] = $mounts;
250
+        }
251
+        return $this->mountsForUsers[$userUID];
252
+    }
253
+
254
+    public function getInternalPathForMountInfo(CachedMountInfo $info): string {
255
+        $cached = $this->internalPathCache->get($info->getRootId());
256
+        if ($cached !== null) {
257
+            return $cached;
258
+        }
259
+        $builder = $this->connection->getQueryBuilder();
260
+        $query = $builder->select('path')
261
+            ->from('filecache')
262
+            ->where($builder->expr()->eq('fileid', $builder->createNamedParameter($info->getRootId())));
263
+        return $query->executeQuery()->fetchOne() ?: '';
264
+    }
265
+
266
+    /**
267
+     * @param int $numericStorageId
268
+     * @param string|null $user limit the results to a single user
269
+     * @return CachedMountInfo[]
270
+     */
271
+    public function getMountsForStorageId($numericStorageId, $user = null) {
272
+        $builder = $this->connection->getQueryBuilder();
273
+        $query = $builder->select('storage_id', 'root_id', 'user_id', 'mount_point', 'mount_id', 'f.path', 'mount_provider_class')
274
+            ->from('mounts', 'm')
275
+            ->innerJoin('m', 'filecache', 'f', $builder->expr()->eq('m.root_id', 'f.fileid'))
276
+            ->where($builder->expr()->eq('storage_id', $builder->createNamedParameter($numericStorageId, IQueryBuilder::PARAM_INT)));
277
+
278
+        if ($user) {
279
+            $query->andWhere($builder->expr()->eq('user_id', $builder->createNamedParameter($user)));
280
+        }
281
+
282
+        $result = $query->executeQuery();
283
+        $rows = $result->fetchAll();
284
+        $result->closeCursor();
285
+
286
+        return array_filter(array_map([$this, 'dbRowToMountInfo'], $rows));
287
+    }
288
+
289
+    /**
290
+     * @param int $rootFileId
291
+     * @return CachedMountInfo[]
292
+     */
293
+    public function getMountsForRootId($rootFileId) {
294
+        $builder = $this->connection->getQueryBuilder();
295
+        $query = $builder->select('storage_id', 'root_id', 'user_id', 'mount_point', 'mount_id', 'f.path', 'mount_provider_class')
296
+            ->from('mounts', 'm')
297
+            ->innerJoin('m', 'filecache', 'f', $builder->expr()->eq('m.root_id', 'f.fileid'))
298
+            ->where($builder->expr()->eq('root_id', $builder->createNamedParameter($rootFileId, IQueryBuilder::PARAM_INT)));
299
+
300
+        $result = $query->executeQuery();
301
+        $rows = $result->fetchAll();
302
+        $result->closeCursor();
303
+
304
+        return array_filter(array_map([$this, 'dbRowToMountInfo'], $rows));
305
+    }
306
+
307
+    /**
308
+     * @param $fileId
309
+     * @return array{int, string, int}
310
+     * @throws \OCP\Files\NotFoundException
311
+     */
312
+    private function getCacheInfoFromFileId($fileId): array {
313
+        if (!isset($this->cacheInfoCache[$fileId])) {
314
+            $builder = $this->connection->getQueryBuilder();
315
+            $query = $builder->select('storage', 'path', 'mimetype')
316
+                ->from('filecache')
317
+                ->where($builder->expr()->eq('fileid', $builder->createNamedParameter($fileId, IQueryBuilder::PARAM_INT)));
318
+
319
+            $result = $query->executeQuery();
320
+            $row = $result->fetch();
321
+            $result->closeCursor();
322
+
323
+            if (is_array($row)) {
324
+                $this->cacheInfoCache[$fileId] = [
325
+                    (int)$row['storage'],
326
+                    (string)$row['path'],
327
+                    (int)$row['mimetype']
328
+                ];
329
+            } else {
330
+                throw new NotFoundException('File with id "' . $fileId . '" not found');
331
+            }
332
+        }
333
+        return $this->cacheInfoCache[$fileId];
334
+    }
335
+
336
+    /**
337
+     * @param int $fileId
338
+     * @param string|null $user optionally restrict the results to a single user
339
+     * @return ICachedMountFileInfo[]
340
+     * @since 9.0.0
341
+     */
342
+    public function getMountsForFileId($fileId, $user = null) {
343
+        try {
344
+            [$storageId, $internalPath] = $this->getCacheInfoFromFileId($fileId);
345
+        } catch (NotFoundException $e) {
346
+            return [];
347
+        }
348
+        $mountsForStorage = $this->getMountsForStorageId($storageId, $user);
349
+
350
+        // filter mounts that are from the same storage but not a parent of the file we care about
351
+        $filteredMounts = array_filter($mountsForStorage, function (ICachedMountInfo $mount) use ($internalPath, $fileId) {
352
+            if ($fileId === $mount->getRootId()) {
353
+                return true;
354
+            }
355
+            $internalMountPath = $mount->getRootInternalPath();
356
+
357
+            return $internalMountPath === '' || str_starts_with($internalPath, $internalMountPath . '/');
358
+        });
359
+
360
+        $filteredMounts = array_values(array_filter($filteredMounts, function (ICachedMountInfo $mount) {
361
+            return $this->userManager->userExists($mount->getUser()->getUID());
362
+        }));
363
+
364
+        return array_map(function (ICachedMountInfo $mount) use ($internalPath) {
365
+            return new CachedMountFileInfo(
366
+                $mount->getUser(),
367
+                $mount->getStorageId(),
368
+                $mount->getRootId(),
369
+                $mount->getMountPoint(),
370
+                $mount->getMountId(),
371
+                $mount->getMountProvider(),
372
+                $mount->getRootInternalPath(),
373
+                $internalPath
374
+            );
375
+        }, $filteredMounts);
376
+    }
377
+
378
+    /**
379
+     * Remove all cached mounts for a user
380
+     *
381
+     * @param IUser $user
382
+     */
383
+    public function removeUserMounts(IUser $user) {
384
+        $builder = $this->connection->getQueryBuilder();
385
+
386
+        $query = $builder->delete('mounts')
387
+            ->where($builder->expr()->eq('user_id', $builder->createNamedParameter($user->getUID())));
388
+        $query->executeStatement();
389
+    }
390
+
391
+    public function removeUserStorageMount($storageId, $userId) {
392
+        $builder = $this->connection->getQueryBuilder();
393
+
394
+        $query = $builder->delete('mounts')
395
+            ->where($builder->expr()->eq('user_id', $builder->createNamedParameter($userId)))
396
+            ->andWhere($builder->expr()->eq('storage_id', $builder->createNamedParameter($storageId, IQueryBuilder::PARAM_INT)));
397
+        $query->executeStatement();
398
+    }
399
+
400
+    public function remoteStorageMounts($storageId) {
401
+        $builder = $this->connection->getQueryBuilder();
402
+
403
+        $query = $builder->delete('mounts')
404
+            ->where($builder->expr()->eq('storage_id', $builder->createNamedParameter($storageId, IQueryBuilder::PARAM_INT)));
405
+        $query->executeStatement();
406
+    }
407
+
408
+    /**
409
+     * @param array $users
410
+     * @return array
411
+     */
412
+    public function getUsedSpaceForUsers(array $users) {
413
+        $builder = $this->connection->getQueryBuilder();
414
+
415
+        $slash = $builder->createNamedParameter('/');
416
+
417
+        $mountPoint = $builder->func()->concat(
418
+            $builder->func()->concat($slash, 'user_id'),
419
+            $slash
420
+        );
421
+
422
+        $userIds = array_map(function (IUser $user) {
423
+            return $user->getUID();
424
+        }, $users);
425
+
426
+        $query = $builder->select('m.user_id', 'f.size')
427
+            ->from('mounts', 'm')
428
+            ->innerJoin('m', 'filecache', 'f',
429
+                $builder->expr()->andX(
430
+                    $builder->expr()->eq('m.storage_id', 'f.storage'),
431
+                    $builder->expr()->eq('f.path_hash', $builder->createNamedParameter(md5('files')))
432
+                ))
433
+            ->where($builder->expr()->eq('m.mount_point', $mountPoint))
434
+            ->andWhere($builder->expr()->in('m.user_id', $builder->createNamedParameter($userIds, IQueryBuilder::PARAM_STR_ARRAY)));
435
+
436
+        $result = $query->executeQuery();
437
+
438
+        $results = [];
439
+        while ($row = $result->fetch()) {
440
+            $results[$row['user_id']] = $row['size'];
441
+        }
442
+        $result->closeCursor();
443
+        return $results;
444
+    }
445
+
446
+    public function clear(): void {
447
+        $this->cacheInfoCache = new CappedMemoryCache();
448
+        $this->mountsForUsers = new CappedMemoryCache();
449
+    }
450
+
451
+    public function getMountForPath(IUser $user, string $path): ICachedMountInfo {
452
+        $mounts = $this->getMountsForUser($user);
453
+        $mountPoints = array_map(function (ICachedMountInfo $mount) {
454
+            return $mount->getMountPoint();
455
+        }, $mounts);
456
+        $mounts = array_combine($mountPoints, $mounts);
457
+
458
+        $current = rtrim($path, '/');
459
+        // walk up the directory tree until we find a path that has a mountpoint set
460
+        // the loop will return if a mountpoint is found or break if none are found
461
+        while (true) {
462
+            $mountPoint = $current . '/';
463
+            if (isset($mounts[$mountPoint])) {
464
+                return $mounts[$mountPoint];
465
+            } elseif ($current === '') {
466
+                break;
467
+            }
468
+
469
+            $current = dirname($current);
470
+            if ($current === '.' || $current === '/') {
471
+                $current = '';
472
+            }
473
+        }
474
+
475
+        throw new NotFoundException('No cached mount for path ' . $path);
476
+    }
477
+
478
+    public function getMountsInPath(IUser $user, string $path): array {
479
+        $path = rtrim($path, '/') . '/';
480
+        $mounts = $this->getMountsForUser($user);
481
+        return array_filter($mounts, function (ICachedMountInfo $mount) use ($path) {
482
+            return $mount->getMountPoint() !== $path && str_starts_with($mount->getMountPoint(), $path);
483
+        });
484
+    }
485 485
 }
Please login to merge, or discard this patch.