Completed
Push — stable13 ( 9c0a9a...065ec7 )
by Morris
19:50 queued 05:17
created

UserMountCache::removeUserMounts()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7

Duplication

Lines 7
Ratio 100 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 1
dl 7
loc 7
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * @copyright Copyright (c) 2016, ownCloud, Inc.
4
 *
5
 * @author Joas Schilling <[email protected]>
6
 * @author Lukas Reschke <[email protected]>
7
 * @author Robin Appelman <[email protected]>
8
 * @author Vincent Petry <[email protected]>
9
 *
10
 * @license AGPL-3.0
11
 *
12
 * This code is free software: you can redistribute it and/or modify
13
 * it under the terms of the GNU Affero General Public License, version 3,
14
 * as published by the Free Software Foundation.
15
 *
16
 * This program is distributed in the hope that it will be useful,
17
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19
 * GNU Affero General Public License for more details.
20
 *
21
 * You should have received a copy of the GNU Affero General Public License, version 3,
22
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
23
 *
24
 */
25
26
namespace OC\Files\Config;
27
28
use OC\DB\QueryBuilder\Literal;
29
use OCA\Files_Sharing\SharedMount;
30
use OCP\DB\QueryBuilder\IQueryBuilder;
31
use OCP\Files\Config\ICachedMountFileInfo;
32
use OCP\Files\Config\ICachedMountInfo;
33
use OCP\Files\Config\IUserMountCache;
34
use OCP\Files\Mount\IMountPoint;
35
use OCP\Files\NotFoundException;
36
use OCP\ICache;
37
use OCP\IDBConnection;
38
use OCP\ILogger;
39
use OCP\IUser;
40
use OCP\IUserManager;
41
use OC\Cache\CappedMemoryCache;
42
43
/**
44
 * Cache mounts points per user in the cache so we can easilly look them up
45
 */
