aboutsummaryrefslogtreecommitdiffstats
path: root/lib/private/Files/Config
diff options
context:
space:
mode:
Diffstat (limited to 'lib/private/Files/Config')
-rw-r--r--lib/private/Files/Config/CachedMountFileInfo.php25
-rw-r--r--lib/private/Files/Config/CachedMountInfo.php40
-rw-r--r--lib/private/Files/Config/LazyPathCachedMountInfo.php48
-rw-r--r--lib/private/Files/Config/LazyStorageMountInfo.php30
-rw-r--r--lib/private/Files/Config/MountProviderCollection.php164
-rw-r--r--lib/private/Files/Config/UserMountCache.php318
-rw-r--r--lib/private/Files/Config/UserMountCacheListener.php22
7 files changed, 339 insertions, 308 deletions
diff --git a/lib/private/Files/Config/CachedMountFileInfo.php b/lib/private/Files/Config/CachedMountFileInfo.php
index 11a9a505808..69bd4e9301e 100644
--- a/lib/private/Files/Config/CachedMountFileInfo.php
+++ b/lib/private/Files/Config/CachedMountFileInfo.php
@@ -1,25 +1,8 @@
<?php
+
/**
- * @copyright Copyright (c) 2017 Robin Appelman <robin@icewind.nl>
- *
- * @author Robin Appelman <robin@icewind.nl>
- * @author Roeland Jago Douma <roeland@famdouma.nl>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
+ * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OC\Files\Config;
@@ -37,7 +20,7 @@ class CachedMountFileInfo extends CachedMountInfo implements ICachedMountFileInf
?int $mountId,
string $mountProvider,
string $rootInternalPath,
- string $internalPath
+ string $internalPath,
) {
parent::__construct($user, $storageId, $rootId, $mountPoint, $mountProvider, $mountId, $rootInternalPath);
$this->internalPath = $internalPath;
diff --git a/lib/private/Files/Config/CachedMountInfo.php b/lib/private/Files/Config/CachedMountInfo.php
index 43c9fae63ec..79dd6c6ea1d 100644
--- a/lib/private/Files/Config/CachedMountInfo.php
+++ b/lib/private/Files/Config/CachedMountInfo.php
@@ -1,24 +1,9 @@
<?php
+
/**
- * @copyright Copyright (c) 2016, ownCloud, Inc.
- *
- * @author Robin Appelman <robin@icewind.nl>
- * @author Semih Serhat Karakaya <karakayasemi@itu.edu.tr>
- *
- * @license AGPL-3.0
- *
- * This code is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License, version 3,
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License, version 3,
- * along with this program. If not, see <http://www.gnu.org/licenses/>
- *
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
*/
namespace OC\Files\Config;
@@ -35,6 +20,7 @@ class CachedMountInfo implements ICachedMountInfo {
protected ?int $mountId;
protected string $rootInternalPath;
protected string $mountProvider;
+ protected string $key;
/**
* CachedMountInfo constructor.
@@ -52,8 +38,8 @@ class CachedMountInfo implements ICachedMountInfo {
int $rootId,
string $mountPoint,
string $mountProvider,
- int $mountId = null,
- string $rootInternalPath = ''
+ ?int $mountId = null,
+ string $rootInternalPath = '',
) {
$this->user = $user;
$this->storageId = $storageId;
@@ -65,6 +51,7 @@ class CachedMountInfo implements ICachedMountInfo {
throw new \Exception("Mount provider $mountProvider name exceeds the limit of 128 characters");
}
$this->mountProvider = $mountProvider;
+ $this->key = $rootId . '::' . $mountPoint;
}
/**
@@ -95,12 +82,7 @@ class CachedMountInfo implements ICachedMountInfo {
// TODO injection etc
Filesystem::initMountPoints($this->getUser()->getUID());
$userNode = \OC::$server->getUserFolder($this->getUser()->getUID());
- $nodes = $userNode->getParent()->getById($this->getRootId());
- if (count($nodes) > 0) {
- return $nodes[0];
- } else {
- return null;
- }
+ return $userNode->getParent()->getFirstNodeById($this->getRootId());
}
/**
@@ -132,4 +114,8 @@ class CachedMountInfo implements ICachedMountInfo {
public function getMountProvider(): string {
return $this->mountProvider;
}
+
+ public function getKey(): string {
+ return $this->key;
+ }
}
diff --git a/lib/private/Files/Config/LazyPathCachedMountInfo.php b/lib/private/Files/Config/LazyPathCachedMountInfo.php
new file mode 100644
index 00000000000..d2396109b1a
--- /dev/null
+++ b/lib/private/Files/Config/LazyPathCachedMountInfo.php
@@ -0,0 +1,48 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+namespace OC\Files\Config;
+
+use OCP\IUser;
+
+class LazyPathCachedMountInfo extends CachedMountInfo {
+ // we don't allow \ in paths so it makes a great placeholder
+ private const PATH_PLACEHOLDER = '\\PLACEHOLDER\\';
+
+ /** @var callable(CachedMountInfo): string */
+ protected $rootInternalPathCallback;
+
+ /**
+ * @param IUser $user
+ * @param int $storageId
+ * @param int $rootId
+ * @param string $mountPoint
+ * @param string $mountProvider
+ * @param int|null $mountId
+ * @param callable(CachedMountInfo): string $rootInternalPathCallback
+ * @throws \Exception
+ */
+ public function __construct(
+ IUser $user,
+ int $storageId,
+ int $rootId,
+ string $mountPoint,
+ string $mountProvider,
+ ?int $mountId,
+ callable $rootInternalPathCallback,
+ ) {
+ parent::__construct($user, $storageId, $rootId, $mountPoint, $mountProvider, $mountId, self::PATH_PLACEHOLDER);
+ $this->rootInternalPathCallback = $rootInternalPathCallback;
+ }
+
+ public function getRootInternalPath(): string {
+ if ($this->rootInternalPath === self::PATH_PLACEHOLDER) {
+ $this->rootInternalPath = ($this->rootInternalPathCallback)($this);
+ }
+ return $this->rootInternalPath;
+ }
+}
diff --git a/lib/private/Files/Config/LazyStorageMountInfo.php b/lib/private/Files/Config/LazyStorageMountInfo.php
index 78055a2cdb8..eb2c60dfa46 100644
--- a/lib/private/Files/Config/LazyStorageMountInfo.php
+++ b/lib/private/Files/Config/LazyStorageMountInfo.php
@@ -1,23 +1,9 @@
<?php
+
/**
- * @copyright Copyright (c) 2016, ownCloud, Inc.
- *
- * @author Robin Appelman <robin@icewind.nl>
- *
- * @license AGPL-3.0
- *
- * This code is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License, version 3,
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License, version 3,
- * along with this program. If not, see <http://www.gnu.org/licenses/>
- *
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
*/
namespace OC\Files\Config;
@@ -39,6 +25,7 @@ class LazyStorageMountInfo extends CachedMountInfo {
$this->rootId = 0;
$this->storageId = 0;
$this->mountPoint = '';
+ $this->key = '';
}
/**
@@ -87,4 +74,11 @@ class LazyStorageMountInfo extends CachedMountInfo {
public function getMountProvider(): string {
return $this->mount->getMountProvider();
}
+
+ public function getKey(): string {
+ if (!$this->key) {
+ $this->key = $this->getRootId() . '::' . $this->getMountPoint();
+ }
+ return $this->key;
+ }
}
diff --git a/lib/private/Files/Config/MountProviderCollection.php b/lib/private/Files/Config/MountProviderCollection.php
index 0e08d9d0e83..9d63184e05f 100644
--- a/lib/private/Files/Config/MountProviderCollection.php
+++ b/lib/private/Files/Config/MountProviderCollection.php
@@ -1,31 +1,15 @@
<?php
+
/**
- * @copyright Copyright (c) 2016, ownCloud, Inc.
- *
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Morris Jobke <hey@morrisjobke.de>
- * @author Robin Appelman <robin@icewind.nl>
- * @author Roeland Jago Douma <roeland@famdouma.nl>
- *
- * @license AGPL-3.0
- *
- * This code is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License, version 3,
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License, version 3,
- * along with this program. If not, see <http://www.gnu.org/licenses/>
- *
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
*/
namespace OC\Files\Config;
use OC\Hooks\Emitter;
use OC\Hooks\EmitterTrait;
+use OCP\Diagnostics\IEventLogger;
use OCP\Files\Config\IHomeMountProvider;
use OCP\Files\Config\IMountProvider;
use OCP\Files\Config\IMountProviderCollection;
@@ -40,63 +24,65 @@ class MountProviderCollection implements IMountProviderCollection, Emitter {
use EmitterTrait;
/**
- * @var \OCP\Files\Config\IHomeMountProvider[]
+ * @var list<IHomeMountProvider>
*/
- private $homeProviders = [];
+ private array $homeProviders = [];
/**
- * @var \OCP\Files\Config\IMountProvider[]
+ * @var list<IMountProvider>
*/
- private $providers = [];
+ private array $providers = [];
- /** @var \OCP\Files\Config\IRootMountProvider[] */
- private $rootProviders = [];
+ /** @var list<IRootMountProvider> */
+ private array $rootProviders = [];
- /**
- * @var \OCP\Files\Storage\IStorageFactory
- */
- private $loader;
+ /** @var list<callable> */
+ private array $mountFilters = [];
- /**
- * @var \OCP\Files\Config\IUserMountCache
- */
- private $mountCache;
-
- /** @var callable[] */
- private $mountFilters = [];
+ public function __construct(
+ private IStorageFactory $loader,
+ private IUserMountCache $mountCache,
+ private IEventLogger $eventLogger,
+ ) {
+ }
/**
- * @param \OCP\Files\Storage\IStorageFactory $loader
- * @param IUserMountCache $mountCache
+ * @return list<IMountPoint>
*/
- public function __construct(IStorageFactory $loader, IUserMountCache $mountCache) {
- $this->loader = $loader;
- $this->mountCache = $mountCache;
+ private function getMountsFromProvider(IMountProvider $provider, IUser $user, IStorageFactory $loader): array {
+ $class = str_replace('\\', '_', get_class($provider));
+ $uid = $user->getUID();
+ $this->eventLogger->start('fs:setup:provider:' . $class, "Getting mounts from $class for $uid");
+ $mounts = $provider->getMountsForUser($user, $loader) ?? [];
+ $this->eventLogger->end('fs:setup:provider:' . $class);
+ return array_values($mounts);
}
/**
- * @param IUser $user
- * @param IMountProvider[] $providers
- * @return IMountPoint[]
+ * @param list<IMountProvider> $providers
+ * @return list<IMountPoint>
*/
private function getUserMountsForProviders(IUser $user, array $providers): array {
$loader = $this->loader;
$mounts = array_map(function (IMountProvider $provider) use ($user, $loader) {
- return $provider->getMountsForUser($user, $loader);
+ return $this->getMountsFromProvider($provider, $user, $loader);
}, $providers);
- $mounts = array_filter($mounts, function ($result) {
- return is_array($result);
- });
$mounts = array_reduce($mounts, function (array $mounts, array $providerMounts) {
return array_merge($mounts, $providerMounts);
}, []);
return $this->filterMounts($user, $mounts);
}
+ /**
+ * @return list<IMountPoint>
+ */
public function getMountsForUser(IUser $user): array {
return $this->getUserMountsForProviders($user, $this->providers);
}
+ /**
+ * @return list<IMountPoint>
+ */
public function getUserMountsForProviderClasses(IUser $user, array $mountProviderClasses): array {
$providers = array_filter(
$this->providers,
@@ -105,7 +91,10 @@ class MountProviderCollection implements IMountProviderCollection, Emitter {
return $this->getUserMountsForProviders($user, $providers);
}
- public function addMountForUser(IUser $user, IMountManager $mountManager, callable $providerFilter = null) {
+ /**
+ * @return list<IMountPoint>
+ */
+ public function addMountForUser(IUser $user, IMountManager $mountManager, ?callable $providerFilter = null): array {
// shared mount provider gets to go last since it needs to know existing files
// to check for name collisions
$firstMounts = [];
@@ -121,37 +110,32 @@ class MountProviderCollection implements IMountProviderCollection, Emitter {
return (get_class($provider) === 'OCA\Files_Sharing\MountProvider');
});
foreach ($firstProviders as $provider) {
- $mounts = $provider->getMountsForUser($user, $this->loader);
- if (is_array($mounts)) {
- $firstMounts = array_merge($firstMounts, $mounts);
- }
+ $mounts = $this->getMountsFromProvider($provider, $user, $this->loader);
+ $firstMounts = array_merge($firstMounts, $mounts);
}
$firstMounts = $this->filterMounts($user, $firstMounts);
array_walk($firstMounts, [$mountManager, 'addMount']);
$lateMounts = [];
foreach ($lastProviders as $provider) {
- $mounts = $provider->getMountsForUser($user, $this->loader);
- if (is_array($mounts)) {
- $lateMounts = array_merge($lateMounts, $mounts);
- }
+ $mounts = $this->getMountsFromProvider($provider, $user, $this->loader);
+ $lateMounts = array_merge($lateMounts, $mounts);
}
$lateMounts = $this->filterMounts($user, $lateMounts);
+ $this->eventLogger->start('fs:setup:add-mounts', 'Add mounts to the filesystem');
array_walk($lateMounts, [$mountManager, 'addMount']);
+ $this->eventLogger->end('fs:setup:add-mounts');
- return array_merge($lateMounts, $firstMounts);
+ return array_values(array_merge($lateMounts, $firstMounts));
}
/**
* Get the configured home mount for this user
*
- * @param \OCP\IUser $user
- * @return \OCP\Files\Mount\IMountPoint
* @since 9.1.0
*/
- public function getHomeMountForUser(IUser $user) {
- /** @var \OCP\Files\Config\IHomeMountProvider[] $providers */
+ public function getHomeMountForUser(IUser $user): IMountPoint {
$providers = array_reverse($this->homeProviders); // call the latest registered provider first to give apps an opportunity to overwrite builtin
foreach ($providers as $homeProvider) {
if ($mount = $homeProvider->getHomeMountForUser($user, $this->loader)) {
@@ -164,34 +148,36 @@ class MountProviderCollection implements IMountProviderCollection, Emitter {
/**
* Add a provider for mount points
- *
- * @param \OCP\Files\Config\IMountProvider $provider
*/
- public function registerProvider(IMountProvider $provider) {
+ public function registerProvider(IMountProvider $provider): void {
$this->providers[] = $provider;
$this->emit('\OC\Files\Config', 'registerMountProvider', [$provider]);
}
- public function registerMountFilter(callable $filter) {
+ public function registerMountFilter(callable $filter): void {
$this->mountFilters[] = $filter;
}
- private function filterMounts(IUser $user, array $mountPoints) {
- return array_filter($mountPoints, function (IMountPoint $mountPoint) use ($user) {
+ /**
+ * @param list<IMountPoint> $mountPoints
+ * @return list<IMountPoint>
+ */
+ private function filterMounts(IUser $user, array $mountPoints): array {
+ return array_values(array_filter($mountPoints, function (IMountPoint $mountPoint) use ($user) {
foreach ($this->mountFilters as $filter) {
if ($filter($mountPoint, $user) === false) {
return false;
}
}
return true;
- });
+ }));
}
/**
* Add a provider for home mount points
*
- * @param \OCP\Files\Config\IHomeMountProvider $provider
+ * @param IHomeMountProvider $provider
* @since 9.1.0
*/
public function registerHomeProvider(IHomeMountProvider $provider) {
@@ -201,21 +187,19 @@ class MountProviderCollection implements IMountProviderCollection, Emitter {
/**
* Get the mount cache which can be used to search for mounts without setting up the filesystem
- *
- * @return IUserMountCache
*/
- public function getMountCache() {
+ public function getMountCache(): IUserMountCache {
return $this->mountCache;
}
- public function registerRootProvider(IRootMountProvider $provider) {
+ public function registerRootProvider(IRootMountProvider $provider): void {
$this->rootProviders[] = $provider;
}
/**
* Get all root mountpoints
*
- * @return \OCP\Files\Mount\IMountPoint[]
+ * @return list<IMountPoint>
* @since 20.0.0
*/
public function getRootMounts(): array {
@@ -226,16 +210,38 @@ class MountProviderCollection implements IMountProviderCollection, Emitter {
$mounts = array_reduce($mounts, function (array $mounts, array $providerMounts) {
return array_merge($mounts, $providerMounts);
}, []);
- return $mounts;
+
+ if (count($mounts) === 0) {
+ throw new \Exception('No root mounts provided by any provider');
+ }
+
+ return array_values($mounts);
}
- public function clearProviders() {
+ public function clearProviders(): void {
$this->providers = [];
$this->homeProviders = [];
$this->rootProviders = [];
}
+ /**
+ * @return list<IMountProvider>
+ */
public function getProviders(): array {
return $this->providers;
}
+
+ /**
+ * @return list<IHomeMountProvider>
+ */
+ public function getHomeProviders(): array {
+ return $this->homeProviders;
+ }
+
+ /**
+ * @return list<IRootMountProvider>
+ */
+ public function getRootProviders(): array {
+ return $this->rootProviders;
+ }
}
diff --git a/lib/private/Files/Config/UserMountCache.php b/lib/private/Files/Config/UserMountCache.php
index 3540b563742..3e53a67a044 100644
--- a/lib/private/Files/Config/UserMountCache.php
+++ b/lib/private/Files/Config/UserMountCache.php
@@ -1,40 +1,23 @@
<?php
+
/**
- * @copyright Copyright (c) 2016, ownCloud, Inc.
- *
- * @author Dariusz Olszewski <starypatyk@users.noreply.github.com>
- * @author Joas Schilling <coding@schilljs.com>
- * @author Julius Härtl <jus@bitgrid.net>
- * @author Lukas Reschke <lukas@statuscode.ch>
- * @author Morris Jobke <hey@morrisjobke.de>
- * @author Robin Appelman <robin@icewind.nl>
- * @author Roeland Jago Douma <roeland@famdouma.nl>
- * @author Vincent Petry <vincent@nextcloud.com>
- *
- * @license AGPL-3.0
- *
- * This code is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License, version 3,
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License, version 3,
- * along with this program. If not, see <http://www.gnu.org/licenses/>
- *
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
*/
namespace OC\Files\Config;
+use OC\User\LazyUser;
use OCP\Cache\CappedMemoryCache;
-use OCA\Files_Sharing\SharedMount;
use OCP\DB\QueryBuilder\IQueryBuilder;
+use OCP\Diagnostics\IEventLogger;
+use OCP\EventDispatcher\IEventDispatcher;
+use OCP\Files\Config\Event\UserMountAddedEvent;
+use OCP\Files\Config\Event\UserMountRemovedEvent;
+use OCP\Files\Config\Event\UserMountUpdatedEvent;
use OCP\Files\Config\ICachedMountFileInfo;
use OCP\Files\Config\ICachedMountInfo;
use OCP\Files\Config\IUserMountCache;
-use OCP\Files\Mount\IMountPoint;
use OCP\Files\NotFoundException;
use OCP\IDBConnection;
use OCP\IUser;
@@ -45,119 +28,134 @@ use Psr\Log\LoggerInterface;
* Cache mounts points per user in the cache so we can easily look them up
*/
class UserMountCache implements IUserMountCache {
- private IDBConnection $connection;
- private IUserManager $userManager;
/**
* Cached mount info.
* @var CappedMemoryCache<ICachedMountInfo[]>
**/
private CappedMemoryCache $mountsForUsers;
- private LoggerInterface $logger;
+ /**
+ * fileid => internal path mapping for cached mount info.
+ * @var CappedMemoryCache<string>
+ **/
+ private CappedMemoryCache $internalPathCache;
/** @var CappedMemoryCache<array> */
private CappedMemoryCache $cacheInfoCache;
/**
* UserMountCache constructor.
*/
- public function __construct(IDBConnection $connection, IUserManager $userManager, LoggerInterface $logger) {
- $this->connection = $connection;
- $this->userManager = $userManager;
- $this->logger = $logger;
+ public function __construct(
+ private IDBConnection $connection,
+ private IUserManager $userManager,
+ private LoggerInterface $logger,
+ private IEventLogger $eventLogger,
+ private IEventDispatcher $eventDispatcher,
+ ) {
$this->cacheInfoCache = new CappedMemoryCache();
+ $this->internalPathCache = new CappedMemoryCache();
$this->mountsForUsers = new CappedMemoryCache();
}
- public function registerMounts(IUser $user, array $mounts, array $mountProviderClasses = null) {
- // filter out non-proper storages coming from unit tests
- $mounts = array_filter($mounts, function (IMountPoint $mount) {
- return $mount instanceof SharedMount || ($mount->getStorage() && $mount->getStorage()->getCache());
- });
- /** @var ICachedMountInfo[] $newMounts */
- $newMounts = array_map(function (IMountPoint $mount) use ($user) {
+ public function registerMounts(IUser $user, array $mounts, ?array $mountProviderClasses = null) {
+ $this->eventLogger->start('fs:setup:user:register', 'Registering mounts for user');
+ /** @var array<string, ICachedMountInfo> $newMounts */
+ $newMounts = [];
+ foreach ($mounts as $mount) {
// filter out any storages which aren't scanned yet since we aren't interested in files from those storages (yet)
- if ($mount->getStorageRootId() === -1) {
- return null;
- } else {
- return new LazyStorageMountInfo($user, $mount);
+ if ($mount->getStorageRootId() !== -1) {
+ $mountInfo = new LazyStorageMountInfo($user, $mount);
+ $newMounts[$mountInfo->getKey()] = $mountInfo;
}
- }, $mounts);
- $newMounts = array_values(array_filter($newMounts));
- $newMountRootIds = array_map(function (ICachedMountInfo $mount) {
- return $mount->getRootId();
- }, $newMounts);
- $newMounts = array_combine($newMountRootIds, $newMounts);
+ }
$cachedMounts = $this->getMountsForUser($user);
if (is_array($mountProviderClasses)) {
$cachedMounts = array_filter($cachedMounts, function (ICachedMountInfo $mountInfo) use ($mountProviderClasses, $newMounts) {
// for existing mounts that didn't have a mount provider set
// we still want the ones that map to new mounts
- if ($mountInfo->getMountProvider() === '' && isset($newMounts[$mountInfo->getRootId()])) {
+ if ($mountInfo->getMountProvider() === '' && isset($newMounts[$mountInfo->getKey()])) {
return true;
}
return in_array($mountInfo->getMountProvider(), $mountProviderClasses);
});
}
- $cachedMountRootIds = array_map(function (ICachedMountInfo $mount) {
- return $mount->getRootId();
- }, $cachedMounts);
- $cachedMounts = array_combine($cachedMountRootIds, $cachedMounts);
$addedMounts = [];
$removedMounts = [];
- foreach ($newMounts as $rootId => $newMount) {
- if (!isset($cachedMounts[$rootId])) {
+ foreach ($newMounts as $mountKey => $newMount) {
+ if (!isset($cachedMounts[$mountKey])) {
$addedMounts[] = $newMount;
}
}
- foreach ($cachedMounts as $rootId => $cachedMount) {
- if (!isset($newMounts[$rootId])) {
+ foreach ($cachedMounts as $mountKey => $cachedMount) {
+ if (!isset($newMounts[$mountKey])) {
$removedMounts[] = $cachedMount;
}
}
$changedMounts = $this->findChangedMounts($newMounts, $cachedMounts);
- foreach ($addedMounts as $mount) {
- $this->addToCache($mount);
- /** @psalm-suppress InvalidArgument */
- $this->mountsForUsers[$user->getUID()][] = $mount;
- }
- foreach ($removedMounts as $mount) {
- $this->removeFromCache($mount);
- $index = array_search($mount, $this->mountsForUsers[$user->getUID()]);
- unset($this->mountsForUsers[$user->getUID()][$index]);
- }
- foreach ($changedMounts as $mount) {
- $this->updateCachedMount($mount);
+ if ($addedMounts || $removedMounts || $changedMounts) {
+ $this->connection->beginTransaction();
+ $userUID = $user->getUID();
+ try {
+ foreach ($addedMounts as $mount) {
+ $this->logger->debug("Adding mount '{$mount->getKey()}' for user '$userUID'", ['app' => 'files', 'mount_provider' => $mount->getMountProvider()]);
+ $this->addToCache($mount);
+ /** @psalm-suppress InvalidArgument */
+ $this->mountsForUsers[$userUID][$mount->getKey()] = $mount;
+ }
+ foreach ($removedMounts as $mount) {
+ $this->logger->debug("Removing mount '{$mount->getKey()}' for user '$userUID'", ['app' => 'files', 'mount_provider' => $mount->getMountProvider()]);
+ $this->removeFromCache($mount);
+ unset($this->mountsForUsers[$userUID][$mount->getKey()]);
+ }
+ foreach ($changedMounts as $mountPair) {
+ $newMount = $mountPair[1];
+ $this->logger->debug("Updating mount '{$newMount->getKey()}' for user '$userUID'", ['app' => 'files', 'mount_provider' => $newMount->getMountProvider()]);
+ $this->updateCachedMount($newMount);
+ /** @psalm-suppress InvalidArgument */
+ $this->mountsForUsers[$userUID][$newMount->getKey()] = $newMount;
+ }
+ $this->connection->commit();
+ } catch (\Throwable $e) {
+ $this->connection->rollBack();
+ throw $e;
+ }
+
+ // Only fire events after all mounts have already been adjusted in the database.
+ foreach ($addedMounts as $mount) {
+ $this->eventDispatcher->dispatchTyped(new UserMountAddedEvent($mount));
+ }
+ foreach ($removedMounts as $mount) {
+ $this->eventDispatcher->dispatchTyped(new UserMountRemovedEvent($mount));
+ }
+ foreach ($changedMounts as $mountPair) {
+ $this->eventDispatcher->dispatchTyped(new UserMountUpdatedEvent($mountPair[0], $mountPair[1]));
+ }
}
+ $this->eventLogger->end('fs:setup:user:register');
}
/**
- * @param ICachedMountInfo[] $newMounts
- * @param ICachedMountInfo[] $cachedMounts
- * @return ICachedMountInfo[]
+ * @param array<string, ICachedMountInfo> $newMounts
+ * @param array<string, ICachedMountInfo> $cachedMounts
+ * @return list<list{0: ICachedMountInfo, 1: ICachedMountInfo}> Pairs of old and new mounts
*/
- private function findChangedMounts(array $newMounts, array $cachedMounts) {
- $new = [];
- foreach ($newMounts as $mount) {
- $new[$mount->getRootId()] = $mount;
- }
+ private function findChangedMounts(array $newMounts, array $cachedMounts): array {
$changed = [];
- foreach ($cachedMounts as $cachedMount) {
- $rootId = $cachedMount->getRootId();
- if (isset($new[$rootId])) {
- $newMount = $new[$rootId];
+ foreach ($cachedMounts as $key => $cachedMount) {
+ if (isset($newMounts[$key])) {
+ $newMount = $newMounts[$key];
if (
- $newMount->getMountPoint() !== $cachedMount->getMountPoint() ||
- $newMount->getStorageId() !== $cachedMount->getStorageId() ||
- $newMount->getMountId() !== $cachedMount->getMountId() ||
- $newMount->getMountProvider() !== $cachedMount->getMountProvider()
+ $newMount->getStorageId() !== $cachedMount->getStorageId()
+ || $newMount->getMountId() !== $cachedMount->getMountId()
+ || $newMount->getMountProvider() !== $cachedMount->getMountProvider()
) {
- $changed[] = $newMount;
+ $changed[] = [$cachedMount, $newMount];
}
}
}
@@ -173,7 +171,7 @@ class UserMountCache implements IUserMountCache {
'mount_point' => $mount->getMountPoint(),
'mount_id' => $mount->getMountId(),
'mount_provider_class' => $mount->getMountProvider(),
- ], ['root_id', 'user_id']);
+ ], ['root_id', 'user_id', 'mount_point']);
} else {
// in some cases this is legitimate, like orphaned shares
$this->logger->debug('Could not get storage info for mount at ' . $mount->getMountPoint());
@@ -191,7 +189,7 @@ class UserMountCache implements IUserMountCache {
->where($builder->expr()->eq('user_id', $builder->createNamedParameter($mount->getUser()->getUID())))
->andWhere($builder->expr()->eq('root_id', $builder->createNamedParameter($mount->getRootId(), IQueryBuilder::PARAM_INT)));
- $query->execute();
+ $query->executeStatement();
}
private function removeFromCache(ICachedMountInfo $mount) {
@@ -199,28 +197,43 @@ class UserMountCache implements IUserMountCache {
$query = $builder->delete('mounts')
->where($builder->expr()->eq('user_id', $builder->createNamedParameter($mount->getUser()->getUID())))
- ->andWhere($builder->expr()->eq('root_id', $builder->createNamedParameter($mount->getRootId(), IQueryBuilder::PARAM_INT)));
- $query->execute();
+ ->andWhere($builder->expr()->eq('root_id', $builder->createNamedParameter($mount->getRootId(), IQueryBuilder::PARAM_INT)))
+ ->andWhere($builder->expr()->eq('mount_point', $builder->createNamedParameter($mount->getMountPoint())));
+ $query->executeStatement();
}
- private function dbRowToMountInfo(array $row) {
- $user = $this->userManager->get($row['user_id']);
- if (is_null($user)) {
- return null;
- }
+ /**
+ * @param array $row
+ * @param (callable(CachedMountInfo): string)|null $pathCallback
+ * @return CachedMountInfo
+ */
+ private function dbRowToMountInfo(array $row, ?callable $pathCallback = null): ICachedMountInfo {
+ $user = new LazyUser($row['user_id'], $this->userManager);
$mount_id = $row['mount_id'];
if (!is_null($mount_id)) {
$mount_id = (int)$mount_id;
}
- return new CachedMountInfo(
- $user,
- (int)$row['storage_id'],
- (int)$row['root_id'],
- $row['mount_point'],
- $row['mount_provider_class'] ?? '',
- $mount_id,
- isset($row['path']) ? $row['path'] : '',
- );
+ if ($pathCallback) {
+ return new LazyPathCachedMountInfo(
+ $user,
+ (int)$row['storage_id'],
+ (int)$row['root_id'],
+ $row['mount_point'],
+ $row['mount_provider_class'] ?? '',
+ $mount_id,
+ $pathCallback,
+ );
+ } else {
+ return new CachedMountInfo(
+ $user,
+ (int)$row['storage_id'],
+ (int)$row['root_id'],
+ $row['mount_point'],
+ $row['mount_provider_class'] ?? '',
+ $mount_id,
+ $row['path'] ?? '',
+ );
+ }
}
/**
@@ -228,20 +241,43 @@ class UserMountCache implements IUserMountCache {
* @return ICachedMountInfo[]
*/
public function getMountsForUser(IUser $user) {
- if (!isset($this->mountsForUsers[$user->getUID()])) {
+ $userUID = $user->getUID();
+ if (!$this->userManager->userExists($userUID)) {
+ return [];
+ }
+ if (!isset($this->mountsForUsers[$userUID])) {
$builder = $this->connection->getQueryBuilder();
- $query = $builder->select('storage_id', 'root_id', 'user_id', 'mount_point', 'mount_id', 'f.path', 'mount_provider_class')
+ $query = $builder->select('storage_id', 'root_id', 'user_id', 'mount_point', 'mount_id', 'mount_provider_class')
->from('mounts', 'm')
- ->innerJoin('m', 'filecache', 'f', $builder->expr()->eq('m.root_id', 'f.fileid'))
- ->where($builder->expr()->eq('user_id', $builder->createPositionalParameter($user->getUID())));
+ ->where($builder->expr()->eq('user_id', $builder->createNamedParameter($userUID)));
- $result = $query->execute();
+ $result = $query->executeQuery();
$rows = $result->fetchAll();
$result->closeCursor();
- $this->mountsForUsers[$user->getUID()] = array_filter(array_map([$this, 'dbRowToMountInfo'], $rows));
+ /** @var array<string, ICachedMountInfo> $mounts */
+ $mounts = [];
+ foreach ($rows as $row) {
+ $mount = $this->dbRowToMountInfo($row, [$this, 'getInternalPathForMountInfo']);
+ if ($mount !== null) {
+ $mounts[$mount->getKey()] = $mount;
+ }
+ }
+ $this->mountsForUsers[$userUID] = $mounts;
}
- return $this->mountsForUsers[$user->getUID()];
+ return $this->mountsForUsers[$userUID];
+ }
+
+ public function getInternalPathForMountInfo(CachedMountInfo $info): string {
+ $cached = $this->internalPathCache->get($info->getRootId());
+ if ($cached !== null) {
+ return $cached;
+ }
+ $builder = $this->connection->getQueryBuilder();
+ $query = $builder->select('path')
+ ->from('filecache')
+ ->where($builder->expr()->eq('fileid', $builder->createNamedParameter($info->getRootId())));
+ return $query->executeQuery()->fetchOne() ?: '';
}
/**
@@ -254,13 +290,13 @@ class UserMountCache implements IUserMountCache {
$query = $builder->select('storage_id', 'root_id', 'user_id', 'mount_point', 'mount_id', 'f.path', 'mount_provider_class')
->from('mounts', 'm')
->innerJoin('m', 'filecache', 'f', $builder->expr()->eq('m.root_id', 'f.fileid'))
- ->where($builder->expr()->eq('storage_id', $builder->createPositionalParameter($numericStorageId, IQueryBuilder::PARAM_INT)));
+ ->where($builder->expr()->eq('storage_id', $builder->createNamedParameter($numericStorageId, IQueryBuilder::PARAM_INT)));
if ($user) {
- $query->andWhere($builder->expr()->eq('user_id', $builder->createPositionalParameter($user)));
+ $query->andWhere($builder->expr()->eq('user_id', $builder->createNamedParameter($user)));
}
- $result = $query->execute();
+ $result = $query->executeQuery();
$rows = $result->fetchAll();
$result->closeCursor();
@@ -276,9 +312,9 @@ class UserMountCache implements IUserMountCache {
$query = $builder->select('storage_id', 'root_id', 'user_id', 'mount_point', 'mount_id', 'f.path', 'mount_provider_class')
->from('mounts', 'm')
->innerJoin('m', 'filecache', 'f', $builder->expr()->eq('m.root_id', 'f.fileid'))
- ->where($builder->expr()->eq('root_id', $builder->createPositionalParameter($rootFileId, IQueryBuilder::PARAM_INT)));
+ ->where($builder->expr()->eq('root_id', $builder->createNamedParameter($rootFileId, IQueryBuilder::PARAM_INT)));
- $result = $query->execute();
+ $result = $query->executeQuery();
$rows = $result->fetchAll();
$result->closeCursor();
@@ -297,7 +333,7 @@ class UserMountCache implements IUserMountCache {
->from('filecache')
->where($builder->expr()->eq('fileid', $builder->createNamedParameter($fileId, IQueryBuilder::PARAM_INT)));
- $result = $query->execute();
+ $result = $query->executeQuery();
$row = $result->fetch();
$result->closeCursor();
@@ -326,30 +362,22 @@ class UserMountCache implements IUserMountCache {
} catch (NotFoundException $e) {
return [];
}
- $builder = $this->connection->getQueryBuilder();
- $query = $builder->select('storage_id', 'root_id', 'user_id', 'mount_point', 'mount_id', 'f.path', 'mount_provider_class')
- ->from('mounts', 'm')
- ->innerJoin('m', 'filecache', 'f', $builder->expr()->eq('m.root_id', 'f.fileid'))
- ->where($builder->expr()->eq('storage_id', $builder->createPositionalParameter($storageId, IQueryBuilder::PARAM_INT)));
+ $mountsForStorage = $this->getMountsForStorageId($storageId, $user);
- if ($user) {
- $query->andWhere($builder->expr()->eq('user_id', $builder->createPositionalParameter($user)));
- }
-
- $result = $query->execute();
- $rows = $result->fetchAll();
- $result->closeCursor();
- // filter mounts that are from the same storage but a different directory
- $filteredMounts = array_filter($rows, function (array $row) use ($internalPath, $fileId) {
- if ($fileId === (int)$row['root_id']) {
+ // filter mounts that are from the same storage but not a parent of the file we care about
+ $filteredMounts = array_filter($mountsForStorage, function (ICachedMountInfo $mount) use ($internalPath, $fileId) {
+ if ($fileId === $mount->getRootId()) {
return true;
}
- $internalMountPath = $row['path'] ?? '';
+ $internalMountPath = $mount->getRootInternalPath();
- return $internalMountPath === '' || substr($internalPath, 0, strlen($internalMountPath) + 1) === $internalMountPath . '/';
+ return $internalMountPath === '' || str_starts_with($internalPath, $internalMountPath . '/');
});
- $filteredMounts = array_filter(array_map([$this, 'dbRowToMountInfo'], $filteredMounts));
+ $filteredMounts = array_values(array_filter($filteredMounts, function (ICachedMountInfo $mount) {
+ return $this->userManager->userExists($mount->getUser()->getUID());
+ }));
+
return array_map(function (ICachedMountInfo $mount) use ($internalPath) {
return new CachedMountFileInfo(
$mount->getUser(),
@@ -374,7 +402,7 @@ class UserMountCache implements IUserMountCache {
$query = $builder->delete('mounts')
->where($builder->expr()->eq('user_id', $builder->createNamedParameter($user->getUID())));
- $query->execute();
+ $query->executeStatement();
}
public function removeUserStorageMount($storageId, $userId) {
@@ -383,7 +411,7 @@ class UserMountCache implements IUserMountCache {
$query = $builder->delete('mounts')
->where($builder->expr()->eq('user_id', $builder->createNamedParameter($userId)))
->andWhere($builder->expr()->eq('storage_id', $builder->createNamedParameter($storageId, IQueryBuilder::PARAM_INT)));
- $query->execute();
+ $query->executeStatement();
}
public function remoteStorageMounts($storageId) {
@@ -391,7 +419,7 @@ class UserMountCache implements IUserMountCache {
$query = $builder->delete('mounts')
->where($builder->expr()->eq('storage_id', $builder->createNamedParameter($storageId, IQueryBuilder::PARAM_INT)));
- $query->execute();
+ $query->executeStatement();
}
/**
@@ -422,7 +450,7 @@ class UserMountCache implements IUserMountCache {
->where($builder->expr()->eq('m.mount_point', $mountPoint))
->andWhere($builder->expr()->in('m.user_id', $builder->createNamedParameter($userIds, IQueryBuilder::PARAM_STR_ARRAY)));
- $result = $query->execute();
+ $result = $query->executeQuery();
$results = [];
while ($row = $result->fetch()) {
@@ -444,7 +472,7 @@ class UserMountCache implements IUserMountCache {
}, $mounts);
$mounts = array_combine($mountPoints, $mounts);
- $current = $path;
+ $current = rtrim($path, '/');
// walk up the directory tree until we find a path that has a mountpoint set
// the loop will return if a mountpoint is found or break if none are found
while (true) {
@@ -461,14 +489,14 @@ class UserMountCache implements IUserMountCache {
}
}
- throw new NotFoundException("No cached mount for path " . $path);
+ throw new NotFoundException('No cached mount for path ' . $path);
}
public function getMountsInPath(IUser $user, string $path): array {
$path = rtrim($path, '/') . '/';
$mounts = $this->getMountsForUser($user);
return array_filter($mounts, function (ICachedMountInfo $mount) use ($path) {
- return $mount->getMountPoint() !== $path && strpos($mount->getMountPoint(), $path) === 0;
+ return $mount->getMountPoint() !== $path && str_starts_with($mount->getMountPoint(), $path);
});
}
}
diff --git a/lib/private/Files/Config/UserMountCacheListener.php b/lib/private/Files/Config/UserMountCacheListener.php
index eef91a03853..40995de8986 100644
--- a/lib/private/Files/Config/UserMountCacheListener.php
+++ b/lib/private/Files/Config/UserMountCacheListener.php
@@ -1,23 +1,9 @@
<?php
+
/**
- * @copyright Copyright (c) 2016, ownCloud, Inc.
- *
- * @author Robin Appelman <robin@icewind.nl>
- *
- * @license AGPL-3.0
- *
- * This code is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License, version 3,
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License, version 3,
- * along with this program. If not, see <http://www.gnu.org/licenses/>
- *
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
*/
namespace OC\Files\Config;