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 f4e8ee1d99a77..65b486e9cb759 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,8 @@ class Directory extends Node implements \Sabre\DAV\IQuota, \Sabre\DAV\IMoveTarget, \Sabre\DAV\ICopyTarget, - INodeByPath { + INodeByPath, + ICacheableDirectory { /** * Cached directory content * @var FileInfo[] @@ -563,4 +565,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 8e7d489dad0cc..3f826549ed0fc 100644 --- a/apps/dav/lib/DAV/CustomPropertiesBackend.php +++ b/apps/dav/lib/DAV/CustomPropertiesBackend.php @@ -20,6 +20,7 @@ use OCA\DAV\Connector\Sabre\Directory; use OCA\DAV\Db\PropertyMapper; use OCP\DB\QueryBuilder\IQueryBuilder; +use OCP\Files\Folder; use OCP\IDBConnection; use OCP\IUser; use Override; @@ -208,8 +209,11 @@ 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 ICacheableDirectory) && $propFind->getDepth() !== 0) { + $directoriesToPrefetch = $node->getCacheableDirectories(); + foreach ($directoriesToPrefetch as $directory) { + $this->cacheDirectory($path, $directory); + } } if ($node instanceof CalendarHome && $propFind->getDepth() !== 0) { @@ -356,18 +360,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/dav/lib/DAV/ICacheableDirectory.php b/apps/dav/lib/DAV/ICacheableDirectory.php new file mode 100644 index 0000000000000..2157e341d86c8 --- /dev/null +++ b/apps/dav/lib/DAV/ICacheableDirectory.php @@ -0,0 +1,18 @@ +trashManager->getCacheableRootsForUser($this->user); + } } diff --git a/apps/files_trashbin/lib/Trash/ITrashBackend.php b/apps/files_trashbin/lib/Trash/ITrashBackend.php index 11b3132bfba49..2485605d5c9b8 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,13 @@ public function moveToTrash(IStorage $storage, string $internalPath): bool; * @return Node|null */ 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 34.0.0 + */ + public function getCacheableRootsForUser(IUser $user): array; } diff --git a/apps/files_trashbin/lib/Trash/LegacyTrashBackend.php b/apps/files_trashbin/lib/Trash/LegacyTrashBackend.php index 204defde35c72..3262b59a6b6b6 100644 --- a/apps/files_trashbin/lib/Trash/LegacyTrashBackend.php +++ b/apps/files_trashbin/lib/Trash/LegacyTrashBackend.php @@ -4,9 +4,11 @@ * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later */ + namespace OCA\Files_Trashbin\Trash; use OC\Files\Filesystem; +use OC\Files\Node\LazyFolder; use OC\Files\View; use OCA\Files_Trashbin\Helper; use OCA\Files_Trashbin\Storage; @@ -16,8 +18,10 @@ use OCP\Files\IRootFolder; use OCP\Files\NotFoundException; use OCP\Files\Storage\IStorage; +use OCP\IDBConnection; use OCP\IUser; use OCP\IUserManager; +use OCP\Server; class LegacyTrashBackend implements ITrashBackend { /** @var array */ @@ -118,4 +122,30 @@ public function getTrashNodeById(IUser $user, int $fileId) { return null; } } + + public function getCacheableRootsForUser(IUser $user): array { + $userFolder = $this->rootFolder->getUserFolder($user->getUID()); + $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/apps/files_trashbin/lib/Trash/TrashManager.php b/apps/files_trashbin/lib/Trash/TrashManager.php index 521a576c00a7b..027b5893a1736 100644 --- a/apps/files_trashbin/lib/Trash/TrashManager.php +++ b/apps/files_trashbin/lib/Trash/TrashManager.php @@ -108,4 +108,9 @@ public function pauseTrash() { public function resumeTrash() { $this->trashPaused = false; } + + public function getCacheableRootsForUser(IUser $user): array { + return array_reduce($this->getBackends(), + fn (array $items, ITrashBackend $backend) => array_merge($items, $backend->getCacheableRootsForUser($user)), []); + } } 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()); + } } /**