Skip to content

Commit

Permalink
feat(files): Allow advanced search for files
Browse files Browse the repository at this point in the history
Signed-off-by: Benjamin Gaussorgues <benjamin.gaussorgues@nextcloud.com>
  • Loading branch information
Altahrim authored and AndyScherzinger committed Nov 10, 2023
1 parent 82120b7 commit fc2b60c
Show file tree
Hide file tree
Showing 4 changed files with 105 additions and 18 deletions.
105 changes: 88 additions & 17 deletions apps/files/lib/Search/FilesSearchProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,25 +29,32 @@
*/
namespace OCA\Files\Search;

use InvalidArgumentException;
use OCP\Files\Search\ISearchOperator;
use OCP\Search\FilterDefinition;
use OCP\Search\IFilter;
use OCP\Search\IFilteringProvider;
use OCP\Share\IShare;
use OC\Files\Search\SearchBinaryOperator;
use OC\Files\Search\SearchComparison;
use OC\Files\Search\SearchOrder;
use OC\Files\Search\SearchQuery;
use OCP\Files\FileInfo;
use OCP\Files\IMimeTypeDetector;
use OCP\Files\IRootFolder;
use OCP\Files\Search\ISearchComparison;
use OCP\Files\Node;
use OCP\Files\Search\ISearchComparison;
use OCP\Files\Search\ISearchOrder;
use OCP\IL10N;
use OCP\IURLGenerator;
use OCP\IUser;
use OCP\Search\IProvider;
use OCP\Search\ISearchQuery;
use OCP\Search\SearchResult;
use OCP\Search\SearchResultEntry;
use OC\Search\Filter\GroupFilter;
use OC\Search\Filter\UserFilter;

