diff options
Diffstat (limited to 'lib/private/Files')
-rw-r--r-- | lib/private/Files/Cache/Cache.php | 43 | ||||
-rw-r--r-- | lib/private/Files/Cache/FailedCache.php | 5 | ||||
-rw-r--r-- | lib/private/Files/Cache/QuerySearchHelper.php | 160 | ||||
-rw-r--r-- | lib/private/Files/Cache/Wrapper/CacheJail.php | 6 | ||||
-rw-r--r-- | lib/private/Files/Cache/Wrapper/CacheWrapper.php | 6 | ||||
-rw-r--r-- | lib/private/Files/Node/Folder.php | 9 | ||||
-rw-r--r-- | lib/private/Files/Search/SearchBinaryOperator.php | 57 | ||||
-rw-r--r-- | lib/private/Files/Search/SearchComparison.php | 67 | ||||
-rw-r--r-- | lib/private/Files/Search/SearchOrder.php | 57 | ||||
-rw-r--r-- | lib/private/Files/Search/SearchQuery.php | 80 |
10 files changed, 481 insertions, 9 deletions
diff --git a/lib/private/Files/Cache/Cache.php b/lib/private/Files/Cache/Cache.php index 7e7ebd795a0..b0527d801d6 100644 --- a/lib/private/Files/Cache/Cache.php +++ b/lib/private/Files/Cache/Cache.php @@ -36,9 +36,11 @@ namespace OC\Files\Cache; +use Doctrine\DBAL\Driver\Statement; use OCP\Files\Cache\ICache; use OCP\Files\Cache\ICacheEntry; use \OCP\Files\IMimeTypeLoader; +use OCP\Files\Search\ISearchQuery; use OCP\IDBConnection; /** @@ -79,6 +81,9 @@ class Cache implements ICache { */ protected $connection; + /** @var QuerySearchHelper */ + protected $querySearchHelper; + /** * @param \OC\Files\Storage\Storage|string $storage */ @@ -95,6 +100,7 @@ class Cache implements ICache { $this->storageCache = new Storage($storage); $this->mimetypeLoader = \OC::$server->getMimeTypeLoader(); $this->connection = \OC::$server->getDatabaseConnection(); + $this->querySearchHelper = new QuerySearchHelper($this->mimetypeLoader); } /** @@ -350,7 +356,7 @@ class Cache implements ICache { $queryParts[] = '`mtime`'; } } elseif ($name === 'encrypted') { - if(isset($data['encryptedVersion'])) { + if (isset($data['encryptedVersion'])) { $value = $data['encryptedVersion']; } else { // Boolean to integer conversion @@ -599,9 +605,17 @@ class Cache implements ICache { [$this->getNumericStorageId(), $pattern] ); + return $this->searchResultToCacheEntries($result); + } + + /** + * @param Statement $result + * @return CacheEntry[] + */ + private function searchResultToCacheEntries(Statement $result) { $files = $result->fetchAll(); - return array_map(function(array $data) { + return array_map(function (array $data) { return self::cacheEntryFromData($data, $this->mimetypeLoader); }, $files); } @@ -624,14 +638,29 @@ class Cache implements ICache { $mimetype = $this->mimetypeLoader->getId($mimetype); $result = $this->connection->executeQuery($sql, array($mimetype, $this->getNumericStorageId())); - $files = $result->fetchAll(); + return $this->searchResultToCacheEntries($result); + } - return array_map(function (array $data) { - return self::cacheEntryFromData($data, $this->mimetypeLoader); - }, $files); + public function searchQuery(ISearchQuery $searchQuery) { + $builder = \OC::$server->getDatabaseConnection()->getQueryBuilder(); + + $query = $builder->select(['fileid', 'storage', 'path', 'parent', 'name', 'mimetype', 'mimepart', 'size', 'mtime', 'storage_mtime', 'encrypted', 'etag', 'permissions', 'checksum']) + ->from('filecache') + ->where($builder->expr()->eq('storage', $builder->createNamedParameter($this->getNumericStorageId()))) + ->andWhere($this->querySearchHelper->searchOperatorToDBExpr($builder, $searchQuery->getSearchOperation())); + + if ($searchQuery->getLimit()) { + $query->setMaxResults($searchQuery->getLimit()); + } + if ($searchQuery->getOffset()) { + $query->setFirstResult($searchQuery->getOffset()); + } + + $result = $query->execute(); + return $this->searchResultToCacheEntries($result); } - /** + /** * Search for files by tag of a given users. * * Note that every user can tag files differently. diff --git a/lib/private/Files/Cache/FailedCache.php b/lib/private/Files/Cache/FailedCache.php index 3a0424b5e26..932a5e5181a 100644 --- a/lib/private/Files/Cache/FailedCache.php +++ b/lib/private/Files/Cache/FailedCache.php @@ -24,6 +24,7 @@ namespace OC\Files\Cache; use OCP\Constants; use OCP\Files\Cache\ICache; +use OCP\Files\Search\ISearchQuery; /** * Storage placeholder to represent a missing precondition, storage unavailable @@ -125,6 +126,10 @@ class FailedCache implements ICache { return []; } + public function searchQuery(ISearchQuery $query) { + return []; + } + public function getAll() { return []; } diff --git a/lib/private/Files/Cache/QuerySearchHelper.php b/lib/private/Files/Cache/QuerySearchHelper.php new file mode 100644 index 00000000000..931f258ec5b --- /dev/null +++ b/lib/private/Files/Cache/QuerySearchHelper.php @@ -0,0 +1,160 @@ +<?php +/** + * @copyright Copyright (c) 2017 Robin Appelman <robin@icewind.nl> + * + * @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 <http://www.gnu.org/licenses/>. + * + */ + +namespace OC\Files\Cache; + +use OCP\DB\QueryBuilder\IQueryBuilder; +use OCP\Files\IMimeTypeLoader; +use OCP\Files\Search\ISearchBinaryOperator; +use OCP\Files\Search\ISearchComparison; +use OCP\Files\Search\ISearchOperator; + +/** + * Tools for transforming search queries into database queries + */ +class QuerySearchHelper { + static protected $searchOperatorMap = [ + ISearchComparison::COMPARE_LIKE => 'iLike', + ISearchComparison::COMPARE_EQUAL => 'eq', + ISearchComparison::COMPARE_GREATER_THAN => 'gt', + ISearchComparison::COMPARE_GREATER_THAN_EQUAL => 'gte', + ISearchComparison::COMPARE_LESS_THAN => 'lt', + ISearchComparison::COMPARE_LESS_THAN_EQUAL => 'lte' + ]; + + static protected $searchOperatorNegativeMap = [ + ISearchComparison::COMPARE_LIKE => 'notLike', + ISearchComparison::COMPARE_EQUAL => 'neq', + ISearchComparison::COMPARE_GREATER_THAN => 'lte', + ISearchComparison::COMPARE_GREATER_THAN_EQUAL => 'lt', + ISearchComparison::COMPARE_LESS_THAN => 'gte', + ISearchComparison::COMPARE_LESS_THAN_EQUAL => 'lt' + ]; + + /** @var IMimeTypeLoader */ + private $mimetypeLoader; + + /** + * QuerySearchUtil constructor. + * + * @param IMimeTypeLoader $mimetypeLoader + */ + public function __construct(IMimeTypeLoader $mimetypeLoader) { + $this->mimetypeLoader = $mimetypeLoader; + } + + public function searchOperatorToDBExpr(IQueryBuilder $builder, ISearchOperator $operator) { + $expr = $builder->expr(); + if ($operator instanceof ISearchBinaryOperator) { + switch ($operator->getType()) { + case ISearchBinaryOperator::OPERATOR_NOT: + $negativeOperator = $operator->getArguments()[0]; + if ($negativeOperator instanceof ISearchComparison) { + return $this->searchComparisonToDBExpr($builder, $negativeOperator, self::$searchOperatorNegativeMap); + } else { + throw new \InvalidArgumentException('Binary operators inside "not" is not supported'); + } + case ISearchBinaryOperator::OPERATOR_AND: + return $expr->andX($this->searchOperatorToDBExpr($builder, $operator->getArguments()[0]), $this->searchOperatorToDBExpr($builder, $operator->getArguments()[1])); + case ISearchBinaryOperator::OPERATOR_OR: + return $expr->orX($this->searchOperatorToDBExpr($builder, $operator->getArguments()[0]), $this->searchOperatorToDBExpr($builder, $operator->getArguments()[1])); + default: + throw new \InvalidArgumentException('Invalid operator type: ' . $operator->getType()); + } + } else if ($operator instanceof ISearchComparison) { + return $this->searchComparisonToDBExpr($builder, $operator, self::$searchOperatorMap); + } else { + throw new \InvalidArgumentException('Invalid operator type: ' . get_class($operator)); + } + } + + private function searchComparisonToDBExpr(IQueryBuilder $builder, ISearchComparison $comparison, array $operatorMap) { + $this->validateComparison($comparison); + + list($field, $value, $type) = $this->getOperatorFieldAndValue($comparison); + if (isset($operatorMap[$type])) { + $queryOperator = $operatorMap[$type]; + return $builder->expr()->$queryOperator($field, $this->getParameterForValue($builder, $value)); + } else { + throw new \InvalidArgumentException('Invalid operator type: ' . $comparison->getType()); + } + } + + private function getOperatorFieldAndValue(ISearchComparison $operator) { + $field = $operator->getField(); + $value = $operator->getValue(); + $type = $operator->getType(); + if ($field === 'mimetype') { + if ($operator->getType() === ISearchComparison::COMPARE_EQUAL) { + $value = $this->mimetypeLoader->getId($value); + } else if ($operator->getType() === ISearchComparison::COMPARE_LIKE) { + // transform "mimetype='foo/%'" to "mimepart='foo'" + if (preg_match('|(.+)/%|', $value, $matches)) { + $field = 'mimepart'; + $value = $this->mimetypeLoader->getId($matches[1]); + $type = ISearchComparison::COMPARE_EQUAL; + } + if (strpos($value, '%') !== false) { + throw new \InvalidArgumentException('Unsupported query value for mimetype: ' . $value . ', only values in the format "mime/type" or "mime/%" are supported'); + } + } + } + return [$field, $value, $type]; + } + + private function validateComparison(ISearchComparison $operator) { + $types = [ + 'mimetype' => 'string', + 'mtime' => 'integer', + 'name' => 'string', + 'size' => 'integer' + ]; + $comparisons = [ + 'mimetype' => ['eq', 'like'], + 'mtime' => ['eq', 'gt', 'lt', 'gte', 'lte'], + 'name' => ['eq', 'like'], + 'size' => ['eq', 'gt', 'lt', 'gte', 'lte'] + ]; + + if (!isset($types[$operator->getField()])) { + throw new \InvalidArgumentException('Unsupported comparison field ' . $operator->getField()); + } + $type = $types[$operator->getField()]; + if (gettype($operator->getValue()) !== $type) { + throw new \InvalidArgumentException('Invalid type for field ' . $operator->getField()); + } + if (!in_array($operator->getType(), $comparisons[$operator->getField()])) { + throw new \InvalidArgumentException('Unsupported comparison for field ' . $operator->getField() . ': ' . $operator->getType()); + } + } + + private function getParameterForValue(IQueryBuilder $builder, $value) { + if ($value instanceof \DateTime) { + $value = $value->getTimestamp(); + } + if (is_numeric($value)) { + $type = IQueryBuilder::PARAM_INT; + } else { + $type = IQueryBuilder::PARAM_STR; + } + return $builder->createNamedParameter($value, $type); + } +} diff --git a/lib/private/Files/Cache/Wrapper/CacheJail.php b/lib/private/Files/Cache/Wrapper/CacheJail.php index 894fbcc803d..ebab20fbaed 100644 --- a/lib/private/Files/Cache/Wrapper/CacheJail.php +++ b/lib/private/Files/Cache/Wrapper/CacheJail.php @@ -28,6 +28,7 @@ namespace OC\Files\Cache\Wrapper; use OC\Files\Cache\Cache; use OCP\Files\Cache\ICacheEntry; +use OCP\Files\Search\ISearchQuery; /** * Jail to a subdirectory of the wrapped cache @@ -218,6 +219,11 @@ class CacheJail extends CacheWrapper { return $this->formatSearchResults($results); } + public function searchQuery(ISearchQuery $query) { + $results = $this->getCache()->searchQuery($query); + return $this->formatSearchResults($results); + } + /** * search for files by mimetype * diff --git a/lib/private/Files/Cache/Wrapper/CacheWrapper.php b/lib/private/Files/Cache/Wrapper/CacheWrapper.php index 83fe7e5f43e..1463d1467b8 100644 --- a/lib/private/Files/Cache/Wrapper/CacheWrapper.php +++ b/lib/private/Files/Cache/Wrapper/CacheWrapper.php @@ -31,6 +31,7 @@ namespace OC\Files\Cache\Wrapper; use OC\Files\Cache\Cache; use OCP\Files\Cache\ICacheEntry; use OCP\Files\Cache\ICache; +use OCP\Files\Search\ISearchQuery; class CacheWrapper extends Cache { /** @@ -229,6 +230,11 @@ class CacheWrapper extends Cache { return array_map(array($this, 'formatCacheEntry'), $results); } + public function searchQuery(ISearchQuery $query) { + $results = $this->getCache()->searchQuery($query); + return array_map(array($this, 'formatCacheEntry'), $results); + } + /** * search for files by tag * diff --git a/lib/private/Files/Node/Folder.php b/lib/private/Files/Node/Folder.php index fd907f708f3..45372d0fedf 100644 --- a/lib/private/Files/Node/Folder.php +++ b/lib/private/Files/Node/Folder.php @@ -33,6 +33,7 @@ use OCP\Files\FileInfo; use OCP\Files\Mount\IMountPoint; use OCP\Files\NotFoundException; use OCP\Files\NotPermittedException; +use OCP\Files\Search\ISearchOperator; class Folder extends Node implements \OCP\Files\Folder { /** @@ -190,11 +191,15 @@ class Folder extends Node implements \OCP\Files\Folder { /** * search for files with the name matching $query * - * @param string $query + * @param string|ISearchOperator $query * @return \OC\Files\Node\Node[] */ public function search($query) { - return $this->searchCommon('search', array('%' . $query . '%')); + if (is_string($query)) { + return $this->searchCommon('search', array('%' . $query . '%')); + } else { + return $this->searchCommon('searchQuery', array($query)); + } } /** diff --git a/lib/private/Files/Search/SearchBinaryOperator.php b/lib/private/Files/Search/SearchBinaryOperator.php new file mode 100644 index 00000000000..c9466d8b9ea --- /dev/null +++ b/lib/private/Files/Search/SearchBinaryOperator.php @@ -0,0 +1,57 @@ +<?php +/** + * @copyright Copyright (c) 2017 Robin Appelman <robin@icewind.nl> + * + * @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 <http://www.gnu.org/licenses/>. + * + */ + +namespace OC\Files\Search; + +use OCP\Files\Search\ISearchBinaryOperator; +use OCP\Files\Search\ISearchOperator; + +class SearchBinaryOperator implements ISearchBinaryOperator { + /** @var string */ + private $type; + /** @var ISearchOperator[] */ + private $arguments; + + /** + * SearchBinaryOperator constructor. + * + * @param string $type + * @param ISearchOperator[] $arguments + */ + public function __construct($type, array $arguments) { + $this->type = $type; + $this->arguments = $arguments; + } + + /** + * @return string + */ + public function getType() { + return $this->type; + } + + /** + * @return ISearchOperator[] + */ + public function getArguments() { + return $this->arguments; + } +} diff --git a/lib/private/Files/Search/SearchComparison.php b/lib/private/Files/Search/SearchComparison.php new file mode 100644 index 00000000000..32c4ad0e5aa --- /dev/null +++ b/lib/private/Files/Search/SearchComparison.php @@ -0,0 +1,67 @@ +<?php +/** + * @copyright Copyright (c) 2017 Robin Appelman <robin@icewind.nl> + * + * @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 <http://www.gnu.org/licenses/>. + * + */ + +namespace OC\Files\Search; + +use OCP\Files\Search\ISearchComparison; + +class SearchComparison implements ISearchComparison { + /** @var string */ + private $type; + /** @var string */ + private $field; + /** @var string|integer|\DateTime */ + private $value; + + /** + * SearchComparison constructor. + * + * @param string $type + * @param string $field + * @param \DateTime|int|string $value + */ + public function __construct($type, $field, $value) { + $this->type = $type; + $this->field = $field; + $this->value = $value; + } + + /** + * @return string + */ + public function getType() { + return $this->type; + } + + /** + * @return string + */ + public function getField() { + return $this->field; + } + + /** + * @return \DateTime|int|string + */ + public function getValue() { + return $this->value; + } +} diff --git a/lib/private/Files/Search/SearchOrder.php b/lib/private/Files/Search/SearchOrder.php new file mode 100644 index 00000000000..c76d6f2e25e --- /dev/null +++ b/lib/private/Files/Search/SearchOrder.php @@ -0,0 +1,57 @@ +<?php +/** + * @copyright Copyright (c) 2017 Robin Appelman <robin@icewind.nl> + * + * @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 <http://www.gnu.org/licenses/>. + * + */ + +namespace OC\Files\Search; + + +use OCP\Files\Search\ISearchOrder; + +class SearchOrder implements ISearchOrder { + /** @var string */ + private $direction; + /** @var string */ + private $field; + + /** + * SearchOrder constructor. + * + * @param string $direction + * @param string $field + */ + public function __construct($direction, $field) { + $this->direction = $direction; + $this->field = $field; + } + + /** + * @return string + */ + public function getDirection() { + return $this->direction; + } + + /** + * @return string + */ + public function getField() { + return $this->field; + } +} diff --git a/lib/private/Files/Search/SearchQuery.php b/lib/private/Files/Search/SearchQuery.php new file mode 100644 index 00000000000..8a0478ae98e --- /dev/null +++ b/lib/private/Files/Search/SearchQuery.php @@ -0,0 +1,80 @@ +<?php +/** + * @copyright Copyright (c) 2017 Robin Appelman <robin@icewind.nl> + * + * @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 <http://www.gnu.org/licenses/>. + * + */ + +namespace OC\Files\Search; + +use OCP\Files\Search\ISearchOperator; +use OCP\Files\Search\ISearchOrder; +use OCP\Files\Search\ISearchQuery; + +class SearchQuery implements ISearchQuery { + /** @var ISearchOperator */ + private $searchOperation; + /** @var integer */ + private $limit; + /** @var integer */ + private $offset; + /** @var ISearchOrder[] */ + private $order; + + /** + * SearchQuery constructor. + * + * @param ISearchOperator $searchOperation + * @param int $limit + * @param int $offset + * @param array $order + */ + public function __construct(ISearchOperator $searchOperation, $limit, $offset, array $order) { + $this->searchOperation = $searchOperation; + $this->limit = $limit; + $this->offset = $offset; + $this->order = $order; + } + + /** + * @return ISearchOperator + */ + public function getSearchOperation() { + return $this->searchOperation; + } + + /** + * @return int + */ + public function getLimit() { + return $this->limit; + } + + /** + * @return int + */ + public function getOffset() { + return $this->offset; + } + + /** + * @return ISearchOrder[] + */ + public function getOrder() { + return $this->order; + } +} |