Passed
Push — master ( f3baab...36c944 )
by Blizzz
15:44 queued 12s
created

UserMountCache::getCacheInfoFromFileId()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 22
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 16
nc 3
nop 1
dl 0
loc 22
rs 9.7333
c 0
b 0
f 0
1
<?php
2
/**
3
 * @copyright Copyright (c) 2016, ownCloud, Inc.
4
 *
5
 * @author Dariusz Olszewski <[email protected]>
6
 * @author Joas Schilling <[email protected]>
7
 * @author Julius Härtl <[email protected]>
8
 * @author Lukas Reschke <[email protected]>
9
 * @author Morris Jobke <[email protected]>
10
 * @author Robin Appelman <[email protected]>
11
 * @author Roeland Jago Douma <[email protected]>
12
 * @author Vincent Petry <[email protected]>
13
 *
14
 * @license AGPL-3.0
15
 *
16
 * This code is free software: you can redistribute it and/or modify
17
 * it under the terms of the GNU Affero General Public License, version 3,
18
 * as published by the Free Software Foundation.
19
 *
20
 * This program is distributed in the hope that it will be useful,
21
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
22
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23
 * GNU Affero General Public License for more details.
24
 *
25
 * You should have received a copy of the GNU Affero General Public License, version 3,
26
 * along with this program. If not, see <http://www.gnu.org/licenses/>
27
 *
28
 */
29
30
namespace OC\Files\Config;
31
32
use OC\Cache\CappedMemoryCache;
33
use OCA\Files_Sharing\SharedMount;
34
use OCP\DB\QueryBuilder\IQueryBuilder;
35
use OCP\Files\Config\ICachedMountFileInfo;
36
use OCP\Files\Config\ICachedMountInfo;
37
use OCP\Files\Config\IUserMountCache;
38
use OCP\Files\Mount\IMountPoint;
39
use OCP\Files\NotFoundException;
40
use OCP\ICache;
41
use OCP\IDBConnection;
42
use OCP\ILogger;
43
use OCP\IUser;
44
use OCP\IUserManager;
45
46
/**
47
 * Cache mounts points per user in the cache so we can easilly look them up
48
 */
