Skip to content

Commit

Permalink
feat(dav): Support multiple scopes in DAV search
Browse files Browse the repository at this point in the history
Signed-off-by: Louis Chemineau <[email protected]>
  • Loading branch information
artonge committed Apr 17, 2024
1 parent 33c4ddd commit 784e43b
Showing 1 changed file with 73 additions and 17 deletions.
90 changes: 73 additions & 17 deletions apps/dav/lib/Files/FileSearchBackend.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
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;
Expand All @@ -39,6 +40,8 @@
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;
Expand Down Expand Up @@ -152,28 +155,73 @@ private function getPropertyDefinitionsForMetadata(): array {
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): Folder {

Check failure

Code scanning / Psalm

MoreSpecificReturnType Error

The declared return type 'OCP\Files\Folder' for OCA\DAV\Files\FileSearchBackend::getFolderForPath is more specific than the inferred return type 'OCP\Files\Node'

Check failure on line 158 in apps/dav/lib/Files/FileSearchBackend.php

View workflow job for this annotation

GitHub Actions / static-code-analysis

MoreSpecificReturnType

apps/dav/lib/Files/FileSearchBackend.php:158:51: MoreSpecificReturnType: The declared return type 'OCP\Files\Folder' for OCA\DAV\Files\FileSearchBackend::getFolderForPath is more specific than the inferred return type 'OCP\Files\Node' (see https://psalm.dev/070)
if ($path === null) {

Check failure

Code scanning / Psalm

TypeDoesNotContainNull Error

string does not contain null

Check failure on line 159 in apps/dav/lib/Files/FileSearchBackend.php

View workflow job for this annotation

GitHub Actions / static-code-analysis

TypeDoesNotContainNull

apps/dav/lib/Files/FileSearchBackend.php:159:7: TypeDoesNotContainNull: string does not contain null (see https://psalm.dev/090)
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);

return $this->rootFolder->get($fileInfo->getPath());

Check failure

Code scanning / Psalm

LessSpecificReturnStatement Error

The type 'OCP\Files\Node' is more general than the declared return type 'OCP\Files\Folder' for OCA\DAV\Files\FileSearchBackend::getFolderForPath

Check failure on line 171 in apps/dav/lib/Files/FileSearchBackend.php

View workflow job for this annotation

GitHub Actions / static-code-analysis

LessSpecificReturnStatement

apps/dav/lib/Files/FileSearchBackend.php:171:10: LessSpecificReturnStatement: The type 'OCP\Files\Node' is more general than the declared return type 'OCP\Files\Folder' for OCA\DAV\Files\FileSearchBackend::getFolderForPath (see https://psalm.dev/129)
}

/**
* @param Query $search
* @return SearchResult[]
*/
public function search(Query $search): array {

Check notice

Code scanning / Psalm

ParamNameMismatch Note

Argument 1 of OCA\DAV\Files\FileSearchBackend::search has wrong name $search, expecting $query as defined by SearchDAV\Backend\ISearchBackend::search
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);

Check notice

Code scanning / Psalm

PossiblyNullArgument Note

Argument 1 of OCA\DAV\Files\FileSearchBackend::getFolderForPath cannot be null, possibly null value provided
$query = $this->transformQuery($search);
$results = $folder->search($query);
break;
default:
$scopes = [];
foreach ($search->from as $scope) {
$folder = $this->getFolderForPath($scope->path);

Check notice

Code scanning / Psalm

PossiblyNullArgument Note

Argument 1 of OCA\DAV\Files\FileSearchBackend::getFolderForPath cannot be null, possibly null value provided
$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) {
Expand Down Expand Up @@ -288,7 +336,7 @@ private function getHrefForNode(Node $node) {
*
* @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)) {
Expand Down Expand Up @@ -316,8 +364,16 @@ private function transformQuery(Query $query): ISearchQuery {
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,

Check notice

Code scanning / Psalm

RedundantCastGivenDocblockType Note

Redundant cast to int given docblock-provided type
$offset,
$orders,
Expand Down

0 comments on commit 784e43b

Please sign in to comment.