46
class UserMountCache implements IUserMountCache {
47
	/**
48
	 * @var IDBConnection
49
	 */
50
	private $connection;
51
52
	/**
53
	 * @var IUserManager
54
	 */
55
	private $userManager;
56
57
	/**
58
	 * Cached mount info.
59
	 * Map of $userId to ICachedMountInfo.
60
	 *
61
	 * @var ICache
62
	 **/
63
	private $mountsForUsers;
64
65
	/**
66
	 * @var ILogger
67
	 */
68
	private $logger;
69
70
	/**
71
	 * @var ICache
72
	 */
73
	private $cacheInfoCache;
74
75
	/**
76
	 * UserMountCache constructor.
77
	 *
78
	 * @param IDBConnection $connection
79
	 * @param IUserManager $userManager
80
	 * @param ILogger $logger
81
	 */
82
	public function __construct(IDBConnection $connection, IUserManager $userManager, ILogger $logger) {
83
		$this->connection = $connection;
84
		$this->userManager = $userManager;
85
		$this->logger = $logger;
86
		$this->cacheInfoCache = new CappedMemoryCache();
87
		$this->mountsForUsers = new CappedMemoryCache();
88
	}
89
90
	public function registerMounts(IUser $user, array $mounts) {
91
		// filter out non-proper storages coming from unit tests
92
		$mounts = array_filter($mounts, function (IMountPoint $mount) {
93
			return $mount instanceof SharedMount || $mount->getStorage() && $mount->getStorage()->getCache();
94
		});
95
		/** @var ICachedMountInfo[] $newMounts */
96
		$newMounts = array_map(function (IMountPoint $mount) use ($user) {
97
			// filter out any storages which aren't scanned yet since we aren't interested in files from those storages (yet)
98
			if ($mount->getStorageRootId() === -1) {
99
				return null;
100
			} else {
101
				return new LazyStorageMountInfo($user, $mount);
102
			}
103
		}, $mounts);
104
		$newMounts = array_values(array_filter($newMounts));
105
		$newMountRootIds = array_map(function (ICachedMountInfo $mount) {
106
			return $mount->getRootId();
107
		}, $newMounts);
108
		$newMounts = array_combine($newMountRootIds, $newMounts);
109
110
		$cachedMounts = $this->getMountsForUser($user);
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
		foreach ($addedMounts as $mount) {
134
			$this->addToCache($mount);
135
			$this->mountsForUsers[$user->getUID()][] = $mount;
136
		}
137
		foreach ($removedMounts as $mount) {
138
			$this->removeFromCache($mount);
139
			$index = array_search($mount, $this->mountsForUsers[$user->getUID()]);
140
			unset($this->mountsForUsers[$user->getUID()][$index]);
141
		}
142
		foreach ($changedMounts as $mount) {
143
			$this->updateCachedMount($mount);
144
		}
145
	}
146
147
	/**
148
	 * @param ICachedMountInfo[] $newMounts
149
	 * @param ICachedMountInfo[] $cachedMounts
150
	 * @return ICachedMountInfo[]
151
	 */
152
	private function findChangedMounts(array $newMounts, array $cachedMounts) {
153
		$new = [];
154
		foreach ($newMounts as $mount) {
155
			$new[$mount->getRootId()] = $mount;
156
		}
157
		$changed = [];
158
		foreach ($cachedMounts as $cachedMount) {
159
			$rootId = $cachedMount->getRootId();
160
			if (isset($new[$rootId])) {
161
				$newMount = $new[$rootId];
162
				if (
163
					$newMount->getMountPoint() !== $cachedMount->getMountPoint() ||
164
					$newMount->getStorageId() !== $cachedMount->getStorageId() ||
165
					$newMount->getMountId() !== $cachedMount->getMountId()
166
				) {
167
					$changed[] = $newMount;
168
				}
169
			}
170
		}
171
		return $changed;
172
	}
173
174
	private function addToCache(ICachedMountInfo $mount) {
175
		if ($mount->getStorageId() !== -1) {
176
			$this->connection->insertIfNotExist('*PREFIX*mounts', [
177
				'storage_id' => $mount->getStorageId(),
178
				'root_id' => $mount->getRootId(),
179
				'user_id' => $mount->getUser()->getUID(),
180
				'mount_point' => $mount->getMountPoint(),
181
				'mount_id' => $mount->getMountId()
182
			], ['root_id', 'user_id']);
183
		} else {
184
			// in some cases this is legitimate, like orphaned shares
185
			$this->logger->debug('Could not get storage info for mount at ' . $mount->getMountPoint());
186
		}
187
	}
188
189
	private function updateCachedMount(ICachedMountInfo $mount) {
190
		$builder = $this->connection->getQueryBuilder();
191
192
		$query = $builder->update('mounts')
193
			->set('storage_id', $builder->createNamedParameter($mount->getStorageId()))
194
			->set('mount_point', $builder->createNamedParameter($mount->getMountPoint()))
195
			->set('mount_id', $builder->createNamedParameter($mount->getMountId(), IQueryBuilder::PARAM_INT))
196
			->where($builder->expr()->eq('user_id', $builder->createNamedParameter($mount->getUser()->getUID())))
197
			->andWhere($builder->expr()->eq('root_id', $builder->createNamedParameter($mount->getRootId(), IQueryBuilder::PARAM_INT)));
198
199
		$query->execute();
200
	}
201
202 View Code Duplication
	private function removeFromCache(ICachedMountInfo $mount) {
203
		$builder = $this->connection->getQueryBuilder();
204
205
		$query = $builder->delete('mounts')
206
			->where($builder->expr()->eq('user_id', $builder->createNamedParameter($mount->getUser()->getUID())))
207
			->andWhere($builder->expr()->eq('root_id', $builder->createNamedParameter($mount->getRootId(), IQueryBuilder::PARAM_INT)));
208
		$query->execute();
209
	}
210
211
	private function dbRowToMountInfo(array $row) {
212
		$user = $this->userManager->get($row['user_id']);
213
		if (is_null($user)) {
214
			return null;
215
		}
216
		$mount_id = $row['mount_id'];
217
		if (!is_null($mount_id)) {
218
			$mount_id = (int)$mount_id;
219
		}
220
		return new CachedMountInfo($user, (int)$row['storage_id'], (int)$row['root_id'], $row['mount_point'], $mount_id, isset($row['path']) ? $row['path'] : '');
221
	}
222
223
	/**
224
	 * @param IUser $user
225
	 * @return ICachedMountInfo[]
226
	 */
227
	public function getMountsForUser(IUser $user) {
228
		if (!isset($this->mountsForUsers[$user->getUID()])) {
229
			$builder = $this->connection->getQueryBuilder();
230
			$query = $builder->select('storage_id', 'root_id', 'user_id', 'mount_point', 'mount_id', 'f.path')
231
				->from('mounts', 'm')
232
				->innerJoin('m', 'filecache', 'f', $builder->expr()->eq('m.root_id', 'f.fileid'))
233
				->where($builder->expr()->eq('user_id', $builder->createPositionalParameter($user->getUID())));
234
235
			$rows = $query->execute()->fetchAll();
236
237
			$this->mountsForUsers[$user->getUID()] = array_filter(array_map([$this, 'dbRowToMountInfo'], $rows));
238
		}
239
		return $this->mountsForUsers[$user->getUID()];
240
	}
241
242
	/**
243
	 * @param int $numericStorageId
244
	 * @param string|null $user limit the results to a single user
245
	 * @return CachedMountInfo[]
246
	 */
247
	public function getMountsForStorageId($numericStorageId, $user = null) {
248
		$builder = $this->connection->getQueryBuilder();
249
		$query = $builder->select('storage_id', 'root_id', 'user_id', 'mount_point', 'mount_id', 'f.path')
250
			->from('mounts', 'm')
251
			->innerJoin('m', 'filecache', 'f', $builder->expr()->eq('m.root_id', 'f.fileid'))
252
			->where($builder->expr()->eq('storage_id', $builder->createPositionalParameter($numericStorageId, IQueryBuilder::PARAM_INT)));
253
254
		if ($user) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $user of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
255
			$query->andWhere($builder->expr()->eq('user_id', $builder->createPositionalParameter($user)));
256
		}
257
258
		$rows = $query->execute()->fetchAll();
259
260
		return array_filter(array_map([$this, 'dbRowToMountInfo'], $rows));
261
	}
