From 8495993f814b3015dbb795f7f5377cc835b277eb Mon Sep 17 00:00:00 2001 From: Carl Schwan Date: Thu, 7 Aug 2025 12:42:30 +0200 Subject: [PATCH 1/3] fix(trashbin): Fix n+1 issue in propfind in trash root CustomPropertiesBackend is already able to cache the custom properties of a folder content. But the trash root is not a Directory but instead just a ICollection, so extend CacheEntry to also handle a TrashEntry. Signed-off-by: Carl Schwan --- apps/dav/lib/DAV/CustomPropertiesBackend.php | 21 ++++++++++++------- apps/files_trashbin/lib/Sabre/TrashRoot.php | 13 ++++++++++++ .../lib/Trash/ITrashBackend.php | 7 +++++++ .../lib/Trash/ITrashManager.php | 5 +++++ .../lib/Trash/LegacyTrashBackend.php | 5 +++++ .../files_trashbin/lib/Trash/TrashManager.php | 6 ++++++ 6 files changed, 50 insertions(+), 7 deletions(-) diff --git a/apps/dav/lib/DAV/CustomPropertiesBackend.php b/apps/dav/lib/DAV/CustomPropertiesBackend.php index 8e7d489dad0cc..22ce73aa42d0f 100644 --- a/apps/dav/lib/DAV/CustomPropertiesBackend.php +++ b/apps/dav/lib/DAV/CustomPropertiesBackend.php @@ -19,7 +19,10 @@ use OCA\DAV\CalDAV\Trashbin\TrashbinHome; use OCA\DAV\Connector\Sabre\Directory; use OCA\DAV\Db\PropertyMapper; +use OCA\DAV\Connector\Sabre\FilesPlugin; +use OCA\Files_Trashbin\Sabre\TrashRoot; use OCP\DB\QueryBuilder\IQueryBuilder; +use OCP\Files\Folder; use OCP\IDBConnection; use OCP\IUser; use Override; @@ -208,8 +211,13 @@ public function propFind($path, PropFind $propFind): void { } $node = $this->tree->getNodeForPath($path); - if ($node instanceof Directory && $propFind->getDepth() !== 0) { - $this->cacheDirectory($path, $node); + if (($node instanceof Directory) && $propFind->getDepth() !== 0) { + $this->cacheDirectory($path, $node->getNode()); + } else if ($node instanceof TrashRoot) { + $trashNodes = $node->getTrashRoots(); + foreach ($trashNodes as $trashNode) { + $this->cacheDirectory($path, $trashNode); + } } if ($node instanceof CalendarHome && $propFind->getDepth() !== 0) { @@ -356,18 +364,17 @@ private function getPublishedProperties(string $path, array $requestedProperties /** * Prefetch all user properties in a directory */ - private function cacheDirectory(string $path, Directory $node): void { + private function cacheDirectory(string $path, Folder $node): void { $prefix = ltrim($path . '/', '/'); $query = $this->connection->getQueryBuilder(); $query->select('name', 'p.propertypath', 'p.propertyname', 'p.propertyvalue', 'p.valuetype') ->from('filecache', 'f') - ->hintShardKey('storage', $node->getNode()->getMountPoint()->getNumericStorageId()) + ->hintShardKey('storage', $node->getMountPoint()->getNumericStorageId()) ->leftJoin('f', 'properties', 'p', $query->expr()->eq('p.propertypath', $query->func()->concat( $query->createNamedParameter($prefix), 'f.name' - )), - ) - ->where($query->expr()->eq('parent', $query->createNamedParameter($node->getInternalFileId(), IQueryBuilder::PARAM_INT))) + ))) + ->where($query->expr()->eq('parent', $query->createNamedParameter($node->getId(), IQueryBuilder::PARAM_INT))) ->andWhere($query->expr()->orX( $query->expr()->eq('p.userid', $query->createNamedParameter($this->user->getUID())), $query->expr()->isNull('p.userid'), diff --git a/apps/files_trashbin/lib/Sabre/TrashRoot.php b/apps/files_trashbin/lib/Sabre/TrashRoot.php index dd89583d9a1b3..45532ac1b4467 100644 --- a/apps/files_trashbin/lib/Sabre/TrashRoot.php +++ b/apps/files_trashbin/lib/Sabre/TrashRoot.php @@ -13,12 +13,18 @@ use OCA\Files_Trashbin\Trash\ITrashManager; use OCA\Files_Trashbin\Trashbin; use OCP\Files\FileInfo; +use OCP\Files\Folder; +use OCP\Files\IRootFolder; +use OCP\Files\NotFoundException; +use OCP\Files\NotPermittedException; use OCP\IUser; +use OCP\Server; use Sabre\DAV\Exception\Forbidden; use Sabre\DAV\Exception\NotFound; use Sabre\DAV\ICollection; class TrashRoot implements ICollection { + private ?Folder $trashFilesRoot = null; public function __construct( private IUser $user, @@ -90,4 +96,11 @@ public function childExists($name): bool { public function getLastModified(): int { return 0; } + + /** + * @return Folder[] + */ + public function getTrashRoots(): array { + return $this->trashManager->getTrashRootsForUser($this->user); + } } diff --git a/apps/files_trashbin/lib/Trash/ITrashBackend.php b/apps/files_trashbin/lib/Trash/ITrashBackend.php index 11b3132bfba49..b57ac0ed385f9 100644 --- a/apps/files_trashbin/lib/Trash/ITrashBackend.php +++ b/apps/files_trashbin/lib/Trash/ITrashBackend.php @@ -6,6 +6,7 @@ */ namespace OCA\Files_Trashbin\Trash; +use OCP\Files\Folder; use OCP\Files\Node; use OCP\Files\Storage\IStorage; use OCP\IUser; @@ -64,4 +65,10 @@ public function moveToTrash(IStorage $storage, string $internalPath): bool; * @return Node|null */ public function getTrashNodeById(IUser $user, int $fileId); + + /** + * @return Folder[] + * @since 32.0.0 + */ + public function getTrashRootsForUser(IUser $user): array; } diff --git a/apps/files_trashbin/lib/Trash/ITrashManager.php b/apps/files_trashbin/lib/Trash/ITrashManager.php index 743ea01358a1d..3054f80e592fd 100644 --- a/apps/files_trashbin/lib/Trash/ITrashManager.php +++ b/apps/files_trashbin/lib/Trash/ITrashManager.php @@ -38,4 +38,9 @@ public function pauseTrash(); * @since 15.0.0 */ public function resumeTrash(); + + /** + * @since 32.0.0 + */ + public function getTrashRootsForUser(IUser $user): array; } diff --git a/apps/files_trashbin/lib/Trash/LegacyTrashBackend.php b/apps/files_trashbin/lib/Trash/LegacyTrashBackend.php index 204defde35c72..cb7e495869a2c 100644 --- a/apps/files_trashbin/lib/Trash/LegacyTrashBackend.php +++ b/apps/files_trashbin/lib/Trash/LegacyTrashBackend.php @@ -118,4 +118,9 @@ public function getTrashNodeById(IUser $user, int $fileId) { return null; } } + + public function getTrashRootsForUser(IUser $user): array { + $userFolder = $this->rootFolder->getUserFolder($user->getUID()); + return [$userFolder->getParent()->get('files_trashbin/files')]; + } } diff --git a/apps/files_trashbin/lib/Trash/TrashManager.php b/apps/files_trashbin/lib/Trash/TrashManager.php index 521a576c00a7b..17a5f5fd12e36 100644 --- a/apps/files_trashbin/lib/Trash/TrashManager.php +++ b/apps/files_trashbin/lib/Trash/TrashManager.php @@ -108,4 +108,10 @@ public function pauseTrash() { public function resumeTrash() { $this->trashPaused = false; } + + public function getTrashRootsForUser(IUser $user): array { + return array_reduce($this->getBackends(), function (array $items, ITrashBackend $backend) use ($user) { + return array_merge($items, $backend->getTrashRootsForUser($user)); + }, []); + } } From fb92a7a83ca85565e5400b7eaea02a9c6055d672 Mon Sep 17 00:00:00 2001 From: Carl Schwan Date: Fri, 22 Aug 2025 11:45:55 +0200 Subject: [PATCH 2/3] refactor(trash): Introduce ICacheableDirectory interface That interface can be implemented by any ICollection that is based on one or multiple \OCP\Files\Folder to pre-fetch the custom properties. Signed-off-by: Carl Schwan --- apps/dav/lib/Connector/Sabre/Directory.php | 9 ++++++++- apps/dav/lib/DAV/CustomPropertiesBackend.php | 12 ++++-------- apps/dav/lib/DAV/ICacheableDirectory.php | 12 ++++++++++++ apps/files_trashbin/lib/Sabre/TrashRoot.php | 13 ++++--------- apps/files_trashbin/lib/Trash/ITrashBackend.php | 5 ++++- apps/files_trashbin/lib/Trash/ITrashManager.php | 5 ----- apps/files_trashbin/lib/Trash/TrashManager.php | 7 +++---- 7 files changed, 35 insertions(+), 28 deletions(-) create mode 100644 apps/dav/lib/DAV/ICacheableDirectory.php diff --git a/apps/dav/lib/Connector/Sabre/Directory.php b/apps/dav/lib/Connector/Sabre/Directory.php index f4e8ee1d99a77..7530d4aced47d 100644 --- a/apps/dav/lib/Connector/Sabre/Directory.php +++ b/apps/dav/lib/Connector/Sabre/Directory.php @@ -14,6 +14,7 @@ use OCA\DAV\Connector\Sabre\Exception\FileLocked; use OCA\DAV\Connector\Sabre\Exception\Forbidden; use OCA\DAV\Connector\Sabre\Exception\InvalidPath; +use OCA\DAV\DAV\ICacheableDirectory; use OCA\DAV\Storage\PublicShareWrapper; use OCP\App\IAppManager; use OCP\Constants; @@ -46,7 +47,9 @@ class Directory extends Node implements \Sabre\DAV\IQuota, \Sabre\DAV\IMoveTarget, \Sabre\DAV\ICopyTarget, - INodeByPath { + INodeByPath, + ICacheableDirectory +{ /** * Cached directory content * @var FileInfo[] @@ -563,4 +566,8 @@ public function getNodeForPath($path): INode { return $node; } + + public function getCacheableDirectories(): array { + return [$this->getNode()]; + } } diff --git a/apps/dav/lib/DAV/CustomPropertiesBackend.php b/apps/dav/lib/DAV/CustomPropertiesBackend.php index 22ce73aa42d0f..3f826549ed0fc 100644 --- a/apps/dav/lib/DAV/CustomPropertiesBackend.php +++ b/apps/dav/lib/DAV/CustomPropertiesBackend.php @@ -19,8 +19,6 @@ use OCA\DAV\CalDAV\Trashbin\TrashbinHome; use OCA\DAV\Connector\Sabre\Directory; use OCA\DAV\Db\PropertyMapper; -use OCA\DAV\Connector\Sabre\FilesPlugin; -use OCA\Files_Trashbin\Sabre\TrashRoot; use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\Files\Folder; use OCP\IDBConnection; @@ -211,12 +209,10 @@ public function propFind($path, PropFind $propFind): void { } $node = $this->tree->getNodeForPath($path); - if (($node instanceof Directory) && $propFind->getDepth() !== 0) { - $this->cacheDirectory($path, $node->getNode()); - } else if ($node instanceof TrashRoot) { - $trashNodes = $node->getTrashRoots(); - foreach ($trashNodes as $trashNode) { - $this->cacheDirectory($path, $trashNode); + if (($node instanceof ICacheableDirectory) && $propFind->getDepth() !== 0) { + $directoriesToPrefetch = $node->getCacheableDirectories(); + foreach ($directoriesToPrefetch as $directory) { + $this->cacheDirectory($path, $directory); } } diff --git a/apps/dav/lib/DAV/ICacheableDirectory.php b/apps/dav/lib/DAV/ICacheableDirectory.php new file mode 100644 index 0000000000000..addfbed68737d --- /dev/null +++ b/apps/dav/lib/DAV/ICacheableDirectory.php @@ -0,0 +1,12 @@ +trashManager->getTrashRootsForUser($this->user); + public function getCacheableDirectories(): array { + return $this->trashManager->getCacheableRootsForUser($this->user); } } diff --git a/apps/files_trashbin/lib/Trash/ITrashBackend.php b/apps/files_trashbin/lib/Trash/ITrashBackend.php index b57ac0ed385f9..bb3562027deab 100644 --- a/apps/files_trashbin/lib/Trash/ITrashBackend.php +++ b/apps/files_trashbin/lib/Trash/ITrashBackend.php @@ -67,8 +67,11 @@ public function moveToTrash(IStorage $storage, string $internalPath): bool; public function getTrashNodeById(IUser $user, int $fileId); /** + * Returns a non-exhaustive list of folder which can then be used to pre-fetch some metadata + * * for the trash root. + * * @return Folder[] * @since 32.0.0 */ - public function getTrashRootsForUser(IUser $user): array; + public function getCacheableRootsForUser(IUser $user): array; } diff --git a/apps/files_trashbin/lib/Trash/ITrashManager.php b/apps/files_trashbin/lib/Trash/ITrashManager.php index 3054f80e592fd..743ea01358a1d 100644 --- a/apps/files_trashbin/lib/Trash/ITrashManager.php +++ b/apps/files_trashbin/lib/Trash/ITrashManager.php @@ -38,9 +38,4 @@ public function pauseTrash(); * @since 15.0.0 */ public function resumeTrash(); - - /** - * @since 32.0.0 - */ - public function getTrashRootsForUser(IUser $user): array; } diff --git a/apps/files_trashbin/lib/Trash/TrashManager.php b/apps/files_trashbin/lib/Trash/TrashManager.php index 17a5f5fd12e36..027b5893a1736 100644 --- a/apps/files_trashbin/lib/Trash/TrashManager.php +++ b/apps/files_trashbin/lib/Trash/TrashManager.php @@ -109,9 +109,8 @@ public function resumeTrash() { $this->trashPaused = false; } - public function getTrashRootsForUser(IUser $user): array { - return array_reduce($this->getBackends(), function (array $items, ITrashBackend $backend) use ($user) { - return array_merge($items, $backend->getTrashRootsForUser($user)); - }, []); + public function getCacheableRootsForUser(IUser $user): array { + return array_reduce($this->getBackends(), + fn (array $items, ITrashBackend $backend) => array_merge($items, $backend->getCacheableRootsForUser($user)), []); } } From 70d62d792f4d091df5aad5c0487a2589312d9f87 Mon Sep 17 00:00:00 2001 From: Carl Schwan Date: Fri, 22 Aug 2025 11:50:39 +0200 Subject: [PATCH 3/3] feat(trashbin): Use a lazy folder for roots of trashbin Introduze a LazyMountPoint which is similar to LazyFile/LazyFolder and allow to avoid doing DB request when we just want the storage id and we already have it. Signed-off-by: Carl Schwan --- .../composer/composer/autoload_classmap.php | 1 + .../dav/composer/composer/autoload_static.php | 1 + apps/dav/lib/Connector/Sabre/Directory.php | 3 +- apps/dav/lib/DAV/ICacheableDirectory.php | 6 ++ .../lib/Trash/ITrashBackend.php | 4 +- .../lib/Trash/LegacyTrashBackend.php | 29 +++++- lib/composer/composer/autoload_classmap.php | 1 + lib/composer/composer/autoload_static.php | 1 + lib/private/Files/Mount/LazyMountPoint.php | 93 +++++++++++++++++++ lib/private/Files/Node/LazyFolder.php | 11 ++- 10 files changed, 143 insertions(+), 7 deletions(-) create mode 100644 lib/private/Files/Mount/LazyMountPoint.php diff --git a/apps/dav/composer/composer/autoload_classmap.php b/apps/dav/composer/composer/autoload_classmap.php index 6716b6c5ac1cd..866ed459e0037 100644 --- a/apps/dav/composer/composer/autoload_classmap.php +++ b/apps/dav/composer/composer/autoload_classmap.php @@ -264,6 +264,7 @@ 'OCA\\DAV\\Controller\\UpcomingEventsController' => $baseDir . '/../lib/Controller/UpcomingEventsController.php', 'OCA\\DAV\\DAV\\CustomPropertiesBackend' => $baseDir . '/../lib/DAV/CustomPropertiesBackend.php', 'OCA\\DAV\\DAV\\GroupPrincipalBackend' => $baseDir . '/../lib/DAV/GroupPrincipalBackend.php', + 'OCA\\DAV\\DAV\\ICacheableDirectory' => $baseDir . '/../lib/DAV/ICacheableDirectory.php', 'OCA\\DAV\\DAV\\PublicAuth' => $baseDir . '/../lib/DAV/PublicAuth.php', 'OCA\\DAV\\DAV\\RemoteUserPrincipalBackend' => $baseDir . '/../lib/DAV/RemoteUserPrincipalBackend.php', 'OCA\\DAV\\DAV\\Sharing\\Backend' => $baseDir . '/../lib/DAV/Sharing/Backend.php', diff --git a/apps/dav/composer/composer/autoload_static.php b/apps/dav/composer/composer/autoload_static.php index 5b5b1e3fcb413..673c7d647dc9b 100644 --- a/apps/dav/composer/composer/autoload_static.php +++ b/apps/dav/composer/composer/autoload_static.php @@ -279,6 +279,7 @@ class ComposerStaticInitDAV 'OCA\\DAV\\Controller\\UpcomingEventsController' => __DIR__ . '/..' . '/../lib/Controller/UpcomingEventsController.php', 'OCA\\DAV\\DAV\\CustomPropertiesBackend' => __DIR__ . '/..' . '/../lib/DAV/CustomPropertiesBackend.php', 'OCA\\DAV\\DAV\\GroupPrincipalBackend' => __DIR__ . '/..' . '/../lib/DAV/GroupPrincipalBackend.php', + 'OCA\\DAV\\DAV\\ICacheableDirectory' => __DIR__ . '/..' . '/../lib/DAV/ICacheableDirectory.php', 'OCA\\DAV\\DAV\\PublicAuth' => __DIR__ . '/..' . '/../lib/DAV/PublicAuth.php', 'OCA\\DAV\\DAV\\RemoteUserPrincipalBackend' => __DIR__ . '/..' . '/../lib/DAV/RemoteUserPrincipalBackend.php', 'OCA\\DAV\\DAV\\Sharing\\Backend' => __DIR__ . '/..' . '/../lib/DAV/Sharing/Backend.php', diff --git a/apps/dav/lib/Connector/Sabre/Directory.php b/apps/dav/lib/Connector/Sabre/Directory.php index 7530d4aced47d..65b486e9cb759 100644 --- a/apps/dav/lib/Connector/Sabre/Directory.php +++ b/apps/dav/lib/Connector/Sabre/Directory.php @@ -48,8 +48,7 @@ class Directory extends Node implements \Sabre\DAV\IMoveTarget, \Sabre\DAV\ICopyTarget, INodeByPath, - ICacheableDirectory -{ + ICacheableDirectory { /** * Cached directory content * @var FileInfo[] diff --git a/apps/dav/lib/DAV/ICacheableDirectory.php b/apps/dav/lib/DAV/ICacheableDirectory.php index addfbed68737d..2157e341d86c8 100644 --- a/apps/dav/lib/DAV/ICacheableDirectory.php +++ b/apps/dav/lib/DAV/ICacheableDirectory.php @@ -1,5 +1,11 @@ rootFolder->getUserFolder($user->getUID()); - return [$userFolder->getParent()->get('files_trashbin/files')]; + $connection = Server::get(IDBConnection::class); + $qb = $connection->getQueryBuilder(); + $qb->select('fileid', 'storage') + ->from('filecache') + ->hintShardKey('storage', $userFolder->getMountPoint()->getNumericStorageId()) + ->where($qb->expr()->eq('path_hash', $qb->createNamedParameter(md5('files_trashbin/files')))); + $result = $qb->executeQuery()->fetchAll(); + + if (count($result) === 0) { + return []; + } + + return [ + new LazyFolder( + $this->rootFolder, + fn () => $userFolder->getParent()->get('files_trashbin/files'), + [ + 'fileid' => $result[0]['fileid'], + 'mountpoint_numericStorageId' => $result[0]['storage'], + ] + ) + ]; } } diff --git a/lib/composer/composer/autoload_classmap.php b/lib/composer/composer/autoload_classmap.php index dcb05293b0d9a..e811d2934f20b 100644 --- a/lib/composer/composer/autoload_classmap.php +++ b/lib/composer/composer/autoload_classmap.php @@ -1739,6 +1739,7 @@ 'OC\\Files\\Lock\\LockManager' => $baseDir . '/lib/private/Files/Lock/LockManager.php', 'OC\\Files\\Mount\\CacheMountProvider' => $baseDir . '/lib/private/Files/Mount/CacheMountProvider.php', 'OC\\Files\\Mount\\HomeMountPoint' => $baseDir . '/lib/private/Files/Mount/HomeMountPoint.php', + 'OC\\Files\\Mount\\LazyMountPoint' => $baseDir . '/lib/private/Files/Mount/LazyMountPoint.php', 'OC\\Files\\Mount\\LocalHomeMountProvider' => $baseDir . '/lib/private/Files/Mount/LocalHomeMountProvider.php', 'OC\\Files\\Mount\\Manager' => $baseDir . '/lib/private/Files/Mount/Manager.php', 'OC\\Files\\Mount\\MountPoint' => $baseDir . '/lib/private/Files/Mount/MountPoint.php', diff --git a/lib/composer/composer/autoload_static.php b/lib/composer/composer/autoload_static.php index 34e3bcced48c7..e29ed8c500936 100644 --- a/lib/composer/composer/autoload_static.php +++ b/lib/composer/composer/autoload_static.php @@ -1780,6 +1780,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2 'OC\\Files\\Lock\\LockManager' => __DIR__ . '/../../..' . '/lib/private/Files/Lock/LockManager.php', 'OC\\Files\\Mount\\CacheMountProvider' => __DIR__ . '/../../..' . '/lib/private/Files/Mount/CacheMountProvider.php', 'OC\\Files\\Mount\\HomeMountPoint' => __DIR__ . '/../../..' . '/lib/private/Files/Mount/HomeMountPoint.php', + 'OC\\Files\\Mount\\LazyMountPoint' => __DIR__ . '/../../..' . '/lib/private/Files/Mount/LazyMountPoint.php', 'OC\\Files\\Mount\\LocalHomeMountProvider' => __DIR__ . '/../../..' . '/lib/private/Files/Mount/LocalHomeMountProvider.php', 'OC\\Files\\Mount\\Manager' => __DIR__ . '/../../..' . '/lib/private/Files/Mount/Manager.php', 'OC\\Files\\Mount\\MountPoint' => __DIR__ . '/../../..' . '/lib/private/Files/Mount/MountPoint.php', diff --git a/lib/private/Files/Mount/LazyMountPoint.php b/lib/private/Files/Mount/LazyMountPoint.php new file mode 100644 index 0000000000000..64aacb4f6c256 --- /dev/null +++ b/lib/private/Files/Mount/LazyMountPoint.php @@ -0,0 +1,93 @@ +mountPoint === null) { + $this->mountPoint = call_user_func($this->mountPointClosure); + } + return $this->mountPoint; + } + + public function __call($method, $args) { + return call_user_func_array([$this->getRealMountPoint(), $method], $args); + } + + public function getMountPoint() { + return $this->__call(__FUNCTION__, func_get_args()); + } + + public function setMountPoint($mountPoint) { + return $this->__call(__FUNCTION__, func_get_args()); + } + + private function createStorage() { + return $this->__call(__FUNCTION__, func_get_args()); + } + + public function getStorage() { + return $this->__call(__FUNCTION__, func_get_args()); + } + + public function getStorageId(): ?string { + return $this->__call(__FUNCTION__, func_get_args()); + } + + public function getNumericStorageId(): int { + if (isset($this->data['numericStorageId'])) { + return $this->data['numericStorageId']; + } + return $this->__call(__FUNCTION__, func_get_args()); + } + + public function getInternalPath($path) { + return $this->__call(__FUNCTION__, func_get_args()); + } + + public function wrapStorage($wrapper): void { + $this->__call(__FUNCTION__, func_get_args()); + } + + public function getOption($name, $default) { + return $this->__call(__FUNCTION__, func_get_args()); + } + + public function getOptions(): array { + return $this->__call(__FUNCTION__, func_get_args()); + } + + public function getStorageRootId(): int { + return $this->__call(__FUNCTION__, func_get_args()); + } + + public function getMountId() { + return $this->__call(__FUNCTION__, func_get_args()); + } + + public function getMountType() { + return $this->__call(__FUNCTION__, func_get_args()); + } + + public function getMountProvider(): string { + return $this->__call(__FUNCTION__, func_get_args()); + } +} diff --git a/lib/private/Files/Node/LazyFolder.php b/lib/private/Files/Node/LazyFolder.php index c23a7d03ada9e..a2a86e7eafa6f 100644 --- a/lib/private/Files/Node/LazyFolder.php +++ b/lib/private/Files/Node/LazyFolder.php @@ -8,6 +8,7 @@ namespace OC\Files\Node; use OC\Files\Filesystem; +use OC\Files\Mount\LazyMountPoint; use OC\Files\Utils\PathHelper; use OCP\Constants; use OCP\Files\Folder; @@ -373,7 +374,15 @@ public function isMounted() { * @inheritDoc */ public function getMountPoint() { - return $this->__call(__FUNCTION__, func_get_args()); + if (array_any(array_keys($this->data), fn ($key) => str_starts_with($key, 'mountpoint_'))) { + return new LazyMountPoint(function () { + return $this->__call('getMountPoint', func_get_args()); + }, [ + 'numericStorageId' => $this->data['mountpoint_numericStorageId'], + ]); + } else { + return $this->__call(__FUNCTION__, func_get_args()); + } } /**