From 2ea872d1562e77f486eb21323a2905b0585ea2f4 Mon Sep 17 00:00:00 2001 From: Arthur Schiwon Date: Thu, 27 Apr 2023 22:24:16 +0200 Subject: PoC: SystemTags endpoint to return tags used by a user with meta data MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Target case is photos app: when visiting the tags category, all systemtags of the whole cloud are retrieved. In subequent steps the next tag is requested until the browser view is filled with tag tiles (i.e. previews are requested just as well). With this approach, we incorpoate the dav search and look for user related tags that are used by them, and already returns the statistics (number of files tagged with the respective tag) as well as a file id for the purpose to load the preview. This defaults to the file with the highest id. Call: curl -s -u 'user:password' \ 'https://my.nc.srv/remote.php/dav/systemtags-current' \ -X PROPFIND -H 'Accept: text/plain' \ -H 'Accept-Language: en-US,en;q=0.5' -H 'Depth: 1' \ -H 'Content-Type: text/plain;charset=UTF-8' \ --data @/home/doe/request-systemtag-props.xml With request-systemtag-props.xml: Example output: … /master/remote.php/dav/systemtags/84 84 Computer true true true 42 924022 HTTP/1.1 200 OK /remote.php/dav/systemtags/97 97 Bear true true true 1 923422 HTTP/1.1 200 OK … Signed-off-by: Arthur Schiwon --- lib/private/Files/Cache/CacheQueryBuilder.php | 22 ++++++++- lib/private/Files/Cache/QuerySearchHelper.php | 58 +++++++++++++++--------- lib/private/Files/Node/Folder.php | 64 +++++++++++++++++---------- 3 files changed, 98 insertions(+), 46 deletions(-) (limited to 'lib') diff --git a/lib/private/Files/Cache/CacheQueryBuilder.php b/lib/private/Files/Cache/CacheQueryBuilder.php index 496a8361d77..c5563750c4d 100644 --- a/lib/private/Files/Cache/CacheQueryBuilder.php +++ b/lib/private/Files/Cache/CacheQueryBuilder.php @@ -41,8 +41,28 @@ class CacheQueryBuilder extends QueryBuilder { parent::__construct($connection, $systemConfig, $logger); } + public function selectTagUsage(): self { + $this + ->select('systemtag.name', 'systemtag.id', 'systemtag.visibility', 'systemtag.editable') + ->selectAlias($this->createFunction('COUNT(filecache.fileid)'), 'number_files') + ->selectAlias($this->createFunction('MAX(filecache.fileid)'), 'ref_file_id') + ->from('filecache', 'filecache') + ->leftJoin('filecache', 'systemtag_object_mapping', 'systemtagmap', $this->expr()->andX( + $this->expr()->eq('filecache.fileid', $this->expr()->castColumn('systemtagmap.objectid', IQueryBuilder::PARAM_INT)), + $this->expr()->eq('systemtagmap.objecttype', $this->createNamedParameter('files')) + )) + ->leftJoin('systemtagmap', 'systemtag', 'systemtag', $this->expr()->andX( + $this->expr()->eq('systemtag.id', 'systemtagmap.systemtagid'), + $this->expr()->eq('systemtag.visibility', $this->createNamedParameter(true)) + )) + ->where($this->expr()->like('systemtag.name', $this->createNamedParameter('_%'))) + ->groupBy('systemtag.name', 'systemtag.id', 'systemtag.visibility', 'systemtag.editable'); + + return $this; + } + public function selectFileCache(string $alias = null, bool $joinExtendedCache = true) { - $name = $alias ? $alias : 'filecache'; + $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') ->from('filecache', $name); diff --git a/lib/private/Files/Cache/QuerySearchHelper.php b/lib/private/Files/Cache/QuerySearchHelper.php index eba2aac927b..c2eed5688b5 100644 --- a/lib/private/Files/Cache/QuerySearchHelper.php +++ b/lib/private/Files/Cache/QuerySearchHelper.php @@ -74,6 +74,41 @@ class QuerySearchHelper { ); } + protected function applySearchConstraints(CacheQueryBuilder $query, ISearchQuery $searchQuery, array $caches): void { + $storageFilters = array_values(array_map(function (ICache $cache) { + return $cache->getQueryFilterForStorage(); + }, $caches)); + $storageFilter = new SearchBinaryOperator(ISearchBinaryOperator::OPERATOR_OR, $storageFilters); + $filter = new SearchBinaryOperator(ISearchBinaryOperator::OPERATOR_AND, [$searchQuery->getSearchOperation(), $storageFilter]); + $this->queryOptimizer->processOperator($filter); + + $searchExpr = $this->searchBuilder->searchOperatorToDBExpr($query, $filter); + if ($searchExpr) { + $query->andWhere($searchExpr); + } + + $this->searchBuilder->addSearchOrdersToQuery($query, $searchQuery->getOrder()); + + if ($searchQuery->getLimit()) { + $query->setMaxResults($searchQuery->getLimit()); + } + if ($searchQuery->getOffset()) { + $query->setFirstResult($searchQuery->getOffset()); + } + } + + public function findUsedTagsInCaches(ISearchQuery $searchQuery, array $caches): array { + $query = $this->getQueryBuilder(); + $query->selectTagUsage(); + + $this->applySearchConstraints($query, $searchQuery, $caches); + + $result = $query->execute(); + $tags = $result->fetchAll(); + $result->closeCursor(); + return $tags; + } + /** * Perform a file system search in multiple caches * @@ -127,26 +162,7 @@ class QuerySearchHelper { )); } - $storageFilters = array_values(array_map(function (ICache $cache) { - return $cache->getQueryFilterForStorage(); - }, $caches)); - $storageFilter = new SearchBinaryOperator(ISearchBinaryOperator::OPERATOR_OR, $storageFilters); - $filter = new SearchBinaryOperator(ISearchBinaryOperator::OPERATOR_AND, [$searchQuery->getSearchOperation(), $storageFilter]); - $this->queryOptimizer->processOperator($filter); - - $searchExpr = $this->searchBuilder->searchOperatorToDBExpr($builder, $filter); - if ($searchExpr) { - $query->andWhere($searchExpr); - } - - $this->searchBuilder->addSearchOrdersToQuery($query, $searchQuery->getOrder()); - - if ($searchQuery->getLimit()) { - $query->setMaxResults($searchQuery->getLimit()); - } - if ($searchQuery->getOffset()) { - $query->setFirstResult($searchQuery->getOffset()); - } + $this->applySearchConstraints($query, $searchQuery, $caches); $result = $query->execute(); $files = $result->fetchAll(); @@ -158,7 +174,7 @@ class QuerySearchHelper { $result->closeCursor(); // loop through all caches for each result to see if the result matches that storage - // results are grouped by the same array keys as the caches argument to allow the caller to distringuish the source of the results + // results are grouped by the same array keys as the caches argument to allow the caller to distinguish the source of the results $results = array_fill_keys(array_keys($caches), []); foreach ($rawEntries as $rawEntry) { foreach ($caches as $cacheKey => $cache) { diff --git a/lib/private/Files/Node/Folder.php b/lib/private/Files/Node/Folder.php index 90aed642a2d..a34e1315385 100644 --- a/lib/private/Files/Node/Folder.php +++ b/lib/private/Files/Node/Folder.php @@ -203,7 +203,7 @@ class Folder extends Node implements \OCP\Files\Folder { throw new NotPermittedException('No create permission for path'); } - private function queryFromOperator(ISearchOperator $operator, string $uid = null): ISearchQuery { + private function queryFromOperator(ISearchOperator $operator, string $uid = null, int $limit = 0, int $offset = 0): ISearchQuery { if ($uid === null) { $user = null; } else { @@ -211,36 +211,17 @@ class Folder extends Node implements \OCP\Files\Folder { $userManager = \OC::$server->query(IUserManager::class); $user = $userManager->get($uid); } - return new SearchQuery($operator, 0, 0, [], $user); + return new SearchQuery($operator, $limit, $offset, [], $user); } /** - * search for files with the name matching $query - * - * @param string|ISearchQuery $query - * @return \OC\Files\Node\Node[] + * @psalm-return list{0: array, 1: array} */ - public function search($query) { - if (is_string($query)) { - $query = $this->queryFromOperator(new SearchComparison(ISearchComparison::COMPARE_LIKE, 'name', '%' . $query . '%')); - } - - // search is handled by a single query covering all caches that this folder contains - // this is done by collect - - $limitToHome = $query->limitToHome(); - if ($limitToHome && count(explode('/', $this->path)) !== 3) { - throw new \InvalidArgumentException('searching by owner is only allows on the users home folder'); - } - + protected function getCachesAndMountpointsForSearch(bool $limitToHome = false): array { $rootLength = strlen($this->path); $mount = $this->root->getMount($this->path); $storage = $mount->getStorage(); $internalPath = $mount->getInternalPath($this->path); - - // collect all caches for this folder, indexed by their mountpoint relative to this folder - // and save the mount which is needed later to construct the FileInfo objects - if ($internalPath !== '') { // a temporary CacheJail is used to handle filtering down the results to within this folder $caches = ['' => new CacheJail($storage->getCache(''), $internalPath)]; @@ -261,12 +242,36 @@ class Folder extends Node implements \OCP\Files\Folder { } } + return [$caches, $mountByMountPoint]; + } + + /** + * search for files with the name matching $query + * + * @param string|ISearchQuery $query + * @return \OC\Files\Node\Node[] + */ + public function search($query) { + if (is_string($query)) { + $query = $this->queryFromOperator(new SearchComparison(ISearchComparison::COMPARE_LIKE, 'name', '%' . $query . '%')); + } + + // search is handled by a single query covering all caches that this folder contains + // this is done by collect + + $limitToHome = $query->limitToHome(); + if ($limitToHome && count(explode('/', $this->path)) !== 3) { + throw new \InvalidArgumentException('searching by owner is only allows on the users home folder'); + } + + [$caches, $mountByMountPoint] = $this->getCachesAndMountpointsForSearch($limitToHome); + /** @var QuerySearchHelper $searchHelper */ $searchHelper = \OC::$server->get(QuerySearchHelper::class); $resultsPerCache = $searchHelper->searchInCaches($query, $caches); // loop through all results per-cache, constructing the FileInfo object from the CacheEntry and merge them all - $files = array_merge(...array_map(function (array $results, $relativeMountPoint) use ($mountByMountPoint) { + $files = array_merge(...array_map(function (array $results, string $relativeMountPoint) use ($mountByMountPoint) { $mount = $mountByMountPoint[$relativeMountPoint]; return array_map(function (ICacheEntry $result) use ($relativeMountPoint, $mount) { return $this->cacheEntryToFileInfo($mount, $relativeMountPoint, $result); @@ -331,6 +336,17 @@ class Folder extends Node implements \OCP\Files\Folder { return $this->search($query); } + /** + * @return Node[] + */ + public function getSystemTags(string $mediaType, int $limit = 0, int $offset = 0): array { + $query = $this->queryFromOperator(new SearchComparison(ISearchComparison::COMPARE_LIKE, 'mimetype', $mediaType . '/%'), null, $limit, $offset); + [$caches, ] = $this->getCachesAndMountpointsForSearch(); + /** @var QuerySearchHelper $searchHelper */ + $searchHelper = \OCP\Server::get(QuerySearchHelper::class); + return $searchHelper->findUsedTagsInCaches($query, $caches); + } + /** * @param int $id * @return \OC\Files\Node\Node[] -- cgit v1.2.3 From c0dbde5fba48be3b93e52087c4b7e5b59e6377ed Mon Sep 17 00:00:00 2001 From: Arthur Schiwon Date: Thu, 4 May 2023 11:57:07 +0200 Subject: feat: specify media type via url path: systemtags-current/$mediaType - only the media part of the mime type can be search, but not the full mime type. It can be added, should it become necessary. - thus fixes previously hardcoded selector for image/ types - also fixes a return type hint - adds a return type hint Signed-off-by: Arthur Schiwon --- apps/dav/lib/SystemTag/SystemTagsInUseCollection.php | 17 +++++++++++++++-- lib/private/Files/Cache/QuerySearchHelper.php | 4 ++++ lib/private/Files/Node/Folder.php | 11 +++++++++-- 3 files changed, 28 insertions(+), 4 deletions(-) (limited to 'lib') diff --git a/apps/dav/lib/SystemTag/SystemTagsInUseCollection.php b/apps/dav/lib/SystemTag/SystemTagsInUseCollection.php index 938b14e1f65..94a86352a96 100644 --- a/apps/dav/lib/SystemTag/SystemTagsInUseCollection.php +++ b/apps/dav/lib/SystemTag/SystemTagsInUseCollection.php @@ -31,21 +31,34 @@ use OCP\Files\IRootFolder; use OCP\IUserSession; use OCP\SystemTag\ISystemTagManager; use Sabre\DAV\Exception\Forbidden; +use Sabre\DAV\Exception\NotFound; class SystemTagsInUseCollection extends \Sabre\DAV\SimpleCollection { protected IUserSession $userSession; protected IRootFolder $rootFolder; + protected string $mediaType; - public function __construct(IUserSession $userSession, IRootFolder $rootFolder) { + public function __construct(IUserSession $userSession, IRootFolder $rootFolder, string $mediaType = '') { $this->userSession = $userSession; $this->rootFolder = $rootFolder; + $this->mediaType = $mediaType; $this->name = 'systemtags-current'; + if ($this->mediaType != '') { + $this->name .= '/' . $this->mediaType; + } } public function setName($name): void { throw new Forbidden('Permission denied to rename this collection'); } + public function getChild($name) { + if ($this->mediaType !== '') { + throw new NotFound('Invalid media type'); + } + return new self($this->userSession, $this->rootFolder, $name); + } + public function getChildren() { $user = $this->userSession->getUser(); if ($user === null) { @@ -53,7 +66,7 @@ class SystemTagsInUseCollection extends \Sabre\DAV\SimpleCollection { } $userFolder = $this->rootFolder->getUserFolder($user->getUID()); - $result = $userFolder->getSystemTags('image'); + $result = $userFolder->getSystemTags($this->mediaType); $children = []; foreach ($result as $tagData) { $tag = new SystemTag((string)$tagData['id'], $tagData['name'], (bool)$tagData['visibility'], (bool)$tagData['editable']); diff --git a/lib/private/Files/Cache/QuerySearchHelper.php b/lib/private/Files/Cache/QuerySearchHelper.php index c2eed5688b5..af198e9c832 100644 --- a/lib/private/Files/Cache/QuerySearchHelper.php +++ b/lib/private/Files/Cache/QuerySearchHelper.php @@ -97,6 +97,10 @@ class QuerySearchHelper { } } + + /** + * @return array + */ public function findUsedTagsInCaches(ISearchQuery $searchQuery, array $caches): array { $query = $this->getQueryBuilder(); $query->selectTagUsage(); diff --git a/lib/private/Files/Node/Folder.php b/lib/private/Files/Node/Folder.php index a34e1315385..15178758a51 100644 --- a/lib/private/Files/Node/Folder.php +++ b/lib/private/Files/Node/Folder.php @@ -337,10 +337,17 @@ class Folder extends Node implements \OCP\Files\Folder { } /** - * @return Node[] + * + * @return array */ public function getSystemTags(string $mediaType, int $limit = 0, int $offset = 0): array { - $query = $this->queryFromOperator(new SearchComparison(ISearchComparison::COMPARE_LIKE, 'mimetype', $mediaType . '/%'), null, $limit, $offset); + // Currently query has to have exactly one search condition. If no media type is provided, + // we fall back to the presence of a systemtag. + if (empty($mediaType)) { + $query = $this->queryFromOperator(new SearchComparison(ISearchComparison::COMPARE_LIKE, 'systemtag', '%'), null, $limit, $offset); + } else { + $query = $this->queryFromOperator(new SearchComparison(ISearchComparison::COMPARE_LIKE, 'mimetype', $mediaType . '/%'), null, $limit, $offset); + } [$caches, ] = $this->getCachesAndMountpointsForSearch(); /** @var QuerySearchHelper $searchHelper */ $searchHelper = \OCP\Server::get(QuerySearchHelper::class); -- cgit v1.2.3 From f2b5a079db5530cf48be137e5a52a984e27ab3ad Mon Sep 17 00:00:00 2001 From: Arthur Schiwon Date: Fri, 5 May 2023 21:28:09 +0200 Subject: refactor: remove SystemTag logic from Folder into QuerySearchHelper - adds OC\SystemTag\SystemTagsInFilesDetector where the search logic is moved to Signed-off-by: Arthur Schiwon --- .../lib/SystemTag/SystemTagsInUseCollection.php | 10 +++- lib/composer/composer/autoload_classmap.php | 1 + lib/composer/composer/autoload_static.php | 1 + lib/private/Files/Cache/QuerySearchHelper.php | 39 +++++++++++++ lib/private/Files/Node/Folder.php | 53 +---------------- .../SystemTag/SystemTagsInFilesDetector.php | 68 ++++++++++++++++++++++ 6 files changed, 117 insertions(+), 55 deletions(-) create mode 100644 lib/private/SystemTag/SystemTagsInFilesDetector.php (limited to 'lib') diff --git a/apps/dav/lib/SystemTag/SystemTagsInUseCollection.php b/apps/dav/lib/SystemTag/SystemTagsInUseCollection.php index aa45e7d8f07..b57e685e7e7 100644 --- a/apps/dav/lib/SystemTag/SystemTagsInUseCollection.php +++ b/apps/dav/lib/SystemTag/SystemTagsInUseCollection.php @@ -27,6 +27,7 @@ declare(strict_types=1); namespace OCA\DAV\SystemTag; use OC\SystemTag\SystemTag; +use OC\SystemTag\SystemTagsInFilesDetector; use OC\User\NoUserException; use OCP\Files\IRootFolder; use OCP\Files\NotPermittedException; @@ -40,19 +41,22 @@ class SystemTagsInUseCollection extends SimpleCollection { protected IUserSession $userSession; protected IRootFolder $rootFolder; protected string $mediaType; - private ISystemTagManager $systemTagManager; + protected ISystemTagManager $systemTagManager; + protected SystemTagsInFilesDetector $systemTagsInFilesDetector; /** @noinspection PhpMissingParentConstructorInspection */ public function __construct( IUserSession $userSession, IRootFolder $rootFolder, ISystemTagManager $systemTagManager, + SystemTagsInFilesDetector $systemTagsInFilesDetector, string $mediaType = '' ) { $this->userSession = $userSession; $this->rootFolder = $rootFolder; $this->systemTagManager = $systemTagManager; $this->mediaType = $mediaType; + $this->systemTagsInFilesDetector = $systemTagsInFilesDetector; $this->name = 'systemtags-assigned'; if ($this->mediaType != '') { $this->name .= '/' . $this->mediaType; @@ -67,7 +71,7 @@ class SystemTagsInUseCollection extends SimpleCollection { if ($this->mediaType !== '') { throw new NotFound('Invalid media type'); } - return new self($this->userSession, $this->rootFolder, $this->systemTagManager, $name); + return new self($this->userSession, $this->rootFolder, $this->systemTagManager, $this->systemTagsInFilesDetector, $name); } /** @@ -89,7 +93,7 @@ class SystemTagsInUseCollection extends SimpleCollection { throw new Forbidden('Permission denied to read this collection'); } - $result = $userFolder->getSystemTags($this->mediaType); + $result = $this->systemTagsInFilesDetector->detectAssignedSystemTagsIn($userFolder, $this->mediaType); $children = []; foreach ($result as $tagData) { $tag = new SystemTag((string)$tagData['id'], $tagData['name'], (bool)$tagData['visibility'], (bool)$tagData['editable']); diff --git a/lib/composer/composer/autoload_classmap.php b/lib/composer/composer/autoload_classmap.php index 1504d39b9a4..5aae56e2430 100644 --- a/lib/composer/composer/autoload_classmap.php +++ b/lib/composer/composer/autoload_classmap.php @@ -1592,6 +1592,7 @@ return array( 'OC\\SystemTag\\SystemTag' => $baseDir . '/lib/private/SystemTag/SystemTag.php', 'OC\\SystemTag\\SystemTagManager' => $baseDir . '/lib/private/SystemTag/SystemTagManager.php', 'OC\\SystemTag\\SystemTagObjectMapper' => $baseDir . '/lib/private/SystemTag/SystemTagObjectMapper.php', + 'OC\\SystemTag\\SystemTagsInFilesDetector' => $baseDir . '/lib/private/SystemTag/SystemTagsInFilesDetector.php', 'OC\\TagManager' => $baseDir . '/lib/private/TagManager.php', 'OC\\Tagging\\Tag' => $baseDir . '/lib/private/Tagging/Tag.php', 'OC\\Tagging\\TagMapper' => $baseDir . '/lib/private/Tagging/TagMapper.php', diff --git a/lib/composer/composer/autoload_static.php b/lib/composer/composer/autoload_static.php index 842e43eca96..5226ade5254 100644 --- a/lib/composer/composer/autoload_static.php +++ b/lib/composer/composer/autoload_static.php @@ -1625,6 +1625,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2 'OC\\SystemTag\\SystemTag' => __DIR__ . '/../../..' . '/lib/private/SystemTag/SystemTag.php', 'OC\\SystemTag\\SystemTagManager' => __DIR__ . '/../../..' . '/lib/private/SystemTag/SystemTagManager.php', 'OC\\SystemTag\\SystemTagObjectMapper' => __DIR__ . '/../../..' . '/lib/private/SystemTag/SystemTagObjectMapper.php', + 'OC\\SystemTag\\SystemTagsInFilesDetector' => __DIR__ . '/../../..' . '/lib/private/SystemTag/SystemTagsInFilesDetector.php', 'OC\\TagManager' => __DIR__ . '/../../..' . '/lib/private/TagManager.php', 'OC\\Tagging\\Tag' => __DIR__ . '/../../..' . '/lib/private/Tagging/Tag.php', 'OC\\Tagging\\TagMapper' => __DIR__ . '/../../..' . '/lib/private/Tagging/TagMapper.php', diff --git a/lib/private/Files/Cache/QuerySearchHelper.php b/lib/private/Files/Cache/QuerySearchHelper.php index af198e9c832..f540f2dcb65 100644 --- a/lib/private/Files/Cache/QuerySearchHelper.php +++ b/lib/private/Files/Cache/QuerySearchHelper.php @@ -25,13 +25,18 @@ */ namespace OC\Files\Cache; +use OC\Files\Cache\Wrapper\CacheJail; +use OC\Files\Node\Root; use OC\Files\Search\QueryOptimizer\QueryOptimizer; use OC\Files\Search\SearchBinaryOperator; use OC\SystemConfig; use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\Files\Cache\ICache; use OCP\Files\Cache\ICacheEntry; +use OCP\Files\Folder; use OCP\Files\IMimeTypeLoader; +use OCP\Files\IRootFolder; +use OCP\Files\Mount\IMountPoint; use OCP\Files\Search\ISearchBinaryOperator; use OCP\Files\Search\ISearchQuery; use OCP\IDBConnection; @@ -190,4 +195,38 @@ class QuerySearchHelper { } return $results; } + + /** + * @return array{array, array} + */ + public function getCachesAndMountPointsForSearch(Root $root, string $path, bool $limitToHome = false): array { + $rootLength = strlen($path); + $mount = $root->getMount($path); + $storage = $mount->getStorage(); + $internalPath = $mount->getInternalPath($path); + + if ($internalPath !== '') { + // a temporary CacheJail is used to handle filtering down the results to within this folder + $caches = ['' => new CacheJail($storage->getCache(''), $internalPath)]; + } else { + $caches = ['' => $storage->getCache('')]; + } + $mountByMountPoint = ['' => $mount]; + + if (!$limitToHome) { + /** @var IMountPoint[] $mounts */ + $mounts = $root->getMountsIn($path); + foreach ($mounts as $mount) { + $storage = $mount->getStorage(); + if ($storage) { + $relativeMountPoint = ltrim(substr($mount->getMountPoint(), $rootLength), '/'); + $caches[$relativeMountPoint] = $storage->getCache(''); + $mountByMountPoint[$relativeMountPoint] = $mount; + } + } + } + + return [$caches, $mountByMountPoint]; + } + } diff --git a/lib/private/Files/Node/Folder.php b/lib/private/Files/Node/Folder.php index 15178758a51..0639829738d 100644 --- a/lib/private/Files/Node/Folder.php +++ b/lib/private/Files/Node/Folder.php @@ -32,7 +32,6 @@ namespace OC\Files\Node; use OC\Files\Cache\QuerySearchHelper; use OC\Files\Search\SearchBinaryOperator; -use OC\Files\Cache\Wrapper\CacheJail; use OC\Files\Search\SearchComparison; use OC\Files\Search\SearchOrder; use OC\Files\Search\SearchQuery; @@ -214,37 +213,6 @@ class Folder extends Node implements \OCP\Files\Folder { return new SearchQuery($operator, $limit, $offset, [], $user); } - /** - * @psalm-return list{0: array, 1: array} - */ - protected function getCachesAndMountpointsForSearch(bool $limitToHome = false): array { - $rootLength = strlen($this->path); - $mount = $this->root->getMount($this->path); - $storage = $mount->getStorage(); - $internalPath = $mount->getInternalPath($this->path); - if ($internalPath !== '') { - // a temporary CacheJail is used to handle filtering down the results to within this folder - $caches = ['' => new CacheJail($storage->getCache(''), $internalPath)]; - } else { - $caches = ['' => $storage->getCache('')]; - } - $mountByMountPoint = ['' => $mount]; - - if (!$limitToHome) { - $mounts = $this->root->getMountsIn($this->path); - foreach ($mounts as $mount) { - $storage = $mount->getStorage(); - if ($storage) { - $relativeMountPoint = ltrim(substr($mount->getMountPoint(), $rootLength), '/'); - $caches[$relativeMountPoint] = $storage->getCache(''); - $mountByMountPoint[$relativeMountPoint] = $mount; - } - } - } - - return [$caches, $mountByMountPoint]; - } - /** * search for files with the name matching $query * @@ -264,10 +232,9 @@ class Folder extends Node implements \OCP\Files\Folder { throw new \InvalidArgumentException('searching by owner is only allows on the users home folder'); } - [$caches, $mountByMountPoint] = $this->getCachesAndMountpointsForSearch($limitToHome); - /** @var QuerySearchHelper $searchHelper */ $searchHelper = \OC::$server->get(QuerySearchHelper::class); + [$caches, $mountByMountPoint] = $searchHelper->getCachesAndMountPointsForSearch($this->root, $this->path, $limitToHome); $resultsPerCache = $searchHelper->searchInCaches($query, $caches); // loop through all results per-cache, constructing the FileInfo object from the CacheEntry and merge them all @@ -336,24 +303,6 @@ class Folder extends Node implements \OCP\Files\Folder { return $this->search($query); } - /** - * - * @return array - */ - public function getSystemTags(string $mediaType, int $limit = 0, int $offset = 0): array { - // Currently query has to have exactly one search condition. If no media type is provided, - // we fall back to the presence of a systemtag. - if (empty($mediaType)) { - $query = $this->queryFromOperator(new SearchComparison(ISearchComparison::COMPARE_LIKE, 'systemtag', '%'), null, $limit, $offset); - } else { - $query = $this->queryFromOperator(new SearchComparison(ISearchComparison::COMPARE_LIKE, 'mimetype', $mediaType . '/%'), null, $limit, $offset); - } - [$caches, ] = $this->getCachesAndMountpointsForSearch(); - /** @var QuerySearchHelper $searchHelper */ - $searchHelper = \OCP\Server::get(QuerySearchHelper::class); - return $searchHelper->findUsedTagsInCaches($query, $caches); - } - /** * @param int $id * @return \OC\Files\Node\Node[] diff --git a/lib/private/SystemTag/SystemTagsInFilesDetector.php b/lib/private/SystemTag/SystemTagsInFilesDetector.php new file mode 100644 index 00000000000..0fcd7e051f6 --- /dev/null +++ b/lib/private/SystemTag/SystemTagsInFilesDetector.php @@ -0,0 +1,68 @@ + + * + * @author Arthur Schiwon + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\SystemTag; + +use OC\Files\Cache\QuerySearchHelper; +use OC\Files\Node\Root; +use OC\Files\Search\SearchComparison; +use OC\Files\Search\SearchQuery; +use OCP\Files\Folder; +use OCP\Files\Search\ISearchComparison; + +class SystemTagsInFilesDetector { + public function __construct(protected QuerySearchHelper $searchHelper) { + } + + public function detectAssignedSystemTagsIn( + Folder $folder, + string $filteredMediaType = '', + int $limit = 0, + int $offset = 0 + ): array { + // Currently query has to have exactly one search condition. If no media type is provided, + // we fall back to the presence of a system tag. + if (empty($filteredMediaType)) { + $query = new SearchQuery(new SearchComparison(ISearchComparison::COMPARE_LIKE, 'systemtag', '%'), $limit, $offset, []); + } else { + $query = new SearchQuery(new SearchComparison(ISearchComparison::COMPARE_LIKE, 'mimetype', $filteredMediaType . '/%'), $limit, $offset, []); + } + [$caches, ] = $this->searchHelper->getCachesAndMountPointsForSearch( + $this->getRootFolder($folder), + $folder->getPath(), + ); + return $this->searchHelper->findUsedTagsInCaches($query, $caches); + } + + protected function getRootFolder(?Folder $folder): Root { + if ($folder instanceof Root) { + return $folder; + } elseif ($folder === null) { + throw new \LogicException('Could not climb up to root folder'); + } + return $this->getRootFolder($folder->getParent()); + } +} -- cgit v1.2.3 From 298af3cf0acc0546668314d09717cca409100106 Mon Sep 17 00:00:00 2001 From: Arthur Schiwon Date: Wed, 10 May 2023 18:40:44 +0200 Subject: refactor: remove where specification from SELECT getter - search constraints are now fully in control of SystemTagsInFilesDetector::detectAssignedSystemTagsIn(), avoids duplication of a WHERE statement Signed-off-by: Arthur Schiwon --- lib/private/Files/Cache/CacheQueryBuilder.php | 1 - lib/private/Files/Cache/QuerySearchHelper.php | 2 -- lib/private/SystemTag/SystemTagsInFilesDetector.php | 12 ++++++++---- 3 files changed, 8 insertions(+), 7 deletions(-) (limited to 'lib') diff --git a/lib/private/Files/Cache/CacheQueryBuilder.php b/lib/private/Files/Cache/CacheQueryBuilder.php index c5563750c4d..34d2177b84e 100644 --- a/lib/private/Files/Cache/CacheQueryBuilder.php +++ b/lib/private/Files/Cache/CacheQueryBuilder.php @@ -55,7 +55,6 @@ class CacheQueryBuilder extends QueryBuilder { $this->expr()->eq('systemtag.id', 'systemtagmap.systemtagid'), $this->expr()->eq('systemtag.visibility', $this->createNamedParameter(true)) )) - ->where($this->expr()->like('systemtag.name', $this->createNamedParameter('_%'))) ->groupBy('systemtag.name', 'systemtag.id', 'systemtag.visibility', 'systemtag.editable'); return $this; diff --git a/lib/private/Files/Cache/QuerySearchHelper.php b/lib/private/Files/Cache/QuerySearchHelper.php index f540f2dcb65..30b3c7225ac 100644 --- a/lib/private/Files/Cache/QuerySearchHelper.php +++ b/lib/private/Files/Cache/QuerySearchHelper.php @@ -35,7 +35,6 @@ use OCP\Files\Cache\ICache; use OCP\Files\Cache\ICacheEntry; use OCP\Files\Folder; use OCP\Files\IMimeTypeLoader; -use OCP\Files\IRootFolder; use OCP\Files\Mount\IMountPoint; use OCP\Files\Search\ISearchBinaryOperator; use OCP\Files\Search\ISearchQuery; @@ -228,5 +227,4 @@ class QuerySearchHelper { return [$caches, $mountByMountPoint]; } - } diff --git a/lib/private/SystemTag/SystemTagsInFilesDetector.php b/lib/private/SystemTag/SystemTagsInFilesDetector.php index 0fcd7e051f6..c9f26c58c02 100644 --- a/lib/private/SystemTag/SystemTagsInFilesDetector.php +++ b/lib/private/SystemTag/SystemTagsInFilesDetector.php @@ -28,9 +28,11 @@ namespace OC\SystemTag; use OC\Files\Cache\QuerySearchHelper; use OC\Files\Node\Root; +use OC\Files\Search\SearchBinaryOperator; use OC\Files\Search\SearchComparison; use OC\Files\Search\SearchQuery; use OCP\Files\Folder; +use OCP\Files\Search\ISearchBinaryOperator; use OCP\Files\Search\ISearchComparison; class SystemTagsInFilesDetector { @@ -43,13 +45,15 @@ class SystemTagsInFilesDetector { int $limit = 0, int $offset = 0 ): array { + $operator = new SearchComparison(ISearchComparison::COMPARE_LIKE, 'systemtag', '%'); // Currently query has to have exactly one search condition. If no media type is provided, // we fall back to the presence of a system tag. - if (empty($filteredMediaType)) { - $query = new SearchQuery(new SearchComparison(ISearchComparison::COMPARE_LIKE, 'systemtag', '%'), $limit, $offset, []); - } else { - $query = new SearchQuery(new SearchComparison(ISearchComparison::COMPARE_LIKE, 'mimetype', $filteredMediaType . '/%'), $limit, $offset, []); + if ($filteredMediaType !== '') { + $mimeOperator = new SearchComparison(ISearchComparison::COMPARE_LIKE, 'mimetype', $filteredMediaType . '/%'); + $operator = new SearchBinaryOperator(ISearchBinaryOperator::OPERATOR_AND, [$operator, $mimeOperator]); } + + $query = new SearchQuery($operator, $limit, $offset, []); [$caches, ] = $this->searchHelper->getCachesAndMountPointsForSearch( $this->getRootFolder($folder), $folder->getPath(), -- cgit v1.2.3