262
263
	/**
264
	 * @param int $rootFileId
265
	 * @return CachedMountInfo[]
266
	 */
267
	public function getMountsForRootId($rootFileId) {
268
		$builder = $this->connection->getQueryBuilder();
269
		$query = $builder->select('storage_id', 'root_id', 'user_id', 'mount_point', 'mount_id', 'f.path')
270
			->from('mounts', 'm')
271
			->innerJoin('m', 'filecache', 'f', $builder->expr()->eq('m.root_id', 'f.fileid'))
272
			->where($builder->expr()->eq('root_id', $builder->createPositionalParameter($rootFileId, IQueryBuilder::PARAM_INT)));
273
274
		$rows = $query->execute()->fetchAll();
275
276
		return array_filter(array_map([$this, 'dbRowToMountInfo'], $rows));
277
	}
278
279
	/**
280
	 * @param $fileId
281
	 * @return array
282
	 * @throws \OCP\Files\NotFoundException
283
	 */
284
	private function getCacheInfoFromFileId($fileId) {
285
		if (!isset($this->cacheInfoCache[$fileId])) {
286
			$builder = $this->connection->getQueryBuilder();
287
			$query = $builder->select('storage', 'path', 'mimetype')
288
				->from('filecache')
289
				->where($builder->expr()->eq('fileid', $builder->createNamedParameter($fileId, IQueryBuilder::PARAM_INT)));
290
291
			$row = $query->execute()->fetch();
292
			if (is_array($row)) {
293
				$this->cacheInfoCache[$fileId] = [
294
					(int)$row['storage'],
295
					$row['path'],
296
					(int)$row['mimetype']
297
				];
298
			} else {
299
				throw new NotFoundException('File with id "' . $fileId . '" not found');
300
			}
301
		}
302
		return $this->cacheInfoCache[$fileId];
303
	}
304
305
	/**
306
	 * @param int $fileId
307
	 * @param string|null $user optionally restrict the results to a single user
308
	 * @return ICachedMountFileInfo[]
309
	 * @since 9.0.0
310
	 */
