diff options
Diffstat (limited to 'lib/private/Files/Config')
-rw-r--r-- | lib/private/Files/Config/CachedMountFileInfo.php | 25 | ||||
-rw-r--r-- | lib/private/Files/Config/CachedMountInfo.php | 40 | ||||
-rw-r--r-- | lib/private/Files/Config/LazyPathCachedMountInfo.php | 48 | ||||
-rw-r--r-- | lib/private/Files/Config/LazyStorageMountInfo.php | 30 | ||||
-rw-r--r-- | lib/private/Files/Config/MountProviderCollection.php | 164 | ||||
-rw-r--r-- | lib/private/Files/Config/UserMountCache.php | 318 | ||||
-rw-r--r-- | lib/private/Files/Config/UserMountCacheListener.php | 22 |
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; |