class FilesSearchProvider implements IProvider {

class FilesSearchProvider implements IFilteringProvider {
/** @var IL10N */
private $l10n;

Expand Down Expand Up @@ -97,21 +104,38 @@ public function getOrder(string $route, array $routeParameters): int {
return 5;
}

/**
* @inheritDoc
*/
public function getSupportedFilters(): array {
return [
'term',
'since',
'until',
'person',
'min-size',
'max-size',
'mime',
'type',
'is-favorite',
'title-only',
];
}

public function getAlternateIds(): array {
return [];
}

public function getCustomFilters(): array {
return [
new FilterDefinition('min-size', FilterDefinition::TYPE_INT),
new FilterDefinition('max-size', FilterDefinition::TYPE_INT),
new FilterDefinition('mime', FilterDefinition::TYPE_STRING),
new FilterDefinition('type', FilterDefinition::TYPE_STRING),
new FilterDefinition('is-favorite', FilterDefinition::TYPE_BOOL),
];
}

public function search(IUser $user, ISearchQuery $query): SearchResult {
$userFolder = $this->rootFolder->getUserFolder($user->getUID());
$fileQuery = new SearchQuery(
new SearchComparison(ISearchComparison::COMPARE_LIKE, 'name', '%' . $query->getTerm() . '%'),
$query->getLimit(),
(int)$query->getCursor(),
$query->getSortOrder() === ISearchQuery::SORT_DATE_DESC ? [
new SearchOrder(ISearchOrder::DIRECTION_DESCENDING, 'mtime'),
] : [],
$user
);

$fileQuery = $this->buildSearchQuery($query, $user);
return SearchResult::paginated(
$this->l10n->t('Files'),
array_map(function (Node $result) use ($userFolder) {
Expand Down Expand Up @@ -141,6 +165,53 @@ public function search(IUser $user, ISearchQuery $query): SearchResult {
);
}

private function buildSearchQuery(ISearchQuery $query, IUser $user): SearchQuery {
$comparisons = [];
foreach ($query->getFilters() as $name => $filter) {
$comparisons[] = match ($name) {
'term' => new SearchComparison(ISearchComparison::COMPARE_LIKE, 'name', '%' . $filter->get() . '%'),
'since' => new SearchComparison(ISearchComparison::COMPARE_GREATER_THAN_EQUAL, 'mtime', $filter->get()->getTimestamp()),
'until' => new SearchComparison(ISearchComparison::COMPARE_LESS_THAN_EQUAL, 'mtime', $filter->get()->getTimestamp()),
'min-size' => new SearchComparison(ISearchComparison::COMPARE_GREATER_THAN_EQUAL, 'size', $filter->get()),
'max-size' => new SearchComparison(ISearchComparison::COMPARE_LESS_THAN_EQUAL, 'size', $filter->get()),
'mime' => new SearchComparison(ISearchComparison::COMPARE_EQUAL, 'mimetype', $filter->get()),
'type' => new SearchComparison(ISearchComparison::COMPARE_LIKE, 'mimetype', $filter->get() . '/%'),
'person' => $this->buildPersonSearchQuery($filter),
default => throw new InvalidArgumentException('Unsupported comparison'),
};
}

return new SearchQuery(
new SearchBinaryOperator(SearchBinaryOperator::OPERATOR_AND, $comparisons),
$query->getLimit(),
(int) $query->getCursor(),
$query->getSortOrder() === ISearchQuery::SORT_DATE_DESC
? [new SearchOrder(ISearchOrder::DIRECTION_DESCENDING, 'mtime')]
: [],
$user
);
}

private function buildPersonSearchQuery(IFilter $person): ISearchOperator {
if ($person instanceof UserFilter) {
return new SearchBinaryOperator(SearchBinaryOperator::OPERATOR_OR, [
new SearchBinaryOperator(SearchBinaryOperator::OPERATOR_AND, [
new SearchComparison(ISearchComparison::COMPARE_EQUAL, 'share_with', $person->get()->getUID()),
new SearchComparison(ISearchComparison::COMPARE_EQUAL, 'share_type', IShare::TYPE_USER),
]),
new SearchComparison(ISearchComparison::COMPARE_EQUAL, 'owner', $person->get()->getUID()),
]);
}
if ($person instanceof GroupFilter) {
return new SearchBinaryOperator(SearchBinaryOperator::OPERATOR_AND, [
new SearchComparison(ISearchComparison::COMPARE_EQUAL, 'share_with', $person->get()->getGID()),
new SearchComparison(ISearchComparison::COMPARE_EQUAL, 'share_type', IShare::TYPE_GROUP),
]);
}

throw new InvalidArgumentException('Unsupported filter type');
}

/**
* Format subline for files
*
Expand Down
2 changes: 1 addition & 1 deletion lib/private/Files/Cache/CacheQueryBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ public function selectTagUsage(): self {
public function selectFileCache(string $alias = null, bool $joinExtendedCache = true) {
$name = $alias ?: 'filecache';
$this->select("$name.fileid", 'storage', 'path', 'path_hash', "$name.parent", "$name.name", 'mimetype', 'mimepart', 'size', 'mtime',
'storage_mtime', 'encrypted', 'etag', 'permissions', 'checksum', 'unencrypted_size')
'storage_mtime', 'encrypted', 'etag', "$name.permissions", 'checksum', 'unencrypted_size')
->from('filecache', $name);

if ($joinExtendedCache) {
Expand Down
8 changes: 8 additions & 0 deletions lib/private/Files/Cache/QuerySearchHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,11 @@ protected function equipQueryForDavTags(CacheQueryBuilder $query, IUser $user):
));
}


protected function equipQueryForShares(CacheQueryBuilder $query): void {
$query->join('file', 'share', 's', $query->expr()->eq('file.fileid', 's.file_source'));
}

/**
* Perform a file system search in multiple caches
*
Expand Down Expand Up @@ -172,6 +177,9 @@ public function searchInCaches(ISearchQuery $searchQuery, array $caches): array
if (in_array('tagname', $requestedFields) || in_array('favorite', $requestedFields)) {
$this->equipQueryForDavTags($query, $this->requireUser($searchQuery));
}
if (in_array('owner', $requestedFields) || in_array('share_with', $requestedFields) || in_array('share_type', $requestedFields)) {
$this->equipQueryForShares($query);
}

$metadataQuery = $query->selectMetadata();

Expand Down
8 changes: 8 additions & 0 deletions lib/private/Files/Cache/SearchBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,8 @@ private function getOperatorFieldAndValue(ISearchComparison $operator) {
} elseif ($field === 'path' && $type === ISearchComparison::COMPARE_EQUAL && $operator->getQueryHint(ISearchComparison::HINT_PATH_EQ_HASH, true)) {
$field = 'path_hash';
$value = md5((string)$value);
} elseif ($field === 'owner') {
$field = 'uid_owner';
}
return [$field, $value, $type];
}
Expand All @@ -208,6 +210,9 @@ private function validateComparison(ISearchComparison $operator) {
'favorite' => 'boolean',
'fileid' => 'integer',
'storage' => 'integer',
'share_with' => 'string',
'share_type' => 'integer',
'owner' => 'string',
];
$comparisons = [
'mimetype' => ['eq', 'like'],
Expand All @@ -220,6 +225,9 @@ private function validateComparison(ISearchComparison $operator) {
'favorite' => ['eq'],
'fileid' => ['eq'],
'storage' => ['eq'],
'share_with' => ['eq'],
'share_type' => ['eq'],
'owner' => ['eq'],
];

if (!isset($types[$operator->getField()])) {
Expand Down

0 comments on commit fc2b60c

Please sign in to comment.