|
1
|
|
|
<?php |
|
2
|
|
|
/** |
|
3
|
|
|
* @copyright Copyright (c) 2016, ownCloud, Inc. |
|
4
|
|
|
* |
|
5
|
|
|
* @author Joas Schilling <[email protected]> |
|
6
|
|
|
* @author Robin Appelman <[email protected]> |
|
7
|
|
|
* @author Vincent Petry <[email protected]> |
|
8
|
|
|
* |
|
9
|
|
|
* @license AGPL-3.0 |
|
10
|
|
|
* |
|
11
|
|
|
* This code is free software: you can redistribute it and/or modify |
|
12
|
|
|
* it under the terms of the GNU Affero General Public License, version 3, |
|
13
|
|
|
* as published by the Free Software Foundation. |
|
14
|
|
|
* |
|
15
|
|
|
* This program is distributed in the hope that it will be useful, |
|
16
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
17
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
18
|
|
|
* GNU Affero General Public License for more details. |
|
19
|
|
|
* |
|
20
|
|
|
* You should have received a copy of the GNU Affero General Public License, version 3, |
|
21
|
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/> |
|
22
|
|
|
* |
|
23
|
|
|
*/ |
|
24
|
|
|
|
|
25
|
|
|
namespace OC\Files\Config; |
|
26
|
|
|
|
|
27
|
|
|
use Doctrine\DBAL\Exception\UniqueConstraintViolationException; |
|
28
|
|
|
use OC\Files\Filesystem; |
|
29
|
|
|
use OCA\Files_Sharing\SharedMount; |
|
30
|
|
|
use OCP\DB\QueryBuilder\IQueryBuilder; |
|
31
|
|
|
use OCP\Files\Config\ICachedMountInfo; |
|
32
|
|
|
use OCP\Files\Config\IUserMountCache; |
|
33
|
|
|
use OCP\Files\Mount\IMountPoint; |
|
34
|
|
|
use OCP\Files\Node; |
|
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
|
|
|
|
|
106
|
|
|
$cachedMounts = $this->getMountsForUser($user); |
|
107
|
|
|
$mountDiff = function (ICachedMountInfo $mount1, ICachedMountInfo $mount2) { |
|
108
|
|
|
// since we are only looking for mounts for a specific user comparing on root id is enough |
|
109
|
|
|
return $mount1->getRootId() - $mount2->getRootId(); |
|
110
|
|
|
}; |
|
111
|
|
|
|
|
112
|
|
|
/** @var ICachedMountInfo[] $addedMounts */ |
|
113
|
|
|
$addedMounts = array_udiff($newMounts, $cachedMounts, $mountDiff); |
|
114
|
|
|
/** @var ICachedMountInfo[] $removedMounts */ |
|
115
|
|
|
$removedMounts = array_udiff($cachedMounts, $newMounts, $mountDiff); |
|
116
|
|
|
|
|
117
|
|
|
$changedMounts = $this->findChangedMounts($newMounts, $cachedMounts); |
|
118
|
|
|
|
|
119
|
|
|
foreach ($addedMounts as $mount) { |
|
120
|
|
|
$this->addToCache($mount); |
|
121
|
|
|
$this->mountsForUsers[$user->getUID()][] = $mount; |
|
122
|
|
|
} |
|
123
|
|
|
foreach ($removedMounts as $mount) { |
|
124
|
|
|
$this->removeFromCache($mount); |
|
125
|
|
|
$index = array_search($mount, $this->mountsForUsers[$user->getUID()]); |
|
126
|
|
|
unset($this->mountsForUsers[$user->getUID()][$index]); |
|
127
|
|
|
} |
|
128
|
|
|
foreach ($changedMounts as $mount) { |
|
129
|
|
|
$this->updateCachedMount($mount); |
|
130
|
|
|
} |
|
131
|
|
|
} |
|
132
|
|
|
|
|
133
|
|
|
/** |
|
134
|
|
|
* @param ICachedMountInfo[] $newMounts |
|
135
|
|
|
* @param ICachedMountInfo[] $cachedMounts |
|
136
|
|
|
* @return ICachedMountInfo[] |
|
137
|
|
|
*/ |
|
138
|
|
|
private function findChangedMounts(array $newMounts, array $cachedMounts) { |
|
139
|
|
|
$changed = []; |
|
140
|
|
|
foreach ($newMounts as $newMount) { |
|
141
|
|
|
foreach ($cachedMounts as $cachedMount) { |
|
142
|
|
|
if ( |
|
143
|
|
|
$newMount->getRootId() === $cachedMount->getRootId() && |
|
144
|
|
|
( |
|
145
|
|
|
$newMount->getMountPoint() !== $cachedMount->getMountPoint() || |
|
146
|
|
|
$newMount->getStorageId() !== $cachedMount->getStorageId() || |
|
147
|
|
|
$newMount->getMountId() !== $cachedMount->getMountId() |
|
148
|
|
|
) |
|
149
|
|
|
) { |
|
150
|
|
|
$changed[] = $newMount; |
|
151
|
|
|
} |
|
152
|
|
|
} |
|
153
|
|
|
} |
|
154
|
|
|
return $changed; |
|
155
|
|
|
} |
|
156
|
|
|
|
|
157
|
|
|
private function addToCache(ICachedMountInfo $mount) { |
|
158
|
|
|
if ($mount->getStorageId() !== -1) { |
|
159
|
|
|
$this->connection->insertIfNotExist('*PREFIX*mounts', [ |
|
160
|
|
|
'storage_id' => $mount->getStorageId(), |
|
161
|
|
|
'root_id' => $mount->getRootId(), |
|
162
|
|
|
'user_id' => $mount->getUser()->getUID(), |
|
163
|
|
|
'mount_point' => $mount->getMountPoint(), |
|
164
|
|
|
'mount_id' => $mount->getMountId() |
|
165
|
|
|
], ['root_id', 'user_id']); |
|
166
|
|
|
} else { |
|
167
|
|
|
// in some cases this is legitimate, like orphaned shares |
|
168
|
|
|
$this->logger->debug('Could not get storage info for mount at ' . $mount->getMountPoint()); |
|
169
|
|
|
} |
|
170
|
|
|
} |
|
171
|
|
|
|
|
172
|
|
|
private function updateCachedMount(ICachedMountInfo $mount) { |
|
173
|
|
|
$builder = $this->connection->getQueryBuilder(); |
|
174
|
|
|
|
|
175
|
|
|
$query = $builder->update('mounts') |
|
176
|
|
|
->set('storage_id', $builder->createNamedParameter($mount->getStorageId())) |
|
177
|
|
|
->set('mount_point', $builder->createNamedParameter($mount->getMountPoint())) |
|
178
|
|
|
->set('mount_id', $builder->createNamedParameter($mount->getMountId(), IQueryBuilder::PARAM_INT)) |
|
179
|
|
|
->where($builder->expr()->eq('user_id', $builder->createNamedParameter($mount->getUser()->getUID()))) |
|
180
|
|
|
->andWhere($builder->expr()->eq('root_id', $builder->createNamedParameter($mount->getRootId(), IQueryBuilder::PARAM_INT))); |
|
181
|
|
|
|
|
182
|
|
|
$query->execute(); |
|
183
|
|
|
} |
|
184
|
|
|
|
|
185
|
|
View Code Duplication |
private function removeFromCache(ICachedMountInfo $mount) { |
|
|
|
|
|
|
186
|
|
|
$builder = $this->connection->getQueryBuilder(); |
|
187
|
|
|
|
|
188
|
|
|
$query = $builder->delete('mounts') |
|
189
|
|
|
->where($builder->expr()->eq('user_id', $builder->createNamedParameter($mount->getUser()->getUID()))) |
|
190
|
|
|
->andWhere($builder->expr()->eq('root_id', $builder->createNamedParameter($mount->getRootId(), IQueryBuilder::PARAM_INT))); |
|
191
|
|
|
$query->execute(); |
|
192
|
|
|
} |
|
193
|
|
|
|
|
194
|
|
|
private function dbRowToMountInfo(array $row) { |
|
195
|
|
|
$user = $this->userManager->get($row['user_id']); |
|
196
|
|
|
if (is_null($user)) { |
|
197
|
|
|
return null; |
|
198
|
|
|
} |
|
199
|
|
|
return new CachedMountInfo($user, (int)$row['storage_id'], (int)$row['root_id'], $row['mount_point'], $row['mount_id'], isset($row['path'])? $row['path']:''); |
|
200
|
|
|
} |
|
201
|
|
|
|
|
202
|
|
|
/** |
|
203
|
|
|
* @param IUser $user |
|
204
|
|
|
* @return ICachedMountInfo[] |
|
205
|
|
|
*/ |
|
206
|
|
|
public function getMountsForUser(IUser $user) { |
|
207
|
|
|
if (!isset($this->mountsForUsers[$user->getUID()])) { |
|
208
|
|
|
$builder = $this->connection->getQueryBuilder(); |
|
209
|
|
|
$query = $builder->select('storage_id', 'root_id', 'user_id', 'mount_point', 'mount_id', 'f.path') |
|
210
|
|
|
->from('mounts', 'm') |
|
211
|
|
|
->innerJoin('m', 'filecache', 'f', $builder->expr()->eq('m.root_id', 'f.fileid')) |
|
212
|
|
|
->where($builder->expr()->eq('user_id', $builder->createPositionalParameter($user->getUID()))); |
|
213
|
|
|
|
|
214
|
|
|
$rows = $query->execute()->fetchAll(); |
|
215
|
|
|
|
|
216
|
|
|
$this->mountsForUsers[$user->getUID()] = array_filter(array_map([$this, 'dbRowToMountInfo'], $rows)); |
|
217
|
|
|
} |
|
218
|
|
|
return $this->mountsForUsers[$user->getUID()]; |
|
219
|
|
|
} |
|
220
|
|
|
|
|
221
|
|
|
/** |
|
222
|
|
|
* @param int $numericStorageId |
|
223
|
|
|
* @return CachedMountInfo[] |
|
224
|
|
|
*/ |
|
225
|
|
View Code Duplication |
public function getMountsForStorageId($numericStorageId) { |
|
|
|
|
|
|
226
|
|
|
$builder = $this->connection->getQueryBuilder(); |
|
227
|
|
|
$query = $builder->select('storage_id', 'root_id', 'user_id', 'mount_point', 'mount_id', 'f.path') |
|
228
|
|
|
->from('mounts', 'm') |
|
229
|
|
|
->innerJoin('m', 'filecache', 'f' , $builder->expr()->eq('m.root_id', 'f.fileid')) |
|
230
|
|
|
->where($builder->expr()->eq('storage_id', $builder->createPositionalParameter($numericStorageId, IQueryBuilder::PARAM_INT))); |
|
231
|
|
|
|
|
232
|
|
|
$rows = $query->execute()->fetchAll(); |
|
233
|
|
|
|
|
234
|
|
|
return array_filter(array_map([$this, 'dbRowToMountInfo'], $rows)); |
|
235
|
|
|
} |
|
236
|
|
|
|
|
237
|
|
|
/** |
|
238
|
|
|
* @param int $rootFileId |
|
239
|
|
|
* @return CachedMountInfo[] |
|
240
|
|
|
*/ |
|
241
|
|
View Code Duplication |
public function getMountsForRootId($rootFileId) { |
|
|
|
|
|
|
242
|
|
|
$builder = $this->connection->getQueryBuilder(); |
|
243
|
|
|
$query = $builder->select('storage_id', 'root_id', 'user_id', 'mount_point', 'mount_id', 'f.path') |
|
244
|
|
|
->from('mounts', 'm') |
|
245
|
|
|
->innerJoin('m', 'filecache', 'f', $builder->expr()->eq('m.root_id', 'f.fileid')) |
|
246
|
|
|
->where($builder->expr()->eq('root_id', $builder->createPositionalParameter($rootFileId, IQueryBuilder::PARAM_INT))); |
|
247
|
|
|
|
|
248
|
|
|
$rows = $query->execute()->fetchAll(); |
|
249
|
|
|
|
|
250
|
|
|
return array_filter(array_map([$this, 'dbRowToMountInfo'], $rows)); |
|
251
|
|
|
} |
|
252
|
|
|
|
|
253
|
|
|
/** |
|
254
|
|
|
* @param $fileId |
|
255
|
|
|
* @return array |
|
256
|
|
|
* @throws \OCP\Files\NotFoundException |
|
257
|
|
|
*/ |
|
258
|
|
|
private function getCacheInfoFromFileId($fileId) { |
|
259
|
|
|
if (!isset($this->cacheInfoCache[$fileId])) { |
|
260
|
|
|
$builder = $this->connection->getQueryBuilder(); |
|
261
|
|
|
$query = $builder->select('storage', 'path', 'mimetype') |
|
262
|
|
|
->from('filecache') |
|
263
|
|
|
->where($builder->expr()->eq('fileid', $builder->createNamedParameter($fileId, IQueryBuilder::PARAM_INT))); |
|
264
|
|
|
|
|
265
|
|
|
$row = $query->execute()->fetch(); |
|
266
|
|
|
if (is_array($row)) { |
|
267
|
|
|
$this->cacheInfoCache[$fileId] = [ |
|
268
|
|
|
(int)$row['storage'], |
|
269
|
|
|
$row['path'], |
|
270
|
|
|
(int)$row['mimetype'] |
|
271
|
|
|
]; |
|
272
|
|
|
} else { |
|
273
|
|
|
throw new NotFoundException('File with id "' . $fileId . '" not found'); |
|
274
|
|
|
} |
|
275
|
|
|
} |
|
276
|
|
|
return $this->cacheInfoCache[$fileId]; |
|
277
|
|
|
} |
|
278
|
|
|
|
|
279
|
|
|
/** |
|
280
|
|
|
* @param int $fileId |
|
281
|
|
|
* @return ICachedMountInfo[] |
|
282
|
|
|
* @since 9.0.0 |
|
283
|
|
|
*/ |
|
284
|
|
|
public function getMountsForFileId($fileId) { |
|
285
|
|
|
try { |
|
286
|
|
|
list($storageId, $internalPath) = $this->getCacheInfoFromFileId($fileId); |
|
287
|
|
|
} catch (NotFoundException $e) { |
|
288
|
|
|
return []; |
|
289
|
|
|
} |
|
290
|
|
|
$mountsForStorage = $this->getMountsForStorageId($storageId); |
|
291
|
|
|
|
|
292
|
|
|
// filter mounts that are from the same storage but a different directory |
|
293
|
|
|
return array_filter($mountsForStorage, function (ICachedMountInfo $mount) use ($internalPath, $fileId) { |
|
294
|
|
|
if ($fileId === $mount->getRootId()) { |
|
295
|
|
|
return true; |
|
296
|
|
|
} |
|
297
|
|
|
$internalMountPath = $mount->getRootInternalPath(); |
|
298
|
|
|
|
|
299
|
|
|
return $internalMountPath === '' || substr($internalPath, 0, strlen($internalMountPath) + 1) === $internalMountPath . '/'; |
|
300
|
|
|
}); |
|
301
|
|
|
} |
|
302
|
|
|
|
|
303
|
|
|
/** |
|
304
|
|
|
* Remove all cached mounts for a user |
|
305
|
|
|
* |
|
306
|
|
|
* @param IUser $user |
|
307
|
|
|
*/ |
|
308
|
|
View Code Duplication |
public function removeUserMounts(IUser $user) { |
|
|
|
|
|
|
309
|
|
|
$builder = $this->connection->getQueryBuilder(); |
|
310
|
|
|
|
|
311
|
|
|
$query = $builder->delete('mounts') |
|
312
|
|
|
->where($builder->expr()->eq('user_id', $builder->createNamedParameter($user->getUID()))); |
|
313
|
|
|
$query->execute(); |
|
314
|
|
|
} |
|
315
|
|
|
|
|
316
|
|
View Code Duplication |
public function removeUserStorageMount($storageId, $userId) { |
|
|
|
|
|
|
317
|
|
|
$builder = $this->connection->getQueryBuilder(); |
|
318
|
|
|
|
|
319
|
|
|
$query = $builder->delete('mounts') |
|
320
|
|
|
->where($builder->expr()->eq('user_id', $builder->createNamedParameter($userId))) |
|
321
|
|
|
->andWhere($builder->expr()->eq('storage_id', $builder->createNamedParameter($storageId, IQueryBuilder::PARAM_INT))); |
|
322
|
|
|
$query->execute(); |
|
323
|
|
|
} |
|
324
|
|
|
|
|
325
|
|
View Code Duplication |
public function remoteStorageMounts($storageId) { |
|
|
|
|
|
|
326
|
|
|
$builder = $this->connection->getQueryBuilder(); |
|
327
|
|
|
|
|
328
|
|
|
$query = $builder->delete('mounts') |
|
329
|
|
|
->where($builder->expr()->eq('storage_id', $builder->createNamedParameter($storageId, IQueryBuilder::PARAM_INT))); |
|
330
|
|
|
$query->execute(); |
|
331
|
|
|
} |
|
332
|
|
|
} |
|
333
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.