49
class UserMountCache implements IUserMountCache {
50
	/**
51
	 * @var IDBConnection
52
	 */
53
	private $connection;
54
55
	/**
56
	 * @var IUserManager
57
	 */
58
	private $userManager;
59
60
	/**
61
	 * Cached mount info.
62
	 * Map of $userId to ICachedMountInfo.
63
	 *
64
	 * @var ICache
65
	 **/
66
	private $mountsForUsers;
67
68
	/**
69
	 * @var ILogger
70
	 */
71
	private $logger;
72
73
	/**
74
	 * @var ICache
75
	 */
76
	private $cacheInfoCache;
77
78
	/**
79
	 * UserMountCache constructor.
80
	 *
81
	 * @param IDBConnection $connection
82
	 * @param IUserManager $userManager
83
	 * @param ILogger $logger
84
	 */
85
	public function __construct(IDBConnection $connection, IUserManager $userManager, ILogger $logger) {
86
		$this->connection = $connection;
87
		$this->userManager = $userManager;
88
		$this->logger = $logger;
89
		$this->cacheInfoCache = new CappedMemoryCache();
90
		$this->mountsForUsers = new CappedMemoryCache();
91
	}
92
93
	public function registerMounts(IUser $user, array $mounts) {
94
		// filter out non-proper storages coming from unit tests
95
		$mounts = array_filter($mounts, function (IMountPoint $mount) {
96
			return $mount instanceof SharedMount || $mount->getStorage() && $mount->getStorage()->getCache();
97
		});
98
		/** @var ICachedMountInfo[] $newMounts */
99
		$newMounts = array_map(function (IMountPoint $mount) use ($user) {
100
			// filter out any storages which aren't scanned yet since we aren't interested in files from those storages (yet)
101
			if ($mount->getStorageRootId() === -1) {
102
				return null;
103
			} else {
104
				return new LazyStorageMountInfo($user, $mount);
105
			}
106
		}, $mounts);
107
		$newMounts = array_values(array_filter($newMounts));
108
		$newMountRootIds = array_map(function (ICachedMountInfo $mount) {
109
			return $mount->getRootId();
110
		}, $newMounts);
111
		$newMounts = array_combine($newMountRootIds, $newMounts);
112
113
		$cachedMounts = $this->getMountsForUser($user);
114
		$cachedMountRootIds = array_map(function (ICachedMountInfo $mount) {
115
			return $mount->getRootId();
116
		}, $cachedMounts);
117
		$cachedMounts = array_combine($cachedMountRootIds, $cachedMounts);
118
119
		$addedMounts = [];
120
		$removedMounts = [];
121
122
		foreach ($newMounts as $rootId => $newMount) {
123
			if (!isset($cachedMounts[$rootId])) {
124
				$addedMounts[] = $newMount;
125
			}
126
		}
127
128
		foreach ($cachedMounts as $rootId => $cachedMount) {
129
			if (!isset($newMounts[$rootId])) {
130
				$removedMounts[] = $cachedMount;
131
			}
132
		}
133
134
		$changedMounts = $this->findChangedMounts($newMounts, $cachedMounts);
135
136
		foreach ($addedMounts as $mount) {
137
			$this->addToCache($mount);
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
	}
149
150
	/**
151
	 * @param ICachedMountInfo[] $newMounts
152
	 * @param ICachedMountInfo[] $cachedMounts
153
	 * @return ICachedMountInfo[]
154
	 */
155
	private function findChangedMounts(array $newMounts, array $cachedMounts) {
156
		$new = [];
157
		foreach ($newMounts as $mount) {
158
			$new[$mount->getRootId()] = $mount;
159
		}
160
		$changed = [];
161
		foreach ($cachedMounts as $cachedMount) {
162
			$rootId = $cachedMount->getRootId();
163
			if (isset($new[$rootId])) {
164
				$newMount = $new[$rootId];
165
				if (
166
					$newMount->getMountPoint() !== $cachedMount->getMountPoint() ||
167
					$newMount->getStorageId() !== $cachedMount->getStorageId() ||
168
					$newMount->getMountId() !== $cachedMount->getMountId()
169
				) {
170
					$changed[] = $newMount;
171
				}
172
			}
173
		}
174
		return $changed;
175
	}
176
177
	private function addToCache(ICachedMountInfo $mount) {
178
		if ($mount->getStorageId() !== -1) {
179
			$this->connection->insertIfNotExist('*PREFIX*mounts', [
180
				'storage_id' => $mount->getStorageId(),
181
				'root_id' => $mount->getRootId(),
182
				'user_id' => $mount->getUser()->getUID(),
183
				'mount_point' => $mount->getMountPoint(),
184
				'mount_id' => $mount->getMountId()
185
			], ['root_id', 'user_id']);
186
		} else {
187
			// in some cases this is legitimate, like orphaned shares
188
			$this->logger->debug('Could not get storage info for mount at ' . $mount->getMountPoint());
189
		}
190
	}
191
192
	private function updateCachedMount(ICachedMountInfo $mount) {
193
		$builder = $this->connection->getQueryBuilder();
194
195
		$query = $builder->update('mounts')
196
			->set('storage_id', $builder->createNamedParameter($mount->getStorageId()))
197
			->set('mount_point', $builder->createNamedParameter($mount->getMountPoint()))
198
			->set('mount_id', $builder->createNamedParameter($mount->getMountId(), IQueryBuilder::PARAM_INT))
199
			->where($builder->expr()->eq('user_id', $builder->createNamedParameter($mount->getUser()->getUID())))
200
			->andWhere($builder->expr()->eq('root_id', $builder->createNamedParameter($mount->getRootId(), IQueryBuilder::PARAM_INT)));
201
202
		$query->execute();
203
	}
204
205
	private function removeFromCache(ICachedMountInfo $mount) {
206
		$builder = $this->connection->getQueryBuilder();
207
208
		$query = $builder->delete('mounts')
209
			->where($builder->expr()->eq('user_id', $builder->createNamedParameter($mount->getUser()->getUID())))
210
			->andWhere($builder->expr()->eq('root_id', $builder->createNamedParameter($mount->getRootId(), IQueryBuilder::PARAM_INT)));
211
		$query->execute();
212
	}
213
214
	private function dbRowToMountInfo(array $row) {
215
		$user = $this->userManager->get($row['user_id']);
216
		if (is_null($user)) {
217
			return null;
218
		}
219
		$mount_id = $row['mount_id'];
220
		if (!is_null($mount_id)) {
221
			$mount_id = (int)$mount_id;
222
		}
223
		return new CachedMountInfo($user, (int)$row['storage_id'], (int)$row['root_id'], $row['mount_point'], $mount_id, isset($row['path']) ? $row['path'] : '');
224
	}
225
226
	/**
227
	 * @param IUser $user
228
	 * @return ICachedMountInfo[]
229
	 */
230
	public function getMountsForUser(IUser $user) {
231
		if (!isset($this->mountsForUsers[$user->getUID()])) {
232
			$builder = $this->connection->getQueryBuilder();
233
			$query = $builder->select('storage_id', 'root_id', 'user_id', 'mount_point', 'mount_id', 'f.path')
234
				->from('mounts', 'm')
235
				->innerJoin('m', 'filecache', 'f', $builder->expr()->eq('m.root_id', 'f.fileid'))
236
				->where($builder->expr()->eq('user_id', $builder->createPositionalParameter($user->getUID())));
237
238
			$result = $query->execute();
239
			$rows = $result->fetchAll();
240
			$result->closeCursor();
241
242
			$this->mountsForUsers[$user->getUID()] = array_filter(array_map([$this, 'dbRowToMountInfo'], $rows));
243
		}
244
		return $this->mountsForUsers[$user->getUID()];
245
	}
246
247
	/**
248
	 * @param int $numericStorageId
249
	 * @param string|null $user limit the results to a single user
250
	 * @return CachedMountInfo[]
251
	 */
252
	public function getMountsForStorageId($numericStorageId, $user = null) {
253
		$builder = $this->connection->getQueryBuilder();
254
		$query = $builder->select('storage_id', 'root_id', 'user_id', 'mount_point', 'mount_id', 'f.path')
255
			->from('mounts', 'm')
256
			->innerJoin('m', 'filecache', 'f', $builder->expr()->eq('m.root_id', 'f.fileid'))
257
			->where($builder->expr()->eq('storage_id', $builder->createPositionalParameter($numericStorageId, IQueryBuilder::PARAM_INT)));
258
259
		if ($user) {
260
			$query->andWhere($builder->expr()->eq('user_id', $builder->createPositionalParameter($user)));
261
		}
262
263
		$result = $query->execute();
264
		$rows = $result->fetchAll();
265
		$result->closeCursor();
266
267
		return array_filter(array_map([$this, 'dbRowToMountInfo'], $rows));
268
	}
269
270
	/**
271
	 * @param int $rootFileId
272
	 * @return CachedMountInfo[]
273
	 */
274
	public function getMountsForRootId($rootFileId) {
275
		$builder = $this->connection->getQueryBuilder();
276
		$query = $builder->select('storage_id', 'root_id', 'user_id', 'mount_point', 'mount_id', 'f.path')
277
			->from('mounts', 'm')
278
			->innerJoin('m', 'filecache', 'f', $builder->expr()->eq('m.root_id', 'f.fileid'))
279
			->where($builder->expr()->eq('root_id', $builder->createPositionalParameter($rootFileId, IQueryBuilder::PARAM_INT)));
280
281
		$result = $query->execute();
282
		$rows = $result->fetchAll();
283
		$result->closeCursor();
284
285
		return array_filter(array_map([$this, 'dbRowToMountInfo'], $rows));
286
	}
287
288
	/**
289
	 * @param $fileId
290
	 * @return array
291
	 * @throws \OCP\Files\NotFoundException
292
	 */
293
	private function getCacheInfoFromFileId($fileId) {
294
		if (!isset($this->cacheInfoCache[$fileId])) {
295
			$builder = $this->connection->getQueryBuilder();
296
			$query = $builder->select('storage', 'path', 'mimetype')
297
				->from('filecache')
298
				->where($builder->expr()->eq('fileid', $builder->createNamedParameter($fileId, IQueryBuilder::PARAM_INT)));
299
300
			$result = $query->execute();
301
			$row = $result->fetch();
302
			$result->closeCursor();
303
304
			if (is_array($row)) {
305
				$this->cacheInfoCache[$fileId] = [
306
					(int)$row['storage'],
307
					$row['path'],
308
					(int)$row['mimetype']
309
				];
310
			} else {
311
				throw new NotFoundException('File with id "' . $fileId . '" not found');
312
			}
313
		}
314
		return $this->cacheInfoCache[$fileId];
315
	}
316
317
	/**
318
	 * @param int $fileId
319
	 * @param string|null $user optionally restrict the results to a single user
320
	 * @return ICachedMountFileInfo[]
321
	 * @since 9.0.0
322
	 */
323
	public function getMountsForFileId($fileId, $user = null) {
324
		try {
325
			[$storageId, $internalPath] = $this->getCacheInfoFromFileId($fileId);
326
		} catch (NotFoundException $e) {
327
			return [];
328
		}
329
		$builder = $this->connection->getQueryBuilder();
330
		$query = $builder->select('storage_id', 'root_id', 'user_id', 'mount_point', 'mount_id', 'f.path')
331
			->from('mounts', 'm')
332
			->innerJoin('m', 'filecache', 'f', $builder->expr()->eq('m.root_id', 'f.fileid'))
333
			->where($builder->expr()->eq('storage_id', $builder->createPositionalParameter($storageId, IQueryBuilder::PARAM_INT)));
334
335
		if ($user) {
336
			$query->andWhere($builder->expr()->eq('user_id', $builder->createPositionalParameter($user)));
337
		}
338
339
		$result = $query->execute();
340
		$rows = $result->fetchAll();
341
		$result->closeCursor();
342
		// filter mounts that are from the same storage but a different directory
343
		$filteredMounts = array_filter($rows, function (array $row) use ($internalPath, $fileId) {
344
			if ($fileId === (int)$row['root_id']) {
345
				return true;
346
			}
347
			$internalMountPath = isset($row['path']) ? $row['path'] : '';
348
349
			return $internalMountPath === '' || substr($internalPath, 0, strlen($internalMountPath) + 1) === $internalMountPath . '/';
350
		});
351
352
		$filteredMounts = array_filter(array_map([$this, 'dbRowToMountInfo'], $filteredMounts));
353
		return array_map(function (ICachedMountInfo $mount) use ($internalPath) {
354
			return new CachedMountFileInfo(
355
				$mount->getUser(),
356
				$mount->getStorageId(),
357
				$mount->getRootId(),
358
				$mount->getMountPoint(),
359
				$mount->getMountId(),
360
				$mount->getRootInternalPath(),
361
				$internalPath
362
			);
363
		}, $filteredMounts);
364
	}
365
366
	/**
367
	 * Remove all cached mounts for a user
368
	 *
369
	 * @param IUser $user
370
	 */
371
	public function removeUserMounts(IUser $user) {
372
		$builder = $this->connection->getQueryBuilder();
373
374
		$query = $builder->delete('mounts')
375
			->where($builder->expr()->eq('user_id', $builder->createNamedParameter($user->getUID())));
376
		$query->execute();
377
	}
378
379
	public function removeUserStorageMount($storageId, $userId) {
380
		$builder = $this->connection->getQueryBuilder();
381
382
		$query = $builder->delete('mounts')
383
			->where($builder->expr()->eq('user_id', $builder->createNamedParameter($userId)))
384
			->andWhere($builder->expr()->eq('storage_id', $builder->createNamedParameter($storageId, IQueryBuilder::PARAM_INT)));
385
		$query->execute();
386
	}
387
388
	public function remoteStorageMounts($storageId) {
389
		$builder = $this->connection->getQueryBuilder();
390
391
		$query = $builder->delete('mounts')
392
			->where($builder->expr()->eq('storage_id', $builder->createNamedParameter($storageId, IQueryBuilder::PARAM_INT)));
393
		$query->execute();
394
	}
395
396
	/**
397
	 * @param array $users
398
	 * @return array
399
	 */
400
	public function getUsedSpaceForUsers(array $users) {
401
		$builder = $this->connection->getQueryBuilder();
402
403
		$slash = $builder->createNamedParameter('/');
404
405
		$mountPoint = $builder->func()->concat(
406
			$builder->func()->concat($slash, 'user_id'),
407
			$slash
408
		);
409
410
		$userIds = array_map(function (IUser $user) {
411
			return $user->getUID();
412
		}, $users);
413
414
		$query = $builder->select('m.user_id', 'f.size')
415
			->from('mounts', 'm')
416
			->innerJoin('m', 'filecache', 'f',
417
				$builder->expr()->andX(
418
					$builder->expr()->eq('m.storage_id', 'f.storage'),
419
					$builder->expr()->eq('f.path_hash', $builder->createNamedParameter(md5('files')))
420
				))
421
			->where($builder->expr()->eq('m.mount_point', $mountPoint))
422
			->andWhere($builder->expr()->in('m.user_id', $builder->createNamedParameter($userIds, IQueryBuilder::PARAM_STR_ARRAY)));
423
424
		$result = $query->execute();
425
426
		$results = [];
427
		while ($row = $result->fetch()) {
428
			$results[$row['user_id']] = $row['size'];
429
		}
430
		$result->closeCursor();
431
		return $results;
432
	}
433
434
	public function clear(): void {
435
		$this->cacheInfoCache = new CappedMemoryCache();
436
		$this->mountsForUsers = new CappedMemoryCache();
437
	}
438
}
439