311
	public function getMountsForFileId($fileId, $user = null) {
312
		try {
313
			list($storageId, $internalPath) = $this->getCacheInfoFromFileId($fileId);
314
		} catch (NotFoundException $e) {
315
			return [];
316
		}
317
		$mountsForStorage = $this->getMountsForStorageId($storageId, $user);
318
319
		// filter mounts that are from the same storage but a different directory
320
		$filteredMounts = array_filter($mountsForStorage, function (ICachedMountInfo $mount) use ($internalPath, $fileId) {
321
			if ($fileId === $mount->getRootId()) {
322
				return true;
323
			}
324
			$internalMountPath = $mount->getRootInternalPath();
325
326
			return $internalMountPath === '' || substr($internalPath, 0, strlen($internalMountPath) + 1) === $internalMountPath . '/';
327
		});
328
329
		return array_map(function (ICachedMountInfo $mount) use ($internalPath) {
330
			return new CachedMountFileInfo(
331
				$mount->getUser(),
332
				$mount->getStorageId(),
333
				$mount->getRootId(),
334
				$mount->getMountPoint(),
335
				$mount->getMountId(),
336
				$mount->getRootInternalPath(),
337
				$internalPath
338
			);
339
		}, $filteredMounts);
340
	}
341
342
	/**
343
	 * Remove all cached mounts for a user
344
	 *
345
	 * @param IUser $user
346
	 */
347 View Code Duplication
	public function removeUserMounts(IUser $user) {
348
		$builder = $this->connection->getQueryBuilder();
349
350
		$query = $builder->delete('mounts')
351
			->where($builder->expr()->eq('user_id', $builder->createNamedParameter($user->getUID())));
352
		$query->execute();
353
	}
354
355 View Code Duplication
	public function removeUserStorageMount($storageId, $userId) {
356
		$builder = $this->connection->getQueryBuilder();
357
358
		$query = $builder->delete('mounts')
359
			->where($builder->expr()->eq('user_id', $builder->createNamedParameter($userId)))
360
			->andWhere($builder->expr()->eq('storage_id', $builder->createNamedParameter($storageId, IQueryBuilder::PARAM_INT)));
361
		$query->execute();
362
	}
363
364 View Code Duplication
	public function remoteStorageMounts($storageId) {
365
		$builder = $this->connection->getQueryBuilder();
366
367
		$query = $builder->delete('mounts')
368
			->where($builder->expr()->eq('storage_id', $builder->createNamedParameter($storageId, IQueryBuilder::PARAM_INT)));
369
		$query->execute();
370
	}
371
372
	/**
373
	 * @param array $users
374
	 * @return array
375
	 * @suppress SqlInjectionChecker
376
	 */
377
	public function getUsedSpaceForUsers(array $users) {
378
		$builder = $this->connection->getQueryBuilder();
379
380
		$slash = $builder->createNamedParameter('/');
381
382
		$mountPoint = $builder->func()->concat(
383
			$builder->func()->concat($slash, 'user_id'),
384
			$slash
385
		);
386
387
		$userIds = array_map(function (IUser $user) {
388
			return $user->getUID();
389
		}, $users);
390
391
		$query = $builder->select('m.user_id', 'f.size')
392
			->from('mounts', 'm')
393
			->innerJoin('m', 'filecache', 'f',
394
				$builder->expr()->andX(
0 ignored issues
show
Documentation introduced by
$builder->expr()->andX($...rameter(md5('files')))) is of type object<OCP\DB\QueryBuilder\ICompositeExpression>, but the function expects a string|null.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
395
					$builder->expr()->eq('m.storage_id', 'f.storage'),
396
					$builder->expr()->eq('f.path_hash', $builder->createNamedParameter(md5('files')))
397
				))
398
			->where($builder->expr()->eq('m.mount_point', $mountPoint))
399
			->andWhere($builder->expr()->in('m.user_id', $builder->createNamedParameter($userIds, IQueryBuilder::PARAM_STR_ARRAY)));
400
401
		$result = $query->execute();
402
403
		$results = [];
404
		while ($row = $result->fetch()) {
405
			$results[$row['user_id']] = $row['size'];
406
		}
407
		$result->closeCursor();
408
		return $results;
409
	}
410
}
411