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