diff options
author | Vincent Petry <vincent@nextcloud.com> | 2022-03-24 21:08:15 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-03-24 21:08:15 +0100 |
commit | a7e778b57fb81c8e62d093ece7f0689d34db4c45 (patch) | |
tree | f50492f0380bb21d0cce095231c2560f77b00d36 | |
parent | 00076c07098dbff236d133998de03bee8d4fec33 (diff) | |
parent | 91ab4e1df4f3e234c5e4941d89cf0d9f19166c18 (diff) | |
download | nextcloud-server-a7e778b57fb81c8e62d093ece7f0689d34db4c45.tar.gz nextcloud-server-a7e778b57fb81c8e62d093ece7f0689d34db4c45.zip |
Merge pull request #31265 from nextcloud/fs-limited-setup
Fine grained filesystem setup
25 files changed, 561 insertions, 74 deletions
diff --git a/apps/dav/lib/Connector/Sabre/Auth.php b/apps/dav/lib/Connector/Sabre/Auth.php index 71e833809ac..df4e3c65ce0 100644 --- a/apps/dav/lib/Connector/Sabre/Auth.php +++ b/apps/dav/lib/Connector/Sabre/Auth.php @@ -235,7 +235,6 @@ class Auth extends AbstractBasic { \OC_User::handleApacheAuth() ) { $user = $this->userSession->getUser()->getUID(); - \OC_Util::setupFS($user); $this->currentUser = $user; $this->session->close(); return [true, $this->principalPrefix . $user]; diff --git a/apps/dav/lib/Connector/Sabre/ServerFactory.php b/apps/dav/lib/Connector/Sabre/ServerFactory.php index ff96b7a19c5..095fb631c2b 100644 --- a/apps/dav/lib/Connector/Sabre/ServerFactory.php +++ b/apps/dav/lib/Connector/Sabre/ServerFactory.php @@ -31,7 +31,7 @@ */ namespace OCA\DAV\Connector\Sabre; -use OC\Files\Node\Folder; +use OCP\Files\Folder; use OCA\DAV\AppInfo\PluginManager; use OCA\DAV\Files\BrowserErrorPagePlugin; use OCP\Files\Mount\IMountManager; diff --git a/apps/files_external/lib/Service/StoragesService.php b/apps/files_external/lib/Service/StoragesService.php index b8eabd65e1e..489192dbdc2 100644 --- a/apps/files_external/lib/Service/StoragesService.php +++ b/apps/files_external/lib/Service/StoragesService.php @@ -40,7 +40,9 @@ use OCA\Files_External\Lib\Backend\InvalidBackend; use OCA\Files_External\Lib\DefinitionParameter; use OCA\Files_External\Lib\StorageConfig; use OCA\Files_External\NotFoundException; +use OCP\EventDispatcher\IEventDispatcher; use OCP\Files\Config\IUserMountCache; +use OCP\Files\Events\InvalidateMountCacheEvent; use OCP\Files\StorageNotAvailableException; use OCP\ILogger; @@ -62,15 +64,24 @@ abstract class StoragesService { */ protected $userMountCache; + protected IEventDispatcher $eventDispatcher; + /** * @param BackendService $backendService * @param DBConfigService $dbConfigService * @param IUserMountCache $userMountCache + * @param IEventDispatcher $eventDispatcher */ - public function __construct(BackendService $backendService, DBConfigService $dbConfigService, IUserMountCache $userMountCache) { + public function __construct( + BackendService $backendService, + DBConfigService $dbConfigService, + IUserMountCache $userMountCache, + IEventDispatcher $eventDispatcher + ) { $this->backendService = $backendService; $this->dbConfig = $dbConfigService; $this->userMountCache = $userMountCache; + $this->eventDispatcher = $eventDispatcher; } protected function readDBConfig() { @@ -338,7 +349,8 @@ abstract class StoragesService { * @param string $mountType hook mount type param * @param array $applicableArray array of applicable users/groups for which to trigger the hook */ - protected function triggerApplicableHooks($signal, $mountPoint, $mountType, $applicableArray) { + protected function triggerApplicableHooks($signal, $mountPoint, $mountType, $applicableArray): void { + $this->eventDispatcher->dispatchTyped(new InvalidateMountCacheEvent(null)); foreach ($applicableArray as $applicable) { \OCP\Util::emitHook( Filesystem::CLASSNAME, diff --git a/apps/files_external/lib/Service/UserGlobalStoragesService.php b/apps/files_external/lib/Service/UserGlobalStoragesService.php index ba894d8f210..2eda36e9242 100644 --- a/apps/files_external/lib/Service/UserGlobalStoragesService.php +++ b/apps/files_external/lib/Service/UserGlobalStoragesService.php @@ -24,6 +24,7 @@ namespace OCA\Files_External\Service; use OCA\Files_External\Lib\StorageConfig; +use OCP\EventDispatcher\IEventDispatcher; use OCP\Files\Config\IUserMountCache; use OCP\IGroupManager; use OCP\IUser; @@ -45,15 +46,17 @@ class UserGlobalStoragesService extends GlobalStoragesService { * @param IUserSession $userSession * @param IGroupManager $groupManager * @param IUserMountCache $userMountCache + * @param IEventDispatcher $eventDispatcher */ public function __construct( BackendService $backendService, DBConfigService $dbConfig, IUserSession $userSession, IGroupManager $groupManager, - IUserMountCache $userMountCache + IUserMountCache $userMountCache, + IEventDispatcher $eventDispatcher ) { - parent::__construct($backendService, $dbConfig, $userMountCache); + parent::__construct($backendService, $dbConfig, $userMountCache, $eventDispatcher); $this->userSession = $userSession; $this->groupManager = $groupManager; } diff --git a/apps/files_external/lib/Service/UserStoragesService.php b/apps/files_external/lib/Service/UserStoragesService.php index 8af6bdb3a77..b09b37b40cc 100644 --- a/apps/files_external/lib/Service/UserStoragesService.php +++ b/apps/files_external/lib/Service/UserStoragesService.php @@ -32,6 +32,7 @@ use OC\Files\Filesystem; use OCA\Files_External\Lib\StorageConfig; use OCA\Files_External\NotFoundException; +use OCP\EventDispatcher\IEventDispatcher; use OCP\Files\Config\IUserMountCache; use OCP\IUserSession; @@ -49,15 +50,17 @@ class UserStoragesService extends StoragesService { * @param DBConfigService $dbConfig * @param IUserSession $userSession user session * @param IUserMountCache $userMountCache + * @param IEventDispatcher $eventDispatcher */ public function __construct( BackendService $backendService, DBConfigService $dbConfig, IUserSession $userSession, - IUserMountCache $userMountCache + IUserMountCache $userMountCache, + IEventDispatcher $eventDispatcher ) { $this->userSession = $userSession; - parent::__construct($backendService, $dbConfig, $userMountCache); + parent::__construct($backendService, $dbConfig, $userMountCache, $eventDispatcher); } protected function readDBConfig() { diff --git a/apps/files_external/tests/Service/GlobalStoragesServiceTest.php b/apps/files_external/tests/Service/GlobalStoragesServiceTest.php index b23c4b8f2bf..7d77ea971f3 100644 --- a/apps/files_external/tests/Service/GlobalStoragesServiceTest.php +++ b/apps/files_external/tests/Service/GlobalStoragesServiceTest.php @@ -37,7 +37,7 @@ use OCA\Files_External\Service\GlobalStoragesService; class GlobalStoragesServiceTest extends StoragesServiceTest { protected function setUp(): void { parent::setUp(); - $this->service = new GlobalStoragesService($this->backendService, $this->dbConfig, $this->mountCache); + $this->service = new GlobalStoragesService($this->backendService, $this->dbConfig, $this->mountCache, $this->eventDispatcher); } protected function tearDown(): void { diff --git a/apps/files_external/tests/Service/StoragesServiceTest.php b/apps/files_external/tests/Service/StoragesServiceTest.php index 3829a9ea0ce..4eaf70a8e84 100644 --- a/apps/files_external/tests/Service/StoragesServiceTest.php +++ b/apps/files_external/tests/Service/StoragesServiceTest.php @@ -39,6 +39,7 @@ use OCA\Files_External\Service\BackendService; use OCA\Files_External\Service\DBConfigService; use OCA\Files_External\Service\StoragesService; use OCP\AppFramework\IAppContainer; +use OCP\EventDispatcher\IEventDispatcher; use OCP\Files\Cache\ICache; use OCP\Files\Config\IUserMountCache; use OCP\Files\Mount\IMountPoint; @@ -96,6 +97,11 @@ abstract class StoragesServiceTest extends \Test\TestCase { */ protected $mountCache; + /** + * @var \PHPUnit\Framework\MockObject\MockObject|IEventDispatcher + */ + protected IEventDispatcher $eventDispatcher; + protected function setUp(): void { parent::setUp(); $this->dbConfig = new CleaningDBConfig(\OC::$server->getDatabaseConnection(), \OC::$server->getCrypto()); @@ -108,6 +114,7 @@ abstract class StoragesServiceTest extends \Test\TestCase { \OCA\Files_External\MountConfig::$skipTest = true; $this->mountCache = $this->createMock(IUserMountCache::class); + $this->eventDispatcher = $this->createMock(IEventDispatcher::class); // prepare BackendService mock $this->backendService = diff --git a/apps/files_external/tests/Service/UserGlobalStoragesServiceTest.php b/apps/files_external/tests/Service/UserGlobalStoragesServiceTest.php index ea77148c8f2..aa5aa1df431 100644 --- a/apps/files_external/tests/Service/UserGlobalStoragesServiceTest.php +++ b/apps/files_external/tests/Service/UserGlobalStoragesServiceTest.php @@ -100,7 +100,8 @@ class UserGlobalStoragesServiceTest extends GlobalStoragesServiceTest { $this->dbConfig, $userSession, $this->groupManager, - $this->mountCache + $this->mountCache, + $this->eventDispatcher, ); } diff --git a/apps/files_external/tests/Service/UserStoragesServiceTest.php b/apps/files_external/tests/Service/UserStoragesServiceTest.php index ff39ea9ddbc..cda1dd0a27f 100644 --- a/apps/files_external/tests/Service/UserStoragesServiceTest.php +++ b/apps/files_external/tests/Service/UserStoragesServiceTest.php @@ -54,7 +54,7 @@ class UserStoragesServiceTest extends StoragesServiceTest { protected function setUp(): void { parent::setUp(); - $this->globalStoragesService = new GlobalStoragesService($this->backendService, $this->dbConfig, $this->mountCache); + $this->globalStoragesService = new GlobalStoragesService($this->backendService, $this->dbConfig, $this->mountCache, $this->eventDispatcher); $this->userId = $this->getUniqueID('user_'); $this->createUser($this->userId, $this->userId); @@ -67,7 +67,7 @@ class UserStoragesServiceTest extends StoragesServiceTest { ->method('getUser') ->willReturn($this->user); - $this->service = new UserStoragesService($this->backendService, $this->dbConfig, $userSession, $this->mountCache); + $this->service = new UserStoragesService($this->backendService, $this->dbConfig, $userSession, $this->mountCache, $this->eventDispatcher); } private function makeTestStorageData() { diff --git a/apps/files_sharing/lib/MountProvider.php b/apps/files_sharing/lib/MountProvider.php index 102e5d96559..27edf5074e1 100644 --- a/apps/files_sharing/lib/MountProvider.php +++ b/apps/files_sharing/lib/MountProvider.php @@ -134,7 +134,9 @@ class MountProvider implements IMountProvider { ], $loader, $view, - $foldersExistCache + $foldersExistCache, + $this->eventDispatcher, + $user ); $event = new ShareMountedEvent($mount); diff --git a/apps/files_sharing/lib/SharedMount.php b/apps/files_sharing/lib/SharedMount.php index c8f5d0f64ae..60361e25fd0 100644 --- a/apps/files_sharing/lib/SharedMount.php +++ b/apps/files_sharing/lib/SharedMount.php @@ -34,7 +34,9 @@ use OC\Files\Mount\MountPoint; use OC\Files\Mount\MoveableMount; use OC\Files\View; use OCP\EventDispatcher\IEventDispatcher; +use OCP\Files\Events\InvalidateMountCacheEvent; use OCP\Files\Storage\IStorageFactory; +use OCP\IUser; use OCP\Share\Events\VerifyMountPointEvent; /** @@ -51,10 +53,7 @@ class SharedMount extends MountPoint implements MoveableMount { */ private $recipientView; - /** - * @var string - */ - private $user; + private IUser $user; /** @var \OCP\Share\IShare */ private $superShare; @@ -62,22 +61,27 @@ class SharedMount extends MountPoint implements MoveableMount { /** @var \OCP\Share\IShare[] */ private $groupedShares; - /** - * @param string $storage - * @param SharedMount[] $mountpoints - * @param array $arguments - * @param IStorageFactory $loader - * @param View $recipientView - */ - public function __construct($storage, array $mountpoints, $arguments, IStorageFactory $loader, View $recipientView, CappedMemoryCache $folderExistCache) { - $this->user = $arguments['user']; + private IEventDispatcher $eventDispatcher; + + public function __construct( + $storage, + array $mountpoints, + $arguments, + IStorageFactory $loader, + View $recipientView, + CappedMemoryCache $folderExistCache, + IEventDispatcher $eventDispatcher, + IUser $user + ) { + $this->user = $user; $this->recipientView = $recipientView; + $this->eventDispatcher = $eventDispatcher; $this->superShare = $arguments['superShare']; $this->groupedShares = $arguments['groupedShares']; $newMountPoint = $this->verifyMountPoint($this->superShare, $mountpoints, $folderExistCache); - $absMountPoint = '/' . $this->user . '/files' . $newMountPoint; + $absMountPoint = '/' . $user->getUID() . '/files' . $newMountPoint; parent::__construct($storage, $absMountPoint, $arguments, $loader, null, null, MountProvider::class); } @@ -93,9 +97,7 @@ class SharedMount extends MountPoint implements MoveableMount { $parent = dirname($share->getTarget()); $event = new VerifyMountPointEvent($share, $this->recipientView, $parent); - /** @var IEventDispatcher $dispatcher */ - $dispatcher = \OC::$server->query(IEventDispatcher::class); - $dispatcher->dispatchTyped($event); + $this->eventDispatcher->dispatchTyped($event); $parent = $event->getParent(); if ($folderExistCache->hasKey($parent)) { @@ -105,7 +107,7 @@ class SharedMount extends MountPoint implements MoveableMount { $folderExistCache->set($parent, $parentExists); } if (!$parentExists) { - $parent = Helper::getShareFolder($this->recipientView, $this->user); + $parent = Helper::getShareFolder($this->recipientView, $this->user->getUID()); } $newMountPoint = $this->generateUniqueTarget( @@ -133,8 +135,10 @@ class SharedMount extends MountPoint implements MoveableMount { foreach ($this->groupedShares as $tmpShare) { $tmpShare->setTarget($newPath); - \OC::$server->getShareManager()->moveShare($tmpShare, $this->user); + \OC::$server->getShareManager()->moveShare($tmpShare, $this->user->getUID()); } + + $this->eventDispatcher->dispatchTyped(new InvalidateMountCacheEvent($this->user)); } diff --git a/lib/composer/composer/autoload_classmap.php b/lib/composer/composer/autoload_classmap.php index feaa60a4ca6..79035f2e42a 100644 --- a/lib/composer/composer/autoload_classmap.php +++ b/lib/composer/composer/autoload_classmap.php @@ -260,6 +260,7 @@ return array( 'OCP\\Files\\Events\\FileCacheUpdated' => $baseDir . '/lib/public/Files/Events/FileCacheUpdated.php', 'OCP\\Files\\Events\\FileScannedEvent' => $baseDir . '/lib/public/Files/Events/FileScannedEvent.php', 'OCP\\Files\\Events\\FolderScannedEvent' => $baseDir . '/lib/public/Files/Events/FolderScannedEvent.php', + 'OCP\\Files\\Events\\InvalidateMountCacheEvent' => $baseDir . '/lib/public/Files/Events/InvalidateMountCacheEvent.php', 'OCP\\Files\\Events\\NodeAddedToCache' => $baseDir . '/lib/public/Files/Events/NodeAddedToCache.php', 'OCP\\Files\\Events\\NodeRemovedFromCache' => $baseDir . '/lib/public/Files/Events/NodeRemovedFromCache.php', 'OCP\\Files\\Events\\Node\\AbstractNodeEvent' => $baseDir . '/lib/public/Files/Events/Node/AbstractNodeEvent.php', @@ -1141,6 +1142,7 @@ return array( 'OC\\Files\\Node\\HookConnector' => $baseDir . '/lib/private/Files/Node/HookConnector.php', 'OC\\Files\\Node\\LazyFolder' => $baseDir . '/lib/private/Files/Node/LazyFolder.php', 'OC\\Files\\Node\\LazyRoot' => $baseDir . '/lib/private/Files/Node/LazyRoot.php', + 'OC\\Files\\Node\\LazyUserFolder' => $baseDir . '/lib/private/Files/Node/LazyUserFolder.php', 'OC\\Files\\Node\\Node' => $baseDir . '/lib/private/Files/Node/Node.php', 'OC\\Files\\Node\\NonExistingFile' => $baseDir . '/lib/private/Files/Node/NonExistingFile.php', 'OC\\Files\\Node\\NonExistingFolder' => $baseDir . '/lib/private/Files/Node/NonExistingFolder.php', diff --git a/lib/composer/composer/autoload_static.php b/lib/composer/composer/autoload_static.php index 795742f87f8..4a4c886a082 100644 --- a/lib/composer/composer/autoload_static.php +++ b/lib/composer/composer/autoload_static.php @@ -289,6 +289,7 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c 'OCP\\Files\\Events\\FileCacheUpdated' => __DIR__ . '/../../..' . '/lib/public/Files/Events/FileCacheUpdated.php', 'OCP\\Files\\Events\\FileScannedEvent' => __DIR__ . '/../../..' . '/lib/public/Files/Events/FileScannedEvent.php', 'OCP\\Files\\Events\\FolderScannedEvent' => __DIR__ . '/../../..' . '/lib/public/Files/Events/FolderScannedEvent.php', + 'OCP\\Files\\Events\\InvalidateMountCacheEvent' => __DIR__ . '/../../..' . '/lib/public/Files/Events/InvalidateMountCacheEvent.php', 'OCP\\Files\\Events\\NodeAddedToCache' => __DIR__ . '/../../..' . '/lib/public/Files/Events/NodeAddedToCache.php', 'OCP\\Files\\Events\\NodeRemovedFromCache' => __DIR__ . '/../../..' . '/lib/public/Files/Events/NodeRemovedFromCache.php', 'OCP\\Files\\Events\\Node\\AbstractNodeEvent' => __DIR__ . '/../../..' . '/lib/public/Files/Events/Node/AbstractNodeEvent.php', @@ -1170,6 +1171,7 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c 'OC\\Files\\Node\\HookConnector' => __DIR__ . '/../../..' . '/lib/private/Files/Node/HookConnector.php', 'OC\\Files\\Node\\LazyFolder' => __DIR__ . '/../../..' . '/lib/private/Files/Node/LazyFolder.php', 'OC\\Files\\Node\\LazyRoot' => __DIR__ . '/../../..' . '/lib/private/Files/Node/LazyRoot.php', + 'OC\\Files\\Node\\LazyUserFolder' => __DIR__ . '/../../..' . '/lib/private/Files/Node/LazyUserFolder.php', 'OC\\Files\\Node\\Node' => __DIR__ . '/../../..' . '/lib/private/Files/Node/Node.php', 'OC\\Files\\Node\\NonExistingFile' => __DIR__ . '/../../..' . '/lib/private/Files/Node/NonExistingFile.php', 'OC\\Files\\Node\\NonExistingFolder' => __DIR__ . '/../../..' . '/lib/private/Files/Node/NonExistingFolder.php', diff --git a/lib/private/Files/Config/MountProviderCollection.php b/lib/private/Files/Config/MountProviderCollection.php index cd8a2a2e29f..2b0acf7d69d 100644 --- a/lib/private/Files/Config/MountProviderCollection.php +++ b/lib/private/Files/Config/MountProviderCollection.php @@ -75,16 +75,15 @@ class MountProviderCollection implements IMountProviderCollection, Emitter { } /** - * Get all configured mount points for the user - * - * @param \OCP\IUser $user - * @return \OCP\Files\Mount\IMountPoint[] + * @param IUser $user + * @param IMountProvider[] $providers + * @return IMountPoint[] */ - public function getMountsForUser(IUser $user) { + 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); - }, $this->providers); + }, $providers); $mounts = array_filter($mounts, function ($result) { return is_array($result); }); @@ -94,14 +93,31 @@ class MountProviderCollection implements IMountProviderCollection, Emitter { return $this->filterMounts($user, $mounts); } - public function addMountForUser(IUser $user, IMountManager $mountManager) { + public function getMountsForUser(IUser $user): array { + return $this->getUserMountsForProviders($user, $this->providers); + } + + public function getUserMountsForProviderClass(IUser $user, string $mountProviderClass): array { + $providers = array_filter( + $this->providers, + fn (IMountProvider $mountProvider) => (get_class($mountProvider) === $mountProviderClass) + ); + return $this->getUserMountsForProviders($user, $providers); + } + + public function addMountForUser(IUser $user, IMountManager $mountManager, callable $providerFilter = null) { // shared mount provider gets to go last since it needs to know existing files // to check for name collisions $firstMounts = []; - $firstProviders = array_filter($this->providers, function (IMountProvider $provider) { + if ($providerFilter) { + $providers = array_filter($this->providers, $providerFilter); + } else { + $providers = $this->providers; + } + $firstProviders = array_filter($providers, function (IMountProvider $provider) { return (get_class($provider) !== 'OCA\Files_Sharing\MountProvider'); }); - $lastProviders = array_filter($this->providers, function (IMountProvider $provider) { + $lastProviders = array_filter($providers, function (IMountProvider $provider) { return (get_class($provider) === 'OCA\Files_Sharing\MountProvider'); }); foreach ($firstProviders as $provider) { diff --git a/lib/private/Files/Config/UserMountCache.php b/lib/private/Files/Config/UserMountCache.php index dc2640361e7..9a5eddc4878 100644 --- a/lib/private/Files/Config/UserMountCache.php +++ b/lib/private/Files/Config/UserMountCache.php @@ -89,7 +89,7 @@ class UserMountCache implements IUserMountCache { $this->mountsForUsers = new CappedMemoryCache(); } - public function registerMounts(IUser $user, array $mounts) { + 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(); @@ -110,6 +110,11 @@ class UserMountCache implements IUserMountCache { $newMounts = array_combine($newMountRootIds, $newMounts); $cachedMounts = $this->getMountsForUser($user); + if (is_array($mountProviderClasses)) { + $cachedMounts = array_filter($cachedMounts, function (ICachedMountInfo $mountInfo) use ($mountProviderClasses) { + return in_array($mountInfo->getMountProvider(), $mountProviderClasses); + }); + } $cachedMountRootIds = array_map(function (ICachedMountInfo $mount) { return $mount->getRootId(); }, $cachedMounts); @@ -446,4 +451,39 @@ class UserMountCache implements IUserMountCache { $this->cacheInfoCache = new CappedMemoryCache(); $this->mountsForUsers = new CappedMemoryCache(); } + + public function getMountForPath(IUser $user, string $path): ICachedMountInfo { + $mounts = $this->getMountsForUser($user); + $mountPoints = array_map(function (ICachedMountInfo $mount) { + return $mount->getMountPoint(); + }, $mounts); + $mounts = array_combine($mountPoints, $mounts); + + $current = $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) { + $mountPoint = $current . '/'; + if (isset($mounts[$mountPoint])) { + return $mounts[$mountPoint]; + } elseif ($current === '') { + break; + } + + $current = dirname($current); + if ($current === '.' || $current === '/') { + $current = ''; + } + } + + 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; + }); + } } diff --git a/lib/private/Files/Filesystem.php b/lib/private/Files/Filesystem.php index 9db9252037f..20b44e2736a 100644 --- a/lib/private/Files/Filesystem.php +++ b/lib/private/Files/Filesystem.php @@ -46,6 +46,7 @@ use OCP\Files\NotFoundException; use OCP\Files\Storage\IStorageFactory; use OCP\IUser; use OCP\IUserManager; +use OCP\IUserSession; class Filesystem { @@ -324,6 +325,18 @@ class Filesystem { if (self::$defaultInstance) { return false; } + self::initInternal($root); + + //load custom mount config + self::initMountPoints($user); + + return true; + } + + public static function initInternal($root) { + if (self::$defaultInstance) { + return false; + } self::getLoader(); self::$defaultInstance = new View($root); /** @var IEventDispatcher $eventDispatcher */ @@ -338,9 +351,6 @@ class Filesystem { self::$mounts = \OC::$server->getMountManager(); } - //load custom mount config - self::initMountPoints($user); - self::$loaded = true; return true; @@ -378,6 +388,15 @@ class Filesystem { * @return View */ public static function getView() { + if (!self::$defaultInstance) { + /** @var IUserSession $session */ + $session = \OC::$server->get(IUserSession::class); + $user = $session->getUser(); + if ($user) { + $userDir = '/' . $user->getUID() . '/files'; + self::initInternal($userDir); + } + } return self::$defaultInstance; } @@ -736,7 +755,7 @@ class Filesystem { * @return \OC\Files\FileInfo|false False if file does not exist */ public static function getFileInfo($path, $includeMountPoints = true) { - return self::$defaultInstance->getFileInfo($path, $includeMountPoints); + return self::getView()->getFileInfo($path, $includeMountPoints); } /** diff --git a/lib/private/Files/Mount/Manager.php b/lib/private/Files/Mount/Manager.php index 66832690363..ecd97760f17 100644 --- a/lib/private/Files/Mount/Manager.php +++ b/lib/private/Files/Mount/Manager.php @@ -125,7 +125,7 @@ class Manager implements IMountManager { * @return IMountPoint[] */ public function findIn(string $path): array { - $this->setupManager->setupForPath($path); + $this->setupManager->setupForPath($path, true); $path = $this->formatPath($path); if (isset($this->inPathCache[$path])) { diff --git a/lib/private/Files/Node/LazyFolder.php b/lib/private/Files/Node/LazyFolder.php index bfdaeeccff7..45451e5c53c 100644 --- a/lib/private/Files/Node/LazyFolder.php +++ b/lib/private/Files/Node/LazyFolder.php @@ -25,6 +25,8 @@ declare(strict_types=1); */ namespace OC\Files\Node; +use OCP\Constants; + /** * Class LazyFolder * @@ -40,13 +42,16 @@ class LazyFolder implements \OCP\Files\Folder { /** @var LazyFolder | null */ protected $folder = null; + protected array $data; + /** * LazyFolder constructor. * * @param \Closure $folderClosure */ - public function __construct(\Closure $folderClosure) { + public function __construct(\Closure $folderClosure, array $data = []) { $this->folderClosure = $folderClosure; + $this->data = $data; } /** @@ -181,6 +186,9 @@ class LazyFolder implements \OCP\Files\Folder { * @inheritDoc */ public function getPath() { + if (isset($this->data['path'])) { + return $this->data['path']; + } return $this->__call(__FUNCTION__, func_get_args()); } @@ -230,6 +238,9 @@ class LazyFolder implements \OCP\Files\Folder { * @inheritDoc */ public function getPermissions() { + if (isset($this->data['permissions'])) { + return $this->data['permissions']; + } return $this->__call(__FUNCTION__, func_get_args()); } @@ -237,6 +248,9 @@ class LazyFolder implements \OCP\Files\Folder { * @inheritDoc */ public function isReadable() { + if (isset($this->data['permissions'])) { + return ($this->data['permissions'] & Constants::PERMISSION_READ) == Constants::PERMISSION_READ; + } return $this->__call(__FUNCTION__, func_get_args()); } @@ -244,6 +258,9 @@ class LazyFolder implements \OCP\Files\Folder { * @inheritDoc */ public function isUpdateable() { + if (isset($this->data['permissions'])) { + return ($this->data['permissions'] & Constants::PERMISSION_UPDATE) == Constants::PERMISSION_UPDATE; + } return $this->__call(__FUNCTION__, func_get_args()); } @@ -251,6 +268,9 @@ class LazyFolder implements \OCP\Files\Folder { * @inheritDoc */ public function isDeletable() { + if (isset($this->data['permissions'])) { + return ($this->data['permissions'] & Constants::PERMISSION_DELETE) == Constants::PERMISSION_DELETE; + } return $this->__call(__FUNCTION__, func_get_args()); } @@ -258,6 +278,9 @@ class LazyFolder implements \OCP\Files\Folder { * @inheritDoc */ public function isShareable() { + if (isset($this->data['permissions'])) { + return ($this->data['permissions'] & Constants::PERMISSION_SHARE) == Constants::PERMISSION_SHARE; + } return $this->__call(__FUNCTION__, func_get_args()); } diff --git a/lib/private/Files/Node/LazyUserFolder.php b/lib/private/Files/Node/LazyUserFolder.php new file mode 100644 index 00000000000..4c9e89ce233 --- /dev/null +++ b/lib/private/Files/Node/LazyUserFolder.php @@ -0,0 +1,56 @@ +<?php + +declare(strict_types=1); +/** + * @copyright Copyright (c) 2022 Robin Appelman <robin@icewind.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/>. + * + */ + +namespace OC\Files\Node; + +use OCP\Constants; +use OCP\Files\IRootFolder; +use OCP\Files\NotFoundException; +use OCP\IUser; + +class LazyUserFolder extends LazyFolder { + private IRootFolder $rootFolder; + private IUser $user; + + public function __construct(IRootFolder $rootFolder, IUser $user) { + $this->rootFolder = $rootFolder; + $this->user = $user; + parent::__construct(function () use ($user) { + try { + return $this->rootFolder->get('/' . $user->getUID() . '/files'); + } catch (NotFoundException $e) { + if (!$this->rootFolder->nodeExists('/' . $user->getUID())) { + $this->rootFolder->newFolder('/' . $user->getUID()); + } + return $this->rootFolder->newFolder('/' . $user->getUID() . '/files'); + } + }, [ + 'path' => '/' . $user->getUID() . '/files', + 'permissions' => Constants::PERMISSION_ALL, + ]); + } + + public function get($path) { + return $this->rootFolder->get('/' . $this->user->getUID() . '/files/' . rtrim($path, '/')); + } +} diff --git a/lib/private/Files/Node/Root.php b/lib/private/Files/Node/Root.php index 88ac4a31d34..53162737b6f 100644 --- a/lib/private/Files/Node/Root.php +++ b/lib/private/Files/Node/Root.php @@ -380,15 +380,20 @@ class Root extends Folder implements IRootFolder { $userId = $userObject->getUID(); if (!$this->userFolderCache->hasKey($userId)) { - \OC\Files\Filesystem::initMountPoints($userId); - - try { - $folder = $this->get('/' . $userId . '/files'); - } catch (NotFoundException $e) { - if (!$this->nodeExists('/' . $userId)) { - $this->newFolder('/' . $userId); + if ($this->mountManager->getSetupManager()->isSetupComplete($userObject)) { + try { + $folder = $this->get('/' . $userId . '/files'); + if (!$folder instanceof \OCP\Files\Folder) { + throw new \Exception("User folder for $userId exists as a file"); + } + } catch (NotFoundException $e) { + if (!$this->nodeExists('/' . $userId)) { + $this->newFolder('/' . $userId); + } + $folder = $this->newFolder('/' . $userId . '/files'); } - $folder = $this->newFolder('/' . $userId . '/files'); + } else { + $folder = new LazyUserFolder($this, $userObject); } $this->userFolderCache->set($userId, $folder); diff --git a/lib/private/Files/SetupManager.php b/lib/private/Files/SetupManager.php index 9726fbef428..da50983da32 100644 --- a/lib/private/Files/SetupManager.php +++ b/lib/private/Files/SetupManager.php @@ -42,14 +42,23 @@ use OCP\Diagnostics\IEventLogger; use OCP\EventDispatcher\IEventDispatcher; use OCP\Files\Config\IMountProvider; use OCP\Files\Config\IUserMountCache; +use OCP\Files\Events\InvalidateMountCacheEvent; use OCP\Files\Events\Node\FilesystemTornDownEvent; use OCP\Files\Mount\IMountManager; use OCP\Files\Mount\IMountPoint; +use OCP\Files\NotFoundException; use OCP\Files\Storage\IStorage; +use OCP\Group\Events\UserAddedEvent; +use OCP\Group\Events\UserRemovedEvent; +use OCP\ICache; +use OCP\ICacheFactory; +use OCP\IConfig; use OCP\IUser; use OCP\IUserManager; use OCP\IUserSession; use OCP\Lockdown\ILockdownManager; +use OCP\Share\Events\ShareCreatedEvent; +use Psr\Log\LoggerInterface; class SetupManager { private bool $rootSetup = false; @@ -57,11 +66,19 @@ class SetupManager { private MountProviderCollection $mountProviderCollection; private IMountManager $mountManager; private IUserManager $userManager; + // List of users for which at least one mount is setup private array $setupUsers = []; + // List of users for which all mounts are setup + private array $setupUsersComplete = []; + /** @var array<string, string[]> */ + private array $setupUserMountProviders = []; private IEventDispatcher $eventDispatcher; private IUserMountCache $userMountCache; private ILockdownManager $lockdownManager; private IUserSession $userSession; + private ICache $cache; + private LoggerInterface $logger; + private IConfig $config; private bool $listeningForProviders; public function __construct( @@ -72,7 +89,10 @@ class SetupManager { IEventDispatcher $eventDispatcher, IUserMountCache $userMountCache, ILockdownManager $lockdownManager, - IUserSession $userSession + IUserSession $userSession, + ICacheFactory $cacheFactory, + LoggerInterface $logger, + IConfig $config ) { $this->eventLogger = $eventLogger; $this->mountProviderCollection = $mountProviderCollection; @@ -81,8 +101,21 @@ class SetupManager { $this->eventDispatcher = $eventDispatcher; $this->userMountCache = $userMountCache; $this->lockdownManager = $lockdownManager; + $this->logger = $logger; $this->userSession = $userSession; + $this->cache = $cacheFactory->createDistributed('setupmanager::'); $this->listeningForProviders = false; + $this->config = $config; + + $this->setupListeners(); + } + + private function isSetupStarted(IUser $user): bool { + return in_array($user->getUID(), $this->setupUsers, true); + } + + public function isSetupComplete(IUser $user): bool { + return in_array($user->getUID(), $this->setupUsersComplete, true); } private function setupBuiltinWrappers() { @@ -159,15 +192,33 @@ class SetupManager { * Setup the full filesystem for the specified user */ public function setupForUser(IUser $user): void { - $this->setupRoot(); + if ($this->isSetupComplete($user)) { + return; + } + $this->setupUsersComplete[] = $user->getUID(); + + if (!isset($this->setupUserMountProviders[$user->getUID()])) { + $this->setupUserMountProviders[$user->getUID()] = []; + } + $this->setupForUserWith($user, function () use ($user) { + $this->mountProviderCollection->addMountForUser($user, $this->mountManager, function ( + IMountProvider $provider + ) use ($user) { + return !in_array(get_class($provider), $this->setupUserMountProviders[$user->getUID()]); + }); + }); + $this->afterUserFullySetup($user); + } + + /** + * part of the user setup that is run only once per user + */ + private function oneTimeUserSetup(IUser $user) { if (in_array($user->getUID(), $this->setupUsers, true)) { return; } $this->setupUsers[] = $user->getUID(); - - $this->eventLogger->start('setup_fs', 'Setup filesystem'); - $prevLogging = Filesystem::logWarningWhenAddingStorageWrapper(false); OC_Hook::emit('OC_Filesystem', 'preSetup', ['user' => $user->getUID()]); @@ -176,7 +227,7 @@ class SetupManager { $userDir = '/' . $user->getUID() . '/files'; - Filesystem::init($user, $userDir); + Filesystem::initInternal($userDir); if ($this->lockdownManager->canAccessFilesystem()) { // home mounts are handled separate since we need to ensure this is mounted before we call the other mount providers @@ -187,13 +238,6 @@ class SetupManager { $homeMount->getStorage()->mkdir(''); $homeMount->getStorage()->getScanner()->scan(''); } - - // Chance to mount for other storages - $mounts = $this->mountProviderCollection->addMountForUser($user, $this->mountManager); - $mounts[] = $homeMount; - $this->userMountCache->registerMounts($user, $mounts); - - $this->listenForNewMountProviders(); } else { $this->mountManager->addMount(new MountPoint( new NullStorage([]), @@ -203,9 +247,46 @@ class SetupManager { new NullStorage([]), '/' . $user->getUID() . '/files' )); + $this->setupUsersComplete[] = $user->getUID(); + } + + $this->listenForNewMountProviders(); + } + + /** + * Final housekeeping after a user has been fully setup + */ + private function afterUserFullySetup(IUser $user): void { + $userRoot = '/' . $user->getUID() . '/'; + $mounts = $this->mountManager->getAll(); + $mounts = array_filter($mounts, function (IMountPoint $mount) use ($userRoot) { + return strpos($mount->getMountPoint(), $userRoot) === 0; + }); + $this->userMountCache->registerMounts($user, $mounts); + } + + /** + * @param IUser $user + * @param IMountPoint $mounts + * @return void + * @throws \OCP\HintException + * @throws \OC\ServerNotAvailableException + */ + private function setupForUserWith(IUser $user, callable $mountCallback): void { + $this->setupRoot(); + + if (!$this->isSetupStarted($user)) { + $this->oneTimeUserSetup($user); + } + + $this->eventLogger->start('setup_fs', 'Setup filesystem'); + + if ($this->lockdownManager->canAccessFilesystem()) { + $mountCallback(); } \OC_Hook::emit('OC_Filesystem', 'post_initMountPoints', ['user' => $user->getUID()]); + $userDir = '/' . $user->getUID() . '/files'; OC_Hook::emit('OC_Filesystem', 'setup', ['user' => $user->getUID(), 'user_dir' => $userDir]); $this->eventLogger->end('setup_fs'); @@ -242,7 +323,7 @@ class SetupManager { /** * Set up the filesystem for the specified path */ - public function setupForPath(string $path): void { + public function setupForPath(string $path, bool $includeChildren = false): void { if (substr_count($path, '/') < 2) { if ($user = $this->userSession->getUser()) { $this->setupForUser($user); @@ -264,11 +345,81 @@ class SetupManager { return; } - $this->setupForUser($user); + if ($this->isSetupComplete($user)) { + return; + } + + // we perform a "cached" setup only after having done the full setup recently + // this is also used to trigger a full setup after handling events that are likely + // to change the available mounts + $cachedSetup = $this->cache->get($user->getUID()); + if (!$cachedSetup) { + $this->setupForUser($user); + + $cacheDuration = $this->config->getSystemValueInt('fs_mount_cache_duration', 5 * 60); + if ($cacheDuration > 0) { + $this->cache->set($user->getUID(), true, $cacheDuration); + } + return; + } + + if (!isset($this->setupUserMountProviders[$user->getUID()])) { + $this->setupUserMountProviders[$user->getUID()] = []; + } + $setupProviders = &$this->setupUserMountProviders[$user->getUID()]; + $currentProviders = []; + + try { + $cachedMount = $this->userMountCache->getMountForPath($user, $path); + } catch (NotFoundException $e) { + $this->setupForUser($user); + return; + } + + $mounts = []; + if (!in_array($cachedMount->getMountProvider(), $setupProviders)) { + $setupProviders[] = $cachedMount->getMountProvider(); + $currentProviders[] = $cachedMount->getMountProvider(); + if ($cachedMount->getMountProvider()) { + $mounts = $this->mountProviderCollection->getUserMountsForProviderClass($user, $cachedMount->getMountProvider()); + } else { + $this->logger->debug("mount at " . $cachedMount->getMountPoint() . " has no provider set, performing full setup"); + $this->setupForUser($user); + return; + } + } + + if ($includeChildren) { + $subCachedMounts = $this->userMountCache->getMountsInPath($user, $path); + foreach ($subCachedMounts as $cachedMount) { + if (!in_array($cachedMount->getMountProvider(), $setupProviders)) { + $setupProviders[] = $cachedMount->getMountProvider(); + $currentProviders[] = $cachedMount->getMountProvider(); + if ($cachedMount->getMountProvider()) { + $mounts = array_merge($mounts, $this->mountProviderCollection->getUserMountsForProviderClass($user, $cachedMount->getMountProvider())); + } else { + $this->logger->debug("mount at " . $cachedMount->getMountPoint() . " has no provider set, performing full setup"); + $this->setupForUser($user); + return; + } + } + } + } + + if (count($mounts)) { + $this->userMountCache->registerMounts($user, $mounts, $currentProviders); + $this->setupForUserWith($user, function () use ($mounts) { + array_walk($mounts, [$this->mountManager, 'addMount']); + }); + } elseif (!$this->isSetupStarted($user)) { + $this->oneTimeUserSetup($user); + } } public function tearDown() { $this->setupUsers = []; + $this->setupUsersComplete = []; + $this->setupUserMountProviders = []; $this->rootSetup = false; $this->mountManager->clear(); $this->eventDispatcher->dispatchTyped(new FilesystemTornDownEvent()); @@ -280,7 +431,9 @@ class SetupManager { private function listenForNewMountProviders() { if (!$this->listeningForProviders) { $this->listeningForProviders = true; - $this->mountProviderCollection->listen('\OC\Files\Config', 'registerMountProvider', function (IMountProvider $provider) { + $this->mountProviderCollection->listen('\OC\Files\Config', 'registerMountProvider', function ( + IMountProvider $provider + ) { foreach ($this->setupUsers as $userId) { $user = $this->userManager->get($userId); if ($user) { @@ -291,4 +444,40 @@ class SetupManager { }); } } + + private function setupListeners() { + // note that this event handling is intentionally pessimistic + // clearing the cache to often is better than not enough + + $this->eventDispatcher->addListener(UserAddedEvent::class, function (UserAddedEvent $event) { + $this->cache->remove($event->getUser()->getUID()); + }); + $this->eventDispatcher->addListener(UserRemovedEvent::class, function (UserRemovedEvent $event) { + $this->cache->remove($event->getUser()->getUID()); + }); + $this->eventDispatcher->addListener(ShareCreatedEvent::class, function (ShareCreatedEvent $event) { + $this->cache->remove($event->getShare()->getSharedWith()); + }); + $this->eventDispatcher->addListener(InvalidateMountCacheEvent::class, function (InvalidateMountCacheEvent $event + ) { + if ($user = $event->getUser()) { + $this->cache->remove($user->getUID()); + } else { + $this->cache->clear(); + } + }); + + $genericEvents = [ + '\OCA\Circles::onCircleCreation', + '\OCA\Circles::onCircleDestruction', + '\OCA\Circles::onMemberNew', + '\OCA\Circles::onMemberLeaving', + ]; + + foreach ($genericEvents as $genericEvent) { + $this->eventDispatcher->addListener($genericEvent, function ($event) { + $this->cache->clear(); + }); + } + } } diff --git a/lib/private/Files/SetupManagerFactory.php b/lib/private/Files/SetupManagerFactory.php index 56e70d09961..1d9efbd411f 100644 --- a/lib/private/Files/SetupManagerFactory.php +++ b/lib/private/Files/SetupManagerFactory.php @@ -28,9 +28,12 @@ use OCP\EventDispatcher\IEventDispatcher; use OCP\Files\Config\IMountProviderCollection; use OCP\Files\Config\IUserMountCache; use OCP\Files\Mount\IMountManager; +use OCP\ICacheFactory; +use OCP\IConfig; use OCP\IUserManager; use OCP\IUserSession; use OCP\Lockdown\ILockdownManager; +use Psr\Log\LoggerInterface; class SetupManagerFactory { private IEventLogger $eventLogger; @@ -41,6 +44,9 @@ class SetupManagerFactory { private ILockdownManager $lockdownManager; private IUserSession $userSession; private ?SetupManager $setupManager; + private ICacheFactory $cacheFactory; + private LoggerInterface $logger; + private IConfig $config; public function __construct( IEventLogger $eventLogger, @@ -49,7 +55,10 @@ class SetupManagerFactory { IEventDispatcher $eventDispatcher, IUserMountCache $userMountCache, ILockdownManager $lockdownManager, - IUserSession $userSession + IUserSession $userSession, + ICacheFactory $cacheFactory, + LoggerInterface $logger, + IConfig $config ) { $this->eventLogger = $eventLogger; $this->mountProviderCollection = $mountProviderCollection; @@ -58,6 +67,9 @@ class SetupManagerFactory { $this->userMountCache = $userMountCache; $this->lockdownManager = $lockdownManager; $this->userSession = $userSession; + $this->cacheFactory = $cacheFactory; + $this->logger = $logger; + $this->config = $config; $this->setupManager = null; } @@ -72,6 +84,9 @@ class SetupManagerFactory { $this->userMountCache, $this->lockdownManager, $this->userSession, + $this->cacheFactory, + $this->logger, + $this->config ); } return $this->setupManager; diff --git a/lib/public/Files/Config/IMountProviderCollection.php b/lib/public/Files/Config/IMountProviderCollection.php index f845d72cee6..5894d06a388 100644 --- a/lib/public/Files/Config/IMountProviderCollection.php +++ b/lib/public/Files/Config/IMountProviderCollection.php @@ -39,6 +39,16 @@ interface IMountProviderCollection { public function getMountsForUser(IUser $user); /** + * Get the configured mount points for the user from a specific mount provider + * + * @param \OCP\IUser $user + * @param class-string<IMountProvider> $mountProviderClass + * @return \OCP\Files\Mount\IMountPoint[] + * @since 24.0.0 + */ + public function getUserMountsForProviderClass(IUser $user, string $mountProviderClass); + + /** * Get the configured home mount for this user * * @param \OCP\IUser $user diff --git a/lib/public/Files/Config/IUserMountCache.php b/lib/public/Files/Config/IUserMountCache.php index 08f95990d3c..4411200c7ae 100644 --- a/lib/public/Files/Config/IUserMountCache.php +++ b/lib/public/Files/Config/IUserMountCache.php @@ -25,6 +25,7 @@ namespace OCP\Files\Config; use OCP\Files\Mount\IMountPoint; +use OCP\Files\NotFoundException; use OCP\IUser; /** @@ -38,9 +39,10 @@ interface IUserMountCache { * * @param IUser $user * @param IMountPoint[] $mounts + * @param array|null $mountProviderClasses * @since 9.0.0 */ - public function registerMounts(IUser $user, array $mounts); + public function registerMounts(IUser $user, array $mounts, array $mountProviderClasses = null); /** * Get all cached mounts for a user @@ -125,4 +127,26 @@ interface IUserMountCache { * @since 20.0.0 */ public function clear(): void; + + /** + * Get all cached mounts for a user + * + * @param IUser $user + * @param string $path + * @return ICachedMountInfo + * @throws NotFoundException + * @since 24.0.0 + */ + public function getMountForPath(IUser $user, string $path): ICachedMountInfo; + + /** + * Get all cached mounts for a user inside a path + * + * @param IUser $user + * @param string $path + * @return ICachedMountInfo[] + * @throws NotFoundException + * @since 24.0.0 + */ + public function getMountsInPath(IUser $user, string $path): array; } diff --git a/lib/public/Files/Events/InvalidateMountCacheEvent.php b/lib/public/Files/Events/InvalidateMountCacheEvent.php new file mode 100644 index 00000000000..6508e168d4c --- /dev/null +++ b/lib/public/Files/Events/InvalidateMountCacheEvent.php @@ -0,0 +1,55 @@ +<?php + +declare(strict_types=1); +/** + * @copyright Copyright (c) 2022 Robin Appelman <robin@icewind.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/>. + * + */ + +namespace OCP\Files\Events; + +use OCP\EventDispatcher\Event; +use OCP\IUser; + +/** + * Used to notify the filesystem setup manager that the available mounts for a user have changed + * + * @since 24.0.0 + */ +class InvalidateMountCacheEvent extends Event { + private ?IUser $user; + + /** + * @param IUser|null $user user + * + * @since 24.0.0 + */ + public function __construct(?IUser $user) { + parent::__construct(); + $this->user = $user; + } + + /** + * @return IUser|null user + * + * @since 24.0.0 + */ + public function getUser(): ?IUser { + return $this->user; + } +} |