From ac0acfbbd5ac925d8a098f660cfc4a23c1bb66ad Mon Sep 17 00:00:00 2001 From: Louis Chemineau Date: Tue, 16 Apr 2024 16:18:14 +0200 Subject: [PATCH] feat(dav): Support multiple scopes in DAV search Signed-off-by: Louis Chemineau --- apps/dav/lib/Files/FileSearchBackend.php | 91 +++++++++++++++++++----- 1 file changed, 74 insertions(+), 17 deletions(-) diff --git a/apps/dav/lib/Files/FileSearchBackend.php b/apps/dav/lib/Files/FileSearchBackend.php index fd45491da7e..8b87ae624ba 100644 --- a/apps/dav/lib/Files/FileSearchBackend.php +++ b/apps/dav/lib/Files/FileSearchBackend.php @@ -30,6 +30,7 @@ use OC\Files\Search\SearchBinaryOperator; use OC\Files\Search\SearchComparison; use OC\Files\Search\SearchOrder; use OC\Files\Search\SearchQuery; +use OC\Files\Storage\Wrapper\Jail; use OC\Files\View; use OCA\DAV\Connector\Sabre\CachingTree; use OCA\DAV\Connector\Sabre\Directory; @@ -39,6 +40,8 @@ use OCP\Files\Cache\ICacheEntry; use OCP\Files\Folder; use OCP\Files\IRootFolder; use OCP\Files\Node; +use OCP\Files\Search\ISearchBinaryOperator; +use OCP\Files\Search\ISearchComparison; use OCP\Files\Search\ISearchOperator; use OCP\Files\Search\ISearchOrder; use OCP\Files\Search\ISearchQuery; @@ -152,28 +155,74 @@ class FileSearchBackend implements ISearchBackend { public function preloadPropertyFor(array $nodes, array $requestProperties): void { } - /** - * @param Query $search - * @return SearchResult[] - */ - public function search(Query $search): array { - if (count($search->from) !== 1) { - throw new \InvalidArgumentException('Searching more than one folder is not supported'); - } - $query = $this->transformQuery($search); - $scope = $search->from[0]; - if ($scope->path === null) { + private function getFolderForPath(?string $path = null): Folder { + if ($path === null) { throw new \InvalidArgumentException('Using uri\'s as scope is not supported, please use a path relative to the search arbiter instead'); } - $node = $this->tree->getNodeForPath($scope->path); + + $node = $this->tree->getNodeForPath($path); + if (!$node instanceof Directory) { throw new \InvalidArgumentException('Search is only supported on directories'); } $fileInfo = $node->getFileInfo(); - $folder = $this->rootFolder->get($fileInfo->getPath()); - /** @var Folder $folder $results */ - $results = $folder->search($query); + + /** @var Folder */ + return $this->rootFolder->get($fileInfo->getPath()); + } + + /** + * @param Query $search + * @return SearchResult[] + */ + public function search(Query $search): array { + switch (count($search->from)) { + case 0: + throw new \InvalidArgumentException('You need to specify a scope for the search.'); + break; + case 1: + $scope = $search->from[0]; + $folder = $this->getFolderForPath($scope->path); + $query = $this->transformQuery($search); + $results = $folder->search($query); + break; + default: + $scopes = []; + foreach ($search->from as $scope) { + $folder = $this->getFolderForPath($scope->path); + $folderStorage = $folder->getStorage(); + if ($folderStorage->instanceOfStorage(Jail::class)) { + /** @var Jail $folderStorage */ + $internalPath = $folderStorage->getUnjailedPath($folder->getInternalPath()); + } else { + $internalPath = $folder->getInternalPath(); + } + + $scopes[] = new SearchBinaryOperator( + ISearchBinaryOperator::OPERATOR_AND, + [ + new SearchComparison( + ISearchComparison::COMPARE_EQUAL, + 'storage', + $folderStorage->getCache()->getNumericStorageId(), + '' + ), + new SearchComparison( + ISearchComparison::COMPARE_LIKE, + 'path', + $internalPath . '/%', + '' + ), + ] + ); + } + + $scopeOperators = new SearchBinaryOperator(ISearchBinaryOperator::OPERATOR_OR, $scopes); + $query = $this->transformQuery($search, $scopeOperators); + $userFolder = $this->rootFolder->getUserFolder($this->user->getUID()); + $results = $userFolder->search($query); + } /** @var SearchResult[] $nodes */ $nodes = array_map(function (Node $node) { @@ -288,7 +337,7 @@ class FileSearchBackend implements ISearchBackend { * * @return ISearchQuery */ - private function transformQuery(Query $query): ISearchQuery { + private function transformQuery(Query $query, ?SearchBinaryOperator $scopeOperators = null): ISearchQuery { $orders = array_map(function (Order $order): ISearchOrder { $direction = $order->order === Order::ASC ? ISearchOrder::DIRECTION_ASCENDING : ISearchOrder::DIRECTION_DESCENDING; if (str_starts_with($order->property->name, FilesPlugin::FILE_METADATA_PREFIX)) { @@ -316,8 +365,16 @@ class FileSearchBackend implements ISearchBackend { throw new \InvalidArgumentException('Invalid search query, maximum operator limit of ' . self::OPERATOR_LIMIT . ' exceeded, got ' . $operatorCount . ' operators'); } + /** @var SearchBinaryOperator|SearchComparison */ + $queryOperators = $this->transformSearchOperation($query->where); + if ($scopeOperators === null) { + $operators = $queryOperators; + } else { + $operators = new SearchBinaryOperator(ISearchBinaryOperator::OPERATOR_AND, [$queryOperators, $scopeOperators]); + } + return new SearchQuery( - $this->transformSearchOperation($query->where), + $operators, (int)$limit->maxResults, $offset, $orders,