Compare commits
6 Commits
master
...
single-que
Author | SHA1 | Date |
---|---|---|
Robin Appelman | acf974c1cb | |
Robin Appelman | 139fac9526 | |
Robin Appelman | 056996dfb3 | |
Robin Appelman | e576d0da68 | |
Robin Appelman | c3277095c7 | |
Robin Appelman | d2e5f084c8 |
|
@ -31,8 +31,13 @@ namespace OCA\Files_Sharing;
|
||||||
|
|
||||||
use OC\Files\Cache\FailedCache;
|
use OC\Files\Cache\FailedCache;
|
||||||
use OC\Files\Cache\Wrapper\CacheJail;
|
use OC\Files\Cache\Wrapper\CacheJail;
|
||||||
|
use OC\Files\Search\SearchBinaryOperator;
|
||||||
|
use OC\Files\Search\SearchComparison;
|
||||||
use OC\Files\Storage\Wrapper\Jail;
|
use OC\Files\Storage\Wrapper\Jail;
|
||||||
use OCP\Files\Cache\ICacheEntry;
|
use OCP\Files\Cache\ICacheEntry;
|
||||||
|
use OCP\Files\Search\ISearchBinaryOperator;
|
||||||
|
use OCP\Files\Search\ISearchComparison;
|
||||||
|
use OCP\Files\Search\ISearchOperator;
|
||||||
use OCP\Files\StorageNotAvailableException;
|
use OCP\Files\StorageNotAvailableException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -182,19 +187,19 @@ class Cache extends CacheJail {
|
||||||
// Not a valid action for Shared Cache
|
// Not a valid action for Shared Cache
|
||||||
}
|
}
|
||||||
|
|
||||||
public function search($pattern) {
|
public function getQueryFilterForStorage(): ISearchOperator {
|
||||||
// Do the normal search on the whole storage for non files
|
// Do the normal jail behavior for non files
|
||||||
if ($this->storage->getItemType() !== 'file') {
|
if ($this->storage->getItemType() !== 'file') {
|
||||||
return parent::search($pattern);
|
return parent::getQueryFilterForStorage();
|
||||||
}
|
}
|
||||||
|
|
||||||
$regex = '/' . str_replace('%', '.*', $pattern) . '/i';
|
// for single file shares we don't need to do the LIKE
|
||||||
|
return new SearchBinaryOperator(
|
||||||
$data = $this->get('');
|
ISearchBinaryOperator::OPERATOR_AND,
|
||||||
if (preg_match($regex, $data->getName()) === 1) {
|
[
|
||||||
return [$data];
|
\OC\Files\Cache\Cache::getQueryFilterForStorage(),
|
||||||
}
|
new SearchComparison(ISearchComparison::COMPARE_EQUAL, 'path', $this->getGetUnjailedRoot()),
|
||||||
|
]
|
||||||
return [];
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -989,8 +989,7 @@ class Trashbin {
|
||||||
$query = new CacheQueryBuilder(
|
$query = new CacheQueryBuilder(
|
||||||
\OC::$server->getDatabaseConnection(),
|
\OC::$server->getDatabaseConnection(),
|
||||||
\OC::$server->getSystemConfig(),
|
\OC::$server->getSystemConfig(),
|
||||||
\OC::$server->getLogger(),
|
\OC::$server->getLogger()
|
||||||
$cache
|
|
||||||
);
|
);
|
||||||
$normalizedParentPath = ltrim(Filesystem::normalizePath(dirname('files_trashbin/versions/'. $filename)), '/');
|
$normalizedParentPath = ltrim(Filesystem::normalizePath(dirname('files_trashbin/versions/'. $filename)), '/');
|
||||||
$parentId = $cache->getId($normalizedParentPath);
|
$parentId = $cache->getId($normalizedParentPath);
|
||||||
|
@ -999,7 +998,7 @@ class Trashbin {
|
||||||
}
|
}
|
||||||
|
|
||||||
$query->selectFileCache()
|
$query->selectFileCache()
|
||||||
->whereStorageId()
|
->whereStorageId($cache->getNumericStorageId())
|
||||||
->andWhere($query->expr()->eq('parent', $query->createNamedParameter($parentId)))
|
->andWhere($query->expr()->eq('parent', $query->createNamedParameter($parentId)))
|
||||||
->andWhere($query->expr()->iLike('name', $query->createNamedParameter($pattern)));
|
->andWhere($query->expr()->iLike('name', $query->createNamedParameter($pattern)));
|
||||||
|
|
||||||
|
|
|
@ -1057,6 +1057,7 @@ return array(
|
||||||
'OC\\Files\\Cache\\Propagator' => $baseDir . '/lib/private/Files/Cache/Propagator.php',
|
'OC\\Files\\Cache\\Propagator' => $baseDir . '/lib/private/Files/Cache/Propagator.php',
|
||||||
'OC\\Files\\Cache\\QuerySearchHelper' => $baseDir . '/lib/private/Files/Cache/QuerySearchHelper.php',
|
'OC\\Files\\Cache\\QuerySearchHelper' => $baseDir . '/lib/private/Files/Cache/QuerySearchHelper.php',
|
||||||
'OC\\Files\\Cache\\Scanner' => $baseDir . '/lib/private/Files/Cache/Scanner.php',
|
'OC\\Files\\Cache\\Scanner' => $baseDir . '/lib/private/Files/Cache/Scanner.php',
|
||||||
|
'OC\\Files\\Cache\\SearchBuilder' => $baseDir . '/lib/private/Files/Cache/SearchBuilder.php',
|
||||||
'OC\\Files\\Cache\\Storage' => $baseDir . '/lib/private/Files/Cache/Storage.php',
|
'OC\\Files\\Cache\\Storage' => $baseDir . '/lib/private/Files/Cache/Storage.php',
|
||||||
'OC\\Files\\Cache\\StorageGlobal' => $baseDir . '/lib/private/Files/Cache/StorageGlobal.php',
|
'OC\\Files\\Cache\\StorageGlobal' => $baseDir . '/lib/private/Files/Cache/StorageGlobal.php',
|
||||||
'OC\\Files\\Cache\\Updater' => $baseDir . '/lib/private/Files/Cache/Updater.php',
|
'OC\\Files\\Cache\\Updater' => $baseDir . '/lib/private/Files/Cache/Updater.php',
|
||||||
|
|
|
@ -7,7 +7,7 @@ namespace Composer\Autoload;
|
||||||
class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c
|
class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c
|
||||||
{
|
{
|
||||||
public static $prefixLengthsPsr4 = array (
|
public static $prefixLengthsPsr4 = array (
|
||||||
'O' =>
|
'O' =>
|
||||||
array (
|
array (
|
||||||
'OC\\Core\\' => 8,
|
'OC\\Core\\' => 8,
|
||||||
'OC\\' => 3,
|
'OC\\' => 3,
|
||||||
|
@ -16,15 +16,15 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c
|
||||||
);
|
);
|
||||||
|
|
||||||
public static $prefixDirsPsr4 = array (
|
public static $prefixDirsPsr4 = array (
|
||||||
'OC\\Core\\' =>
|
'OC\\Core\\' =>
|
||||||
array (
|
array (
|
||||||
0 => __DIR__ . '/../../..' . '/core',
|
0 => __DIR__ . '/../../..' . '/core',
|
||||||
),
|
),
|
||||||
'OC\\' =>
|
'OC\\' =>
|
||||||
array (
|
array (
|
||||||
0 => __DIR__ . '/../../..' . '/lib/private',
|
0 => __DIR__ . '/../../..' . '/lib/private',
|
||||||
),
|
),
|
||||||
'OCP\\' =>
|
'OCP\\' =>
|
||||||
array (
|
array (
|
||||||
0 => __DIR__ . '/../../..' . '/lib/public',
|
0 => __DIR__ . '/../../..' . '/lib/public',
|
||||||
),
|
),
|
||||||
|
@ -1086,6 +1086,7 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c
|
||||||
'OC\\Files\\Cache\\Propagator' => __DIR__ . '/../../..' . '/lib/private/Files/Cache/Propagator.php',
|
'OC\\Files\\Cache\\Propagator' => __DIR__ . '/../../..' . '/lib/private/Files/Cache/Propagator.php',
|
||||||
'OC\\Files\\Cache\\QuerySearchHelper' => __DIR__ . '/../../..' . '/lib/private/Files/Cache/QuerySearchHelper.php',
|
'OC\\Files\\Cache\\QuerySearchHelper' => __DIR__ . '/../../..' . '/lib/private/Files/Cache/QuerySearchHelper.php',
|
||||||
'OC\\Files\\Cache\\Scanner' => __DIR__ . '/../../..' . '/lib/private/Files/Cache/Scanner.php',
|
'OC\\Files\\Cache\\Scanner' => __DIR__ . '/../../..' . '/lib/private/Files/Cache/Scanner.php',
|
||||||
|
'OC\\Files\\Cache\\SearchBuilder' => __DIR__ . '/../../..' . '/lib/private/Files/Cache/SearchBuilder.php',
|
||||||
'OC\\Files\\Cache\\Storage' => __DIR__ . '/../../..' . '/lib/private/Files/Cache/Storage.php',
|
'OC\\Files\\Cache\\Storage' => __DIR__ . '/../../..' . '/lib/private/Files/Cache/Storage.php',
|
||||||
'OC\\Files\\Cache\\StorageGlobal' => __DIR__ . '/../../..' . '/lib/private/Files/Cache/StorageGlobal.php',
|
'OC\\Files\\Cache\\StorageGlobal' => __DIR__ . '/../../..' . '/lib/private/Files/Cache/StorageGlobal.php',
|
||||||
'OC\\Files\\Cache\\Updater' => __DIR__ . '/../../..' . '/lib/private/Files/Cache/Updater.php',
|
'OC\\Files\\Cache\\Updater' => __DIR__ . '/../../..' . '/lib/private/Files/Cache/Updater.php',
|
||||||
|
|
|
@ -40,7 +40,8 @@
|
||||||
namespace OC\Files\Cache;
|
namespace OC\Files\Cache;
|
||||||
|
|
||||||
use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
|
use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
|
||||||
use OCP\DB\IResult;
|
use OC\Files\Search\SearchComparison;
|
||||||
|
use OC\Files\Search\SearchQuery;
|
||||||
use OCP\DB\QueryBuilder\IQueryBuilder;
|
use OCP\DB\QueryBuilder\IQueryBuilder;
|
||||||
use OCP\EventDispatcher\IEventDispatcher;
|
use OCP\EventDispatcher\IEventDispatcher;
|
||||||
use OCP\Files\Cache\CacheEntryInsertedEvent;
|
use OCP\Files\Cache\CacheEntryInsertedEvent;
|
||||||
|
@ -52,6 +53,8 @@ use OCP\Files\Cache\ICache;
|
||||||
use OCP\Files\Cache\ICacheEntry;
|
use OCP\Files\Cache\ICacheEntry;
|
||||||
use OCP\Files\FileInfo;
|
use OCP\Files\FileInfo;
|
||||||
use OCP\Files\IMimeTypeLoader;
|
use OCP\Files\IMimeTypeLoader;
|
||||||
|
use OCP\Files\Search\ISearchComparison;
|
||||||
|
use OCP\Files\Search\ISearchOperator;
|
||||||
use OCP\Files\Search\ISearchQuery;
|
use OCP\Files\Search\ISearchQuery;
|
||||||
use OCP\Files\Storage\IStorage;
|
use OCP\Files\Storage\IStorage;
|
||||||
use OCP\IDBConnection;
|
use OCP\IDBConnection;
|
||||||
|
@ -118,15 +121,19 @@ class Cache implements ICache {
|
||||||
$this->mimetypeLoader = \OC::$server->getMimeTypeLoader();
|
$this->mimetypeLoader = \OC::$server->getMimeTypeLoader();
|
||||||
$this->connection = \OC::$server->getDatabaseConnection();
|
$this->connection = \OC::$server->getDatabaseConnection();
|
||||||
$this->eventDispatcher = \OC::$server->get(IEventDispatcher::class);
|
$this->eventDispatcher = \OC::$server->get(IEventDispatcher::class);
|
||||||
$this->querySearchHelper = new QuerySearchHelper($this->mimetypeLoader);
|
$this->querySearchHelper = new QuerySearchHelper(
|
||||||
|
$this->mimetypeLoader,
|
||||||
|
$this->connection,
|
||||||
|
\OC::$server->getSystemConfig(),
|
||||||
|
\OC::$server->getLogger()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function getQueryBuilder() {
|
protected function getQueryBuilder() {
|
||||||
return new CacheQueryBuilder(
|
return new CacheQueryBuilder(
|
||||||
$this->connection,
|
$this->connection,
|
||||||
\OC::$server->getSystemConfig(),
|
\OC::$server->getSystemConfig(),
|
||||||
\OC::$server->getLogger(),
|
\OC::$server->getLogger()
|
||||||
$this
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -153,7 +160,7 @@ class Cache implements ICache {
|
||||||
// normalize file
|
// normalize file
|
||||||
$file = $this->normalize($file);
|
$file = $this->normalize($file);
|
||||||
|
|
||||||
$query->whereStorageId()
|
$query->whereStorageId($this->getNumericStorageId())
|
||||||
->wherePath($file);
|
->wherePath($file);
|
||||||
} else { //file id
|
} else { //file id
|
||||||
$query->whereFileId($file);
|
$query->whereFileId($file);
|
||||||
|
@ -482,7 +489,7 @@ class Cache implements ICache {
|
||||||
$query = $this->getQueryBuilder();
|
$query = $this->getQueryBuilder();
|
||||||
$query->select('fileid')
|
$query->select('fileid')
|
||||||
->from('filecache')
|
->from('filecache')
|
||||||
->whereStorageId()
|
->whereStorageId($this->getNumericStorageId())
|
||||||
->wherePath($file);
|
->wherePath($file);
|
||||||
|
|
||||||
$result = $query->execute();
|
$result = $query->execute();
|
||||||
|
@ -718,7 +725,7 @@ class Cache implements ICache {
|
||||||
public function clear() {
|
public function clear() {
|
||||||
$query = $this->getQueryBuilder();
|
$query = $this->getQueryBuilder();
|
||||||
$query->delete('filecache')
|
$query->delete('filecache')
|
||||||
->whereStorageId();
|
->whereStorageId($this->getNumericStorageId());
|
||||||
$query->execute();
|
$query->execute();
|
||||||
|
|
||||||
$query = $this->connection->getQueryBuilder();
|
$query = $this->connection->getQueryBuilder();
|
||||||
|
@ -746,7 +753,7 @@ class Cache implements ICache {
|
||||||
$query = $this->getQueryBuilder();
|
$query = $this->getQueryBuilder();
|
||||||
$query->select('size')
|
$query->select('size')
|
||||||
->from('filecache')
|
->from('filecache')
|
||||||
->whereStorageId()
|
->whereStorageId($this->getNumericStorageId())
|
||||||
->wherePath($file);
|
->wherePath($file);
|
||||||
|
|
||||||
$result = $query->execute();
|
$result = $query->execute();
|
||||||
|
@ -775,37 +782,8 @@ class Cache implements ICache {
|
||||||
* @return ICacheEntry[] an array of cache entries where the name matches the search pattern
|
* @return ICacheEntry[] an array of cache entries where the name matches the search pattern
|
||||||
*/
|
*/
|
||||||
public function search($pattern) {
|
public function search($pattern) {
|
||||||
// normalize pattern
|
$operator = new SearchComparison(ISearchComparison::COMPARE_LIKE, 'name', $pattern);
|
||||||
$pattern = $this->normalize($pattern);
|
return $this->searchQuery(new SearchQuery($operator, 0, 0, [], null));
|
||||||
|
|
||||||
if ($pattern === '%%') {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
$query = $this->getQueryBuilder();
|
|
||||||
$query->selectFileCache()
|
|
||||||
->whereStorageId()
|
|
||||||
->andWhere($query->expr()->iLike('name', $query->createNamedParameter($pattern)));
|
|
||||||
|
|
||||||
$result = $query->execute();
|
|
||||||
$files = $result->fetchAll();
|
|
||||||
$result->closeCursor();
|
|
||||||
|
|
||||||
return array_map(function (array $data) {
|
|
||||||
return self::cacheEntryFromData($data, $this->mimetypeLoader);
|
|
||||||
}, $files);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param IResult $result
|
|
||||||
* @return CacheEntry[]
|
|
||||||
*/
|
|
||||||
private function searchResultToCacheEntries(IResult $result): array {
|
|
||||||
$files = $result->fetchAll();
|
|
||||||
|
|
||||||
return array_map(function (array $data) {
|
|
||||||
return self::cacheEntryFromData($data, $this->mimetypeLoader);
|
|
||||||
}, $files);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -816,71 +794,16 @@ class Cache implements ICache {
|
||||||
* @return ICacheEntry[] an array of cache entries where the mimetype matches the search
|
* @return ICacheEntry[] an array of cache entries where the mimetype matches the search
|
||||||
*/
|
*/
|
||||||
public function searchByMime($mimetype) {
|
public function searchByMime($mimetype) {
|
||||||
$mimeId = $this->mimetypeLoader->getId($mimetype);
|
if (strpos($mimetype, '/') === false) {
|
||||||
|
$operator = new SearchComparison(ISearchComparison::COMPARE_LIKE, 'mimetype', $mimetype . '/%');
|
||||||
$query = $this->getQueryBuilder();
|
|
||||||
$query->selectFileCache()
|
|
||||||
->whereStorageId();
|
|
||||||
|
|
||||||
if (strpos($mimetype, '/')) {
|
|
||||||
$query->andWhere($query->expr()->eq('mimetype', $query->createNamedParameter($mimeId, IQueryBuilder::PARAM_INT)));
|
|
||||||
} else {
|
} else {
|
||||||
$query->andWhere($query->expr()->eq('mimepart', $query->createNamedParameter($mimeId, IQueryBuilder::PARAM_INT)));
|
$operator = new SearchComparison(ISearchComparison::COMPARE_EQUAL, 'mimetype', $mimetype);
|
||||||
}
|
}
|
||||||
|
return $this->searchQuery(new SearchQuery($operator, 0, 0, [], null));
|
||||||
$result = $query->execute();
|
|
||||||
$files = $result->fetchAll();
|
|
||||||
$result->closeCursor();
|
|
||||||
|
|
||||||
return array_map(function (array $data) {
|
|
||||||
return self::cacheEntryFromData($data, $this->mimetypeLoader);
|
|
||||||
}, $files);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function searchQuery(ISearchQuery $searchQuery) {
|
public function searchQuery(ISearchQuery $searchQuery) {
|
||||||
$builder = $this->getQueryBuilder();
|
return current($this->querySearchHelper->searchInCaches($searchQuery, [$this]));
|
||||||
|
|
||||||
$query = $builder->selectFileCache('file');
|
|
||||||
|
|
||||||
$query->whereStorageId();
|
|
||||||
|
|
||||||
if ($this->querySearchHelper->shouldJoinTags($searchQuery->getSearchOperation())) {
|
|
||||||
$user = $searchQuery->getUser();
|
|
||||||
if ($user === null) {
|
|
||||||
throw new \InvalidArgumentException("Searching by tag requires the user to be set in the query");
|
|
||||||
}
|
|
||||||
$query
|
|
||||||
->innerJoin('file', 'vcategory_to_object', 'tagmap', $builder->expr()->eq('file.fileid', 'tagmap.objid'))
|
|
||||||
->innerJoin('tagmap', 'vcategory', 'tag', $builder->expr()->andX(
|
|
||||||
$builder->expr()->eq('tagmap.type', 'tag.type'),
|
|
||||||
$builder->expr()->eq('tagmap.categoryid', 'tag.id')
|
|
||||||
))
|
|
||||||
->andWhere($builder->expr()->eq('tag.type', $builder->createNamedParameter('files')))
|
|
||||||
->andWhere($builder->expr()->eq('tag.uid', $builder->createNamedParameter($user->getUID())));
|
|
||||||
}
|
|
||||||
|
|
||||||
$searchExpr = $this->querySearchHelper->searchOperatorToDBExpr($builder, $searchQuery->getSearchOperation());
|
|
||||||
if ($searchExpr) {
|
|
||||||
$query->andWhere($searchExpr);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($searchQuery->limitToHome() && ($this instanceof HomeCache)) {
|
|
||||||
$query->andWhere($builder->expr()->like('path', $query->expr()->literal('files/%')));
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->querySearchHelper->addSearchOrdersToQuery($query, $searchQuery->getOrder());
|
|
||||||
|
|
||||||
if ($searchQuery->getLimit()) {
|
|
||||||
$query->setMaxResults($searchQuery->getLimit());
|
|
||||||
}
|
|
||||||
if ($searchQuery->getOffset()) {
|
|
||||||
$query->setFirstResult($searchQuery->getOffset());
|
|
||||||
}
|
|
||||||
|
|
||||||
$result = $query->execute();
|
|
||||||
$cacheEntries = $this->searchResultToCacheEntries($result);
|
|
||||||
$result->closeCursor();
|
|
||||||
return $cacheEntries;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -949,7 +872,7 @@ class Cache implements ICache {
|
||||||
$query->selectAlias($query->func()->sum('size'), 'f1')
|
$query->selectAlias($query->func()->sum('size'), 'f1')
|
||||||
->selectAlias($query->func()->min('size'), 'f2')
|
->selectAlias($query->func()->min('size'), 'f2')
|
||||||
->from('filecache')
|
->from('filecache')
|
||||||
->whereStorageId()
|
->whereStorageId($this->getNumericStorageId())
|
||||||
->whereParent($id);
|
->whereParent($id);
|
||||||
|
|
||||||
$result = $query->execute();
|
$result = $query->execute();
|
||||||
|
@ -982,7 +905,7 @@ class Cache implements ICache {
|
||||||
$query = $this->getQueryBuilder();
|
$query = $this->getQueryBuilder();
|
||||||
$query->select('fileid')
|
$query->select('fileid')
|
||||||
->from('filecache')
|
->from('filecache')
|
||||||
->whereStorageId();
|
->whereStorageId($this->getNumericStorageId());
|
||||||
|
|
||||||
$result = $query->execute();
|
$result = $query->execute();
|
||||||
$files = $result->fetchAll(\PDO::FETCH_COLUMN);
|
$files = $result->fetchAll(\PDO::FETCH_COLUMN);
|
||||||
|
@ -1006,7 +929,7 @@ class Cache implements ICache {
|
||||||
$query = $this->getQueryBuilder();
|
$query = $this->getQueryBuilder();
|
||||||
$query->select('path')
|
$query->select('path')
|
||||||
->from('filecache')
|
->from('filecache')
|
||||||
->whereStorageId()
|
->whereStorageId($this->getNumericStorageId())
|
||||||
->andWhere($query->expr()->lt('size', $query->createNamedParameter(0, IQueryBuilder::PARAM_INT)))
|
->andWhere($query->expr()->lt('size', $query->createNamedParameter(0, IQueryBuilder::PARAM_INT)))
|
||||||
->orderBy('fileid', 'DESC')
|
->orderBy('fileid', 'DESC')
|
||||||
->setMaxResults(1);
|
->setMaxResults(1);
|
||||||
|
@ -1028,7 +951,7 @@ class Cache implements ICache {
|
||||||
$query = $this->getQueryBuilder();
|
$query = $this->getQueryBuilder();
|
||||||
$query->select('path')
|
$query->select('path')
|
||||||
->from('filecache')
|
->from('filecache')
|
||||||
->whereStorageId()
|
->whereStorageId($this->getNumericStorageId())
|
||||||
->whereFileId($id);
|
->whereFileId($id);
|
||||||
|
|
||||||
$result = $query->execute();
|
$result = $query->execute();
|
||||||
|
@ -1127,4 +1050,16 @@ class Cache implements ICache {
|
||||||
'metadata_etag' => $entry->getMetadataEtag(),
|
'metadata_etag' => $entry->getMetadataEtag(),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getQueryFilterForStorage(): ISearchOperator {
|
||||||
|
return new SearchComparison(ISearchComparison::COMPARE_EQUAL, 'storage', $this->getNumericStorageId());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCacheEntryFromSearchResult(ICacheEntry $rawEntry): ?ICacheEntry {
|
||||||
|
if ($rawEntry->getStorageId() === $this->getNumericStorageId()) {
|
||||||
|
return $rawEntry;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -125,4 +125,8 @@ class CacheEntry implements ICacheEntry {
|
||||||
public function getData() {
|
public function getData() {
|
||||||
return $this->data;
|
return $this->data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function __clone() {
|
||||||
|
$this->data = array_merge([], $this->data);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,13 +36,10 @@ use OCP\ILogger;
|
||||||
* Query builder with commonly used helpers for filecache queries
|
* Query builder with commonly used helpers for filecache queries
|
||||||
*/
|
*/
|
||||||
class CacheQueryBuilder extends QueryBuilder {
|
class CacheQueryBuilder extends QueryBuilder {
|
||||||
private $cache;
|
|
||||||
private $alias = null;
|
private $alias = null;
|
||||||
|
|
||||||
public function __construct(IDBConnection $connection, SystemConfig $systemConfig, ILogger $logger, Cache $cache) {
|
public function __construct(IDBConnection $connection, SystemConfig $systemConfig, ILogger $logger) {
|
||||||
parent::__construct($connection, $systemConfig, $logger);
|
parent::__construct($connection, $systemConfig, $logger);
|
||||||
|
|
||||||
$this->cache = $cache;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function selectFileCache(string $alias = null) {
|
public function selectFileCache(string $alias = null) {
|
||||||
|
@ -57,8 +54,8 @@ class CacheQueryBuilder extends QueryBuilder {
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function whereStorageId() {
|
public function whereStorageId(int $storageId) {
|
||||||
$this->andWhere($this->expr()->eq('storage', $this->createNamedParameter($this->cache->getNumericStorageId(), IQueryBuilder::PARAM_INT)));
|
$this->andWhere($this->expr()->eq('storage', $this->createNamedParameter($storageId, IQueryBuilder::PARAM_INT)));
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,9 +22,12 @@
|
||||||
|
|
||||||
namespace OC\Files\Cache;
|
namespace OC\Files\Cache;
|
||||||
|
|
||||||
|
use OC\Files\Search\SearchComparison;
|
||||||
use OCP\Constants;
|
use OCP\Constants;
|
||||||
use OCP\Files\Cache\ICache;
|
use OCP\Files\Cache\ICache;
|
||||||
use OCP\Files\Cache\ICacheEntry;
|
use OCP\Files\Cache\ICacheEntry;
|
||||||
|
use OCP\Files\Search\ISearchComparison;
|
||||||
|
use OCP\Files\Search\ISearchOperator;
|
||||||
use OCP\Files\Search\ISearchQuery;
|
use OCP\Files\Search\ISearchQuery;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -139,4 +142,12 @@ class FailedCache implements ICache {
|
||||||
public function copyFromCache(ICache $sourceCache, ICacheEntry $sourceEntry, string $targetPath): int {
|
public function copyFromCache(ICache $sourceCache, ICacheEntry $sourceEntry, string $targetPath): int {
|
||||||
throw new \Exception("Invalid cache");
|
throw new \Exception("Invalid cache");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getQueryFilterForStorage(): ISearchOperator {
|
||||||
|
return new SearchComparison(ISearchComparison::COMPARE_EQUAL, 'storage', -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCacheEntryFromSearchResult(ICacheEntry $rawEntry): ?ICacheEntry {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,206 +26,134 @@
|
||||||
|
|
||||||
namespace OC\Files\Cache;
|
namespace OC\Files\Cache;
|
||||||
|
|
||||||
use OCP\DB\QueryBuilder\IQueryBuilder;
|
use OC\Files\Search\SearchBinaryOperator;
|
||||||
|
use OC\SystemConfig;
|
||||||
|
use OCP\Files\Cache\ICache;
|
||||||
|
use OCP\Files\Cache\ICacheEntry;
|
||||||
use OCP\Files\IMimeTypeLoader;
|
use OCP\Files\IMimeTypeLoader;
|
||||||
use OCP\Files\Search\ISearchBinaryOperator;
|
use OCP\Files\Search\ISearchBinaryOperator;
|
||||||
use OCP\Files\Search\ISearchComparison;
|
use OCP\Files\Search\ISearchQuery;
|
||||||
use OCP\Files\Search\ISearchOperator;
|
use OCP\IDBConnection;
|
||||||
use OCP\Files\Search\ISearchOrder;
|
use OCP\ILogger;
|
||||||
|
|
||||||
/**
|
|
||||||
* Tools for transforming search queries into database queries
|
|
||||||
*/
|
|
||||||
class QuerySearchHelper {
|
class QuerySearchHelper {
|
||||||
protected static $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'
|
|
||||||
];
|
|
||||||
|
|
||||||
protected static $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'
|
|
||||||
];
|
|
||||||
|
|
||||||
public const TAG_FAVORITE = '_$!<Favorite>!$_';
|
|
||||||
|
|
||||||
/** @var IMimeTypeLoader */
|
/** @var IMimeTypeLoader */
|
||||||
private $mimetypeLoader;
|
private $mimetypeLoader;
|
||||||
|
/** @var IDBConnection */
|
||||||
|
private $connection;
|
||||||
|
/** @var SystemConfig */
|
||||||
|
private $systemConfig;
|
||||||
|
/** @var ILogger */
|
||||||
|
private $logger;
|
||||||
|
/** @var SearchBuilder */
|
||||||
|
private $searchBuilder;
|
||||||
|
|
||||||
/**
|
public function __construct(
|
||||||
* QuerySearchUtil constructor.
|
IMimeTypeLoader $mimetypeLoader,
|
||||||
*
|
IDBConnection $connection,
|
||||||
* @param IMimeTypeLoader $mimetypeLoader
|
SystemConfig $systemConfig,
|
||||||
*/
|
ILogger $logger
|
||||||
public function __construct(IMimeTypeLoader $mimetypeLoader) {
|
) {
|
||||||
$this->mimetypeLoader = $mimetypeLoader;
|
$this->mimetypeLoader = $mimetypeLoader;
|
||||||
|
$this->connection = $connection;
|
||||||
|
$this->systemConfig = $systemConfig;
|
||||||
|
$this->logger = $logger;
|
||||||
|
$this->searchBuilder = new SearchBuilder($this->mimetypeLoader);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getQueryBuilder() {
|
||||||
|
return new CacheQueryBuilder(
|
||||||
|
$this->connection,
|
||||||
|
$this->systemConfig,
|
||||||
|
$this->logger
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether or not the tag tables should be joined to complete the search
|
* Perform a file system search in multiple caches
|
||||||
*
|
*
|
||||||
* @param ISearchOperator $operator
|
* the results will be grouped by the same array keys as the $caches argument to allow
|
||||||
* @return boolean
|
* post-processing based on which cache the result came from
|
||||||
|
*
|
||||||
|
* @template T of array-key
|
||||||
|
* @param ISearchQuery $searchQuery
|
||||||
|
* @param array<T, ICache> $caches
|
||||||
|
* @return array<T, ICacheEntry[]>
|
||||||
*/
|
*/
|
||||||
public function shouldJoinTags(ISearchOperator $operator) {
|
public function searchInCaches(ISearchQuery $searchQuery, array $caches): array {
|
||||||
if ($operator instanceof ISearchBinaryOperator) {
|
// search in multiple caches at once by creating one query in the following format
|
||||||
return array_reduce($operator->getArguments(), function ($shouldJoin, ISearchOperator $operator) {
|
// SELECT ... FROM oc_filecache WHERE
|
||||||
return $shouldJoin || $this->shouldJoinTags($operator);
|
// <filter expressions from the search query>
|
||||||
}, false);
|
// AND (
|
||||||
} elseif ($operator instanceof ISearchComparison) {
|
// <filter expression for storage1> OR
|
||||||
return $operator->getField() === 'tagname' || $operator->getField() === 'favorite';
|
// <filter expression for storage2> OR
|
||||||
}
|
// ...
|
||||||
return false;
|
// );
|
||||||
}
|
//
|
||||||
|
// This gives us all the files matching the search query from all caches
|
||||||
|
//
|
||||||
|
// while the resulting rows don't have a way to tell what storage they came from (multiple storages/caches can share storage_id)
|
||||||
|
// we can just ask every cache if the row belongs to them and give them the cache to do any post processing on the result.
|
||||||
|
|
||||||
/**
|
$builder = $this->getQueryBuilder();
|
||||||
* @param IQueryBuilder $builder
|
|
||||||
* @param ISearchOperator $operator
|
|
||||||
*/
|
|
||||||
public function searchOperatorArrayToDBExprArray(IQueryBuilder $builder, array $operators) {
|
|
||||||
return array_filter(array_map(function ($operator) use ($builder) {
|
|
||||||
return $this->searchOperatorToDBExpr($builder, $operator);
|
|
||||||
}, $operators));
|
|
||||||
}
|
|
||||||
|
|
||||||
public function searchOperatorToDBExpr(IQueryBuilder $builder, ISearchOperator $operator) {
|
$query = $builder->selectFileCache('file');
|
||||||
$expr = $builder->expr();
|
|
||||||
if ($operator instanceof ISearchBinaryOperator) {
|
if ($this->searchBuilder->shouldJoinTags($searchQuery->getSearchOperation())) {
|
||||||
if (count($operator->getArguments()) === 0) {
|
$user = $searchQuery->getUser();
|
||||||
return null;
|
if ($user === null) {
|
||||||
|
throw new \InvalidArgumentException("Searching by tag requires the user to be set in the query");
|
||||||
}
|
}
|
||||||
|
$query
|
||||||
switch ($operator->getType()) {
|
->innerJoin('file', 'vcategory_to_object', 'tagmap', $builder->expr()->eq('file.fileid', 'tagmap.objid'))
|
||||||
case ISearchBinaryOperator::OPERATOR_NOT:
|
->innerJoin('tagmap', 'vcategory', 'tag', $builder->expr()->andX(
|
||||||
$negativeOperator = $operator->getArguments()[0];
|
$builder->expr()->eq('tagmap.type', 'tag.type'),
|
||||||
if ($negativeOperator instanceof ISearchComparison) {
|
$builder->expr()->eq('tagmap.categoryid', 'tag.id')
|
||||||
return $this->searchComparisonToDBExpr($builder, $negativeOperator, self::$searchOperatorNegativeMap);
|
))
|
||||||
} else {
|
->andWhere($builder->expr()->eq('tag.type', $builder->createNamedParameter('files')))
|
||||||
throw new \InvalidArgumentException('Binary operators inside "not" is not supported');
|
->andWhere($builder->expr()->eq('tag.uid', $builder->createNamedParameter($user->getUID())));
|
||||||
}
|
|
||||||
// no break
|
|
||||||
case ISearchBinaryOperator::OPERATOR_AND:
|
|
||||||
return call_user_func_array([$expr, 'andX'], $this->searchOperatorArrayToDBExprArray($builder, $operator->getArguments()));
|
|
||||||
case ISearchBinaryOperator::OPERATOR_OR:
|
|
||||||
return call_user_func_array([$expr, 'orX'], $this->searchOperatorArrayToDBExprArray($builder, $operator->getArguments()));
|
|
||||||
default:
|
|
||||||
throw new \InvalidArgumentException('Invalid operator type: ' . $operator->getType());
|
|
||||||
}
|
|
||||||
} elseif ($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) {
|
$searchExpr = $this->searchBuilder->searchOperatorToDBExpr($builder, $searchQuery->getSearchOperation());
|
||||||
$this->validateComparison($comparison);
|
if ($searchExpr) {
|
||||||
|
$query->andWhere($searchExpr);
|
||||||
[$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) {
|
$storageFilters = array_values(array_map(function (ICache $cache) {
|
||||||
$field = $operator->getField();
|
return $cache->getQueryFilterForStorage();
|
||||||
$value = $operator->getValue();
|
}, $caches));
|
||||||
$type = $operator->getType();
|
$query->andWhere($this->searchBuilder->searchOperatorToDBExpr($builder, new SearchBinaryOperator(ISearchBinaryOperator::OPERATOR_OR, $storageFilters)));
|
||||||
if ($field === 'mimetype') {
|
|
||||||
if ($operator->getType() === ISearchComparison::COMPARE_EQUAL) {
|
$this->searchBuilder->addSearchOrdersToQuery($query, $searchQuery->getOrder());
|
||||||
$value = (int)$this->mimetypeLoader->getId($value);
|
|
||||||
} elseif ($operator->getType() === ISearchComparison::COMPARE_LIKE) {
|
if ($searchQuery->getLimit()) {
|
||||||
// transform "mimetype='foo/%'" to "mimepart='foo'"
|
$query->setMaxResults($searchQuery->getLimit());
|
||||||
if (preg_match('|(.+)/%|', $value, $matches)) {
|
}
|
||||||
$field = 'mimepart';
|
if ($searchQuery->getOffset()) {
|
||||||
$value = (int)$this->mimetypeLoader->getId($matches[1]);
|
$query->setFirstResult($searchQuery->getOffset());
|
||||||
$type = ISearchComparison::COMPARE_EQUAL;
|
}
|
||||||
} elseif (strpos($value, '%') !== false) {
|
|
||||||
throw new \InvalidArgumentException('Unsupported query value for mimetype: ' . $value . ', only values in the format "mime/type" or "mime/%" are supported');
|
$result = $query->execute();
|
||||||
} else {
|
$files = $result->fetchAll();
|
||||||
$field = 'mimetype';
|
|
||||||
$value = (int)$this->mimetypeLoader->getId($value);
|
$rawEntries = array_map(function (array $data) {
|
||||||
$type = ISearchComparison::COMPARE_EQUAL;
|
return Cache::cacheEntryFromData($data, $this->mimetypeLoader);
|
||||||
|
}, $files);
|
||||||
|
|
||||||
|
$result->closeCursor();
|
||||||
|
|
||||||
|
// loop trough 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 = array_fill_keys(array_keys($caches), []);
|
||||||
|
foreach ($rawEntries as $rawEntry) {
|
||||||
|
foreach ($caches as $cacheKey => $cache) {
|
||||||
|
$entry = $cache->getCacheEntryFromSearchResult($rawEntry);
|
||||||
|
if ($entry) {
|
||||||
|
$results[$cacheKey][] = $entry;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} elseif ($field === 'favorite') {
|
|
||||||
$field = 'tag.category';
|
|
||||||
$value = self::TAG_FAVORITE;
|
|
||||||
} elseif ($field === 'tagname') {
|
|
||||||
$field = 'tag.category';
|
|
||||||
} elseif ($field === 'fileid') {
|
|
||||||
$field = 'file.fileid';
|
|
||||||
} elseif ($field === 'path' && $type === ISearchComparison::COMPARE_EQUAL) {
|
|
||||||
$field = 'path_hash';
|
|
||||||
$value = md5((string)$value);
|
|
||||||
}
|
|
||||||
return [$field, $value, $type];
|
|
||||||
}
|
|
||||||
|
|
||||||
private function validateComparison(ISearchComparison $operator) {
|
|
||||||
$types = [
|
|
||||||
'mimetype' => 'string',
|
|
||||||
'mtime' => 'integer',
|
|
||||||
'name' => 'string',
|
|
||||||
'path' => 'string',
|
|
||||||
'size' => 'integer',
|
|
||||||
'tagname' => 'string',
|
|
||||||
'favorite' => 'boolean',
|
|
||||||
'fileid' => 'integer'
|
|
||||||
];
|
|
||||||
$comparisons = [
|
|
||||||
'mimetype' => ['eq', 'like'],
|
|
||||||
'mtime' => ['eq', 'gt', 'lt', 'gte', 'lte'],
|
|
||||||
'name' => ['eq', 'like'],
|
|
||||||
'path' => ['eq', 'like'],
|
|
||||||
'size' => ['eq', 'gt', 'lt', 'gte', 'lte'],
|
|
||||||
'tagname' => ['eq', 'like'],
|
|
||||||
'favorite' => ['eq'],
|
|
||||||
'fileid' => ['eq']
|
|
||||||
];
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param IQueryBuilder $query
|
|
||||||
* @param ISearchOrder[] $orders
|
|
||||||
*/
|
|
||||||
public function addSearchOrdersToQuery(IQueryBuilder $query, array $orders) {
|
|
||||||
foreach ($orders as $order) {
|
|
||||||
$query->addOrderBy($order->getField(), $order->getDirection());
|
|
||||||
}
|
}
|
||||||
|
return $results;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,235 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @copyright Copyright (c) 2017 Robin Appelman <robin@icewind.nl>
|
||||||
|
*
|
||||||
|
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
|
||||||
|
* @author Robin Appelman <robin@icewind.nl>
|
||||||
|
* @author Roeland Jago Douma <roeland@famdouma.nl>
|
||||||
|
* @author Tobias Kaminsky <tobias@kaminsky.me>
|
||||||
|
*
|
||||||
|
* @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;
|
||||||
|
use OCP\Files\Search\ISearchOrder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tools for transforming search queries into database queries
|
||||||
|
*/
|
||||||
|
class SearchBuilder {
|
||||||
|
protected static $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',
|
||||||
|
];
|
||||||
|
|
||||||
|
protected static $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',
|
||||||
|
];
|
||||||
|
|
||||||
|
public const TAG_FAVORITE = '_$!<Favorite>!$_';
|
||||||
|
|
||||||
|
/** @var IMimeTypeLoader */
|
||||||
|
private $mimetypeLoader;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
IMimeTypeLoader $mimetypeLoader
|
||||||
|
) {
|
||||||
|
$this->mimetypeLoader = $mimetypeLoader;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether or not the tag tables should be joined to complete the search
|
||||||
|
*
|
||||||
|
* @param ISearchOperator $operator
|
||||||
|
* @return boolean
|
||||||
|
*/
|
||||||
|
public function shouldJoinTags(ISearchOperator $operator) {
|
||||||
|
if ($operator instanceof ISearchBinaryOperator) {
|
||||||
|
return array_reduce($operator->getArguments(), function ($shouldJoin, ISearchOperator $operator) {
|
||||||
|
return $shouldJoin || $this->shouldJoinTags($operator);
|
||||||
|
}, false);
|
||||||
|
} elseif ($operator instanceof ISearchComparison) {
|
||||||
|
return $operator->getField() === 'tagname' || $operator->getField() === 'favorite';
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param IQueryBuilder $builder
|
||||||
|
* @param ISearchOperator $operator
|
||||||
|
*/
|
||||||
|
public function searchOperatorArrayToDBExprArray(IQueryBuilder $builder, array $operators) {
|
||||||
|
return array_filter(array_map(function ($operator) use ($builder) {
|
||||||
|
return $this->searchOperatorToDBExpr($builder, $operator);
|
||||||
|
}, $operators));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function searchOperatorToDBExpr(IQueryBuilder $builder, ISearchOperator $operator) {
|
||||||
|
$expr = $builder->expr();
|
||||||
|
if ($operator instanceof ISearchBinaryOperator) {
|
||||||
|
if (count($operator->getArguments()) === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
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');
|
||||||
|
}
|
||||||
|
// no break
|
||||||
|
case ISearchBinaryOperator::OPERATOR_AND:
|
||||||
|
return call_user_func_array([$expr, 'andX'], $this->searchOperatorArrayToDBExprArray($builder, $operator->getArguments()));
|
||||||
|
case ISearchBinaryOperator::OPERATOR_OR:
|
||||||
|
return call_user_func_array([$expr, 'orX'], $this->searchOperatorArrayToDBExprArray($builder, $operator->getArguments()));
|
||||||
|
default:
|
||||||
|
throw new \InvalidArgumentException('Invalid operator type: ' . $operator->getType());
|
||||||
|
}
|
||||||
|
} elseif ($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);
|
||||||
|
|
||||||
|
[$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') {
|
||||||
|
$value = (string)$value;
|
||||||
|
if ($operator->getType() === ISearchComparison::COMPARE_EQUAL) {
|
||||||
|
$value = (int)$this->mimetypeLoader->getId($value);
|
||||||
|
} elseif ($operator->getType() === ISearchComparison::COMPARE_LIKE) {
|
||||||
|
// transform "mimetype='foo/%'" to "mimepart='foo'"
|
||||||
|
if (preg_match('|(.+)/%|', $value, $matches)) {
|
||||||
|
$field = 'mimepart';
|
||||||
|
$value = (int)$this->mimetypeLoader->getId($matches[1]);
|
||||||
|
$type = ISearchComparison::COMPARE_EQUAL;
|
||||||
|
} elseif (strpos($value, '%') !== false) {
|
||||||
|
throw new \InvalidArgumentException('Unsupported query value for mimetype: ' . $value . ', only values in the format "mime/type" or "mime/%" are supported');
|
||||||
|
} else {
|
||||||
|
$field = 'mimetype';
|
||||||
|
$value = (int)$this->mimetypeLoader->getId($value);
|
||||||
|
$type = ISearchComparison::COMPARE_EQUAL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} elseif ($field === 'favorite') {
|
||||||
|
$field = 'tag.category';
|
||||||
|
$value = self::TAG_FAVORITE;
|
||||||
|
} elseif ($field === 'tagname') {
|
||||||
|
$field = 'tag.category';
|
||||||
|
} elseif ($field === 'fileid') {
|
||||||
|
$field = 'file.fileid';
|
||||||
|
} elseif ($field === 'path' && $type === ISearchComparison::COMPARE_EQUAL) {
|
||||||
|
$field = 'path_hash';
|
||||||
|
$value = md5((string)$value);
|
||||||
|
}
|
||||||
|
return [$field, $value, $type];
|
||||||
|
}
|
||||||
|
|
||||||
|
private function validateComparison(ISearchComparison $operator) {
|
||||||
|
$types = [
|
||||||
|
'mimetype' => 'string',
|
||||||
|
'mtime' => 'integer',
|
||||||
|
'name' => 'string',
|
||||||
|
'path' => 'string',
|
||||||
|
'size' => 'integer',
|
||||||
|
'tagname' => 'string',
|
||||||
|
'favorite' => 'boolean',
|
||||||
|
'fileid' => 'integer',
|
||||||
|
'storage' => 'integer',
|
||||||
|
];
|
||||||
|
$comparisons = [
|
||||||
|
'mimetype' => ['eq', 'like'],
|
||||||
|
'mtime' => ['eq', 'gt', 'lt', 'gte', 'lte'],
|
||||||
|
'name' => ['eq', 'like'],
|
||||||
|
'path' => ['eq', 'like'],
|
||||||
|
'size' => ['eq', 'gt', 'lt', 'gte', 'lte'],
|
||||||
|
'tagname' => ['eq', 'like'],
|
||||||
|
'favorite' => ['eq'],
|
||||||
|
'fileid' => ['eq'],
|
||||||
|
'storage' => ['eq'],
|
||||||
|
];
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param IQueryBuilder $query
|
||||||
|
* @param ISearchOrder[] $orders
|
||||||
|
*/
|
||||||
|
public function addSearchOrdersToQuery(IQueryBuilder $query, array $orders) {
|
||||||
|
foreach ($orders as $order) {
|
||||||
|
$field = $order->getField();
|
||||||
|
if ($field === 'fileid') {
|
||||||
|
$field = 'file.fileid';
|
||||||
|
}
|
||||||
|
$query->addOrderBy($field, $order->getDirection());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -31,12 +31,10 @@ namespace OC\Files\Cache\Wrapper;
|
||||||
use OC\Files\Cache\Cache;
|
use OC\Files\Cache\Cache;
|
||||||
use OC\Files\Search\SearchBinaryOperator;
|
use OC\Files\Search\SearchBinaryOperator;
|
||||||
use OC\Files\Search\SearchComparison;
|
use OC\Files\Search\SearchComparison;
|
||||||
use OC\Files\Search\SearchQuery;
|
|
||||||
use OCP\DB\QueryBuilder\IQueryBuilder;
|
|
||||||
use OCP\Files\Cache\ICacheEntry;
|
use OCP\Files\Cache\ICacheEntry;
|
||||||
use OCP\Files\Search\ISearchBinaryOperator;
|
use OCP\Files\Search\ISearchBinaryOperator;
|
||||||
use OCP\Files\Search\ISearchComparison;
|
use OCP\Files\Search\ISearchComparison;
|
||||||
use OCP\Files\Search\ISearchQuery;
|
use OCP\Files\Search\ISearchOperator;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Jail to a subdirectory of the wrapped cache
|
* Jail to a subdirectory of the wrapped cache
|
||||||
|
@ -108,10 +106,6 @@ class CacheJail extends CacheWrapper {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param ICacheEntry|array $entry
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
protected function formatCacheEntry($entry) {
|
protected function formatCacheEntry($entry) {
|
||||||
if (isset($entry['path'])) {
|
if (isset($entry['path'])) {
|
||||||
$entry['path'] = $this->getJailedPath($entry['path']);
|
$entry['path'] = $this->getJailedPath($entry['path']);
|
||||||
|
@ -230,99 +224,6 @@ class CacheJail extends CacheWrapper {
|
||||||
return $this->getCache()->getStatus($this->getSourcePath($file));
|
return $this->getCache()->getStatus($this->getSourcePath($file));
|
||||||
}
|
}
|
||||||
|
|
||||||
private function formatSearchResults($results) {
|
|
||||||
return array_map(function ($entry) {
|
|
||||||
$entry['path'] = $this->getJailedPath($entry['path'], $this->getGetUnjailedRoot());
|
|
||||||
return $entry;
|
|
||||||
}, $results);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* search for files matching $pattern
|
|
||||||
*
|
|
||||||
* @param string $pattern
|
|
||||||
* @return array an array of file data
|
|
||||||
*/
|
|
||||||
public function search($pattern) {
|
|
||||||
// normalize pattern
|
|
||||||
$pattern = $this->normalize($pattern);
|
|
||||||
|
|
||||||
if ($pattern === '%%') {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
$query = $this->getQueryBuilder();
|
|
||||||
$query->selectFileCache()
|
|
||||||
->whereStorageId()
|
|
||||||
->andWhere($query->expr()->orX(
|
|
||||||
$query->expr()->like('path', $query->createNamedParameter($this->getGetUnjailedRoot() . '/%')),
|
|
||||||
$query->expr()->eq('path_hash', $query->createNamedParameter(md5($this->getGetUnjailedRoot()))),
|
|
||||||
))
|
|
||||||
->andWhere($query->expr()->iLike('name', $query->createNamedParameter($pattern)));
|
|
||||||
|
|
||||||
$result = $query->execute();
|
|
||||||
$files = $result->fetchAll();
|
|
||||||
$result->closeCursor();
|
|
||||||
|
|
||||||
$results = array_map(function (array $data) {
|
|
||||||
return self::cacheEntryFromData($data, $this->mimetypeLoader);
|
|
||||||
}, $files);
|
|
||||||
return $this->formatSearchResults($results);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* search for files by mimetype
|
|
||||||
*
|
|
||||||
* @param string $mimetype
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
public function searchByMime($mimetype) {
|
|
||||||
$mimeId = $this->mimetypeLoader->getId($mimetype);
|
|
||||||
|
|
||||||
$query = $this->getQueryBuilder();
|
|
||||||
$query->selectFileCache()
|
|
||||||
->whereStorageId()
|
|
||||||
->andWhere($query->expr()->orX(
|
|
||||||
$query->expr()->like('path', $query->createNamedParameter($this->getGetUnjailedRoot() . '/%')),
|
|
||||||
$query->expr()->eq('path_hash', $query->createNamedParameter(md5($this->getGetUnjailedRoot()))),
|
|
||||||
));
|
|
||||||
|
|
||||||
if (strpos($mimetype, '/')) {
|
|
||||||
$query->andWhere($query->expr()->eq('mimetype', $query->createNamedParameter($mimeId, IQueryBuilder::PARAM_INT)));
|
|
||||||
} else {
|
|
||||||
$query->andWhere($query->expr()->eq('mimepart', $query->createNamedParameter($mimeId, IQueryBuilder::PARAM_INT)));
|
|
||||||
}
|
|
||||||
|
|
||||||
$result = $query->execute();
|
|
||||||
$files = $result->fetchAll();
|
|
||||||
$result->closeCursor();
|
|
||||||
|
|
||||||
$results = array_map(function (array $data) {
|
|
||||||
return self::cacheEntryFromData($data, $this->mimetypeLoader);
|
|
||||||
}, $files);
|
|
||||||
return $this->formatSearchResults($results);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function searchQuery(ISearchQuery $query) {
|
|
||||||
$prefixFilter = new SearchComparison(
|
|
||||||
ISearchComparison::COMPARE_LIKE,
|
|
||||||
'path',
|
|
||||||
$this->getGetUnjailedRoot() . '/%'
|
|
||||||
);
|
|
||||||
$rootFilter = new SearchComparison(
|
|
||||||
ISearchComparison::COMPARE_EQUAL,
|
|
||||||
'path',
|
|
||||||
$this->getGetUnjailedRoot()
|
|
||||||
);
|
|
||||||
$operation = new SearchBinaryOperator(
|
|
||||||
ISearchBinaryOperator::OPERATOR_AND,
|
|
||||||
[new SearchBinaryOperator(ISearchBinaryOperator::OPERATOR_OR, [$prefixFilter, $rootFilter]) , $query->getSearchOperation()]
|
|
||||||
);
|
|
||||||
$simpleQuery = new SearchQuery($operation, $query->getLimit(), $query->getOffset(), $query->getOrder(), $query->getUser());
|
|
||||||
$results = $this->getCache()->searchQuery($simpleQuery);
|
|
||||||
return $this->formatSearchResults($results);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* update the folder size and the size of all parent folders
|
* update the folder size and the size of all parent folders
|
||||||
*
|
*
|
||||||
|
@ -404,4 +305,30 @@ class CacheJail extends CacheWrapper {
|
||||||
}
|
}
|
||||||
return $this->getCache()->moveFromCache($sourceCache, $sourcePath, $this->getSourcePath($targetPath));
|
return $this->getCache()->moveFromCache($sourceCache, $sourcePath, $this->getSourcePath($targetPath));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getQueryFilterForStorage(): ISearchOperator {
|
||||||
|
return new SearchBinaryOperator(ISearchBinaryOperator::OPERATOR_AND,
|
||||||
|
[
|
||||||
|
$this->getCache()->getQueryFilterForStorage(),
|
||||||
|
new SearchBinaryOperator(ISearchBinaryOperator::OPERATOR_OR,
|
||||||
|
[
|
||||||
|
new SearchComparison(ISearchComparison::COMPARE_EQUAL, 'path', $this->getGetUnjailedRoot()),
|
||||||
|
new SearchComparison(ISearchComparison::COMPARE_LIKE, 'path', $this->getGetUnjailedRoot() . '/%'),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCacheEntryFromSearchResult(ICacheEntry $rawEntry): ?ICacheEntry {
|
||||||
|
$rawEntry = $this->getCache()->getCacheEntryFromSearchResult($rawEntry);
|
||||||
|
if ($rawEntry) {
|
||||||
|
$jailedPath = $this->getJailedPath($rawEntry->getPath());
|
||||||
|
if ($jailedPath !== null) {
|
||||||
|
return $this->formatCacheEntry(clone $rawEntry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,8 +31,10 @@
|
||||||
namespace OC\Files\Cache\Wrapper;
|
namespace OC\Files\Cache\Wrapper;
|
||||||
|
|
||||||
use OC\Files\Cache\Cache;
|
use OC\Files\Cache\Cache;
|
||||||
|
use OC\Files\Cache\QuerySearchHelper;
|
||||||
use OCP\Files\Cache\ICache;
|
use OCP\Files\Cache\ICache;
|
||||||
use OCP\Files\Cache\ICacheEntry;
|
use OCP\Files\Cache\ICacheEntry;
|
||||||
|
use OCP\Files\Search\ISearchOperator;
|
||||||
use OCP\Files\Search\ISearchQuery;
|
use OCP\Files\Search\ISearchQuery;
|
||||||
|
|
||||||
class CacheWrapper extends Cache {
|
class CacheWrapper extends Cache {
|
||||||
|
@ -46,6 +48,14 @@ class CacheWrapper extends Cache {
|
||||||
*/
|
*/
|
||||||
public function __construct($cache) {
|
public function __construct($cache) {
|
||||||
$this->cache = $cache;
|
$this->cache = $cache;
|
||||||
|
$this->mimetypeLoader = \OC::$server->getMimeTypeLoader();
|
||||||
|
$this->connection = \OC::$server->getDatabaseConnection();
|
||||||
|
$this->querySearchHelper = new QuerySearchHelper(
|
||||||
|
$this->mimetypeLoader,
|
||||||
|
$this->connection,
|
||||||
|
\OC::$server->getSystemConfig(),
|
||||||
|
\OC::$server->getLogger()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function getCache() {
|
protected function getCache() {
|
||||||
|
@ -216,31 +226,8 @@ class CacheWrapper extends Cache {
|
||||||
return $this->getCache()->getStatus($file);
|
return $this->getCache()->getStatus($file);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public function searchQuery(ISearchQuery $searchQuery) {
|
||||||
* search for files matching $pattern
|
return current($this->querySearchHelper->searchInCaches($searchQuery, [$this]));
|
||||||
*
|
|
||||||
* @param string $pattern
|
|
||||||
* @return ICacheEntry[] an array of file data
|
|
||||||
*/
|
|
||||||
public function search($pattern) {
|
|
||||||
$results = $this->getCache()->search($pattern);
|
|
||||||
return array_map([$this, 'formatCacheEntry'], $results);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* search for files by mimetype
|
|
||||||
*
|
|
||||||
* @param string $mimetype
|
|
||||||
* @return ICacheEntry[]
|
|
||||||
*/
|
|
||||||
public function searchByMime($mimetype) {
|
|
||||||
$results = $this->getCache()->searchByMime($mimetype);
|
|
||||||
return array_map([$this, 'formatCacheEntry'], $results);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function searchQuery(ISearchQuery $query) {
|
|
||||||
$results = $this->getCache()->searchQuery($query);
|
|
||||||
return array_map([$this, 'formatCacheEntry'], $results);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -322,4 +309,17 @@ class CacheWrapper extends Cache {
|
||||||
public static function getById($id) {
|
public static function getById($id) {
|
||||||
return parent::getById($id);
|
return parent::getById($id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getQueryFilterForStorage(): ISearchOperator {
|
||||||
|
return $this->getCache()->getQueryFilterForStorage();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCacheEntryFromSearchResult(ICacheEntry $rawEntry): ?ICacheEntry {
|
||||||
|
$rawEntry = $this->getCache()->getCacheEntryFromSearchResult($rawEntry);
|
||||||
|
if ($rawEntry) {
|
||||||
|
return $this->formatCacheEntry(clone $rawEntry);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,7 +31,9 @@
|
||||||
|
|
||||||
namespace OC\Files\Node;
|
namespace OC\Files\Node;
|
||||||
|
|
||||||
|
use OC\Files\Cache\QuerySearchHelper;
|
||||||
use OC\Files\Search\SearchBinaryOperator;
|
use OC\Files\Search\SearchBinaryOperator;
|
||||||
|
use OC\Files\Cache\Wrapper\CacheJail;
|
||||||
use OC\Files\Search\SearchComparison;
|
use OC\Files\Search\SearchComparison;
|
||||||
use OC\Files\Search\SearchOrder;
|
use OC\Files\Search\SearchOrder;
|
||||||
use OC\Files\Search\SearchQuery;
|
use OC\Files\Search\SearchQuery;
|
||||||
|
@ -231,18 +233,8 @@ class Folder extends Node implements \OCP\Files\Folder {
|
||||||
$query = $this->queryFromOperator(new SearchComparison(ISearchComparison::COMPARE_LIKE, 'name', '%' . $query . '%'));
|
$query = $this->queryFromOperator(new SearchComparison(ISearchComparison::COMPARE_LIKE, 'name', '%' . $query . '%'));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Limit+offset for queries with ordering
|
// search is handled by a single query covering all caches that this folder contains
|
||||||
//
|
// this is done by collect
|
||||||
// Because we currently can't do ordering between the results from different storages in sql
|
|
||||||
// The only way to do ordering is requesting the $limit number of entries from all storages
|
|
||||||
// sorting them and returning the first $limit entries.
|
|
||||||
//
|
|
||||||
// For offset we have the same problem, we don't know how many entries from each storage should be skipped
|
|
||||||
// by a given $offset, so instead we query $offset + $limit from each storage and return entries $offset..($offset+$limit)
|
|
||||||
// after merging and sorting them.
|
|
||||||
//
|
|
||||||
// This is suboptimal but because limit and offset tend to be fairly small in real world use cases it should
|
|
||||||
// still be significantly better than disabling paging altogether
|
|
||||||
|
|
||||||
$limitToHome = $query->limitToHome();
|
$limitToHome = $query->limitToHome();
|
||||||
if ($limitToHome && count(explode('/', $this->path)) !== 3) {
|
if ($limitToHome && count(explode('/', $this->path)) !== 3) {
|
||||||
|
@ -253,56 +245,43 @@ class Folder extends Node implements \OCP\Files\Folder {
|
||||||
$mount = $this->root->getMount($this->path);
|
$mount = $this->root->getMount($this->path);
|
||||||
$storage = $mount->getStorage();
|
$storage = $mount->getStorage();
|
||||||
$internalPath = $mount->getInternalPath($this->path);
|
$internalPath = $mount->getInternalPath($this->path);
|
||||||
$internalPath = rtrim($internalPath, '/');
|
|
||||||
|
// 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 !== '') {
|
if ($internalPath !== '') {
|
||||||
$internalPath = $internalPath . '/';
|
// a temporary CacheJail is used to handle filtering down the results to within this folder
|
||||||
}
|
$caches = ['' => new CacheJail($storage->getCache(''), $internalPath)];
|
||||||
|
} else {
|
||||||
$subQueryLimit = $query->getLimit() > 0 ? $query->getLimit() + $query->getOffset() : 0;
|
$caches = ['' => $storage->getCache('')];
|
||||||
$rootQuery = new SearchQuery(
|
|
||||||
new SearchBinaryOperator(ISearchBinaryOperator::OPERATOR_AND, [
|
|
||||||
new SearchComparison(ISearchComparison::COMPARE_LIKE, 'path', $internalPath . '%'),
|
|
||||||
$query->getSearchOperation(),
|
|
||||||
]),
|
|
||||||
$subQueryLimit,
|
|
||||||
0,
|
|
||||||
$query->getOrder(),
|
|
||||||
$query->getUser()
|
|
||||||
);
|
|
||||||
|
|
||||||
$files = [];
|
|
||||||
|
|
||||||
$cache = $storage->getCache('');
|
|
||||||
|
|
||||||
$results = $cache->searchQuery($rootQuery);
|
|
||||||
foreach ($results as $result) {
|
|
||||||
$files[] = $this->cacheEntryToFileInfo($mount, '', $internalPath, $result);
|
|
||||||
}
|
}
|
||||||
|
$mountByMountPoint = ['' => $mount];
|
||||||
|
|
||||||
if (!$limitToHome) {
|
if (!$limitToHome) {
|
||||||
$mounts = $this->root->getMountsIn($this->path);
|
$mounts = $this->root->getMountsIn($this->path);
|
||||||
foreach ($mounts as $mount) {
|
foreach ($mounts as $mount) {
|
||||||
$subQuery = new SearchQuery(
|
|
||||||
$query->getSearchOperation(),
|
|
||||||
$subQueryLimit,
|
|
||||||
0,
|
|
||||||
$query->getOrder(),
|
|
||||||
$query->getUser()
|
|
||||||
);
|
|
||||||
|
|
||||||
$storage = $mount->getStorage();
|
$storage = $mount->getStorage();
|
||||||
if ($storage) {
|
if ($storage) {
|
||||||
$cache = $storage->getCache('');
|
|
||||||
|
|
||||||
$relativeMountPoint = ltrim(substr($mount->getMountPoint(), $rootLength), '/');
|
$relativeMountPoint = ltrim(substr($mount->getMountPoint(), $rootLength), '/');
|
||||||
$results = $cache->searchQuery($subQuery);
|
$caches[$relativeMountPoint] = $storage->getCache('');
|
||||||
foreach ($results as $result) {
|
$mountByMountPoint[$relativeMountPoint] = $mount;
|
||||||
$files[] = $this->cacheEntryToFileInfo($mount, $relativeMountPoint, '', $result);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @var QuerySearchHelper $searchHelper */
|
||||||
|
$searchHelper = \OC::$server->get(QuerySearchHelper::class);
|
||||||
|
$resultsPerCache = $searchHelper->searchInCaches($query, $caches);
|
||||||
|
|
||||||
|
// loop trough 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) {
|
||||||
|
$mount = $mountByMountPoint[$relativeMountPoint];
|
||||||
|
return array_map(function (ICacheEntry $result) use ($relativeMountPoint, $mount) {
|
||||||
|
return $this->cacheEntryToFileInfo($mount, $relativeMountPoint, $result);
|
||||||
|
}, $results);
|
||||||
|
}, array_values($resultsPerCache), array_keys($resultsPerCache)));
|
||||||
|
|
||||||
|
// since results were returned per-cache, they are no longer fully sorted
|
||||||
$order = $query->getOrder();
|
$order = $query->getOrder();
|
||||||
if ($order) {
|
if ($order) {
|
||||||
usort($files, function (FileInfo $a, FileInfo $b) use ($order) {
|
usort($files, function (FileInfo $a, FileInfo $b) use ($order) {
|
||||||
|
@ -315,17 +294,15 @@ class Folder extends Node implements \OCP\Files\Folder {
|
||||||
return 0;
|
return 0;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
$files = array_values(array_slice($files, $query->getOffset(), $query->getLimit() > 0 ? $query->getLimit() : null));
|
|
||||||
|
|
||||||
return array_map(function (FileInfo $file) {
|
return array_map(function (FileInfo $file) {
|
||||||
return $this->createNode($file->getPath(), $file);
|
return $this->createNode($file->getPath(), $file);
|
||||||
}, $files);
|
}, $files);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function cacheEntryToFileInfo(IMountPoint $mount, string $appendRoot, string $trimRoot, ICacheEntry $cacheEntry): FileInfo {
|
private function cacheEntryToFileInfo(IMountPoint $mount, string $appendRoot, ICacheEntry $cacheEntry): FileInfo {
|
||||||
$trimLength = strlen($trimRoot);
|
|
||||||
$cacheEntry['internalPath'] = $cacheEntry['path'];
|
$cacheEntry['internalPath'] = $cacheEntry['path'];
|
||||||
$cacheEntry['path'] = $appendRoot . substr($cacheEntry['path'], $trimLength);
|
$cacheEntry['path'] = $appendRoot . $cacheEntry->getPath();
|
||||||
return new \OC\Files\FileInfo($this->path . '/' . $cacheEntry['path'], $mount->getStorage(), $cacheEntry['internalPath'], $cacheEntry, $mount);
|
return new \OC\Files\FileInfo($this->path . '/' . $cacheEntry['path'], $mount->getStorage(), $cacheEntry['internalPath'], $cacheEntry, $mount);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -24,10 +24,13 @@
|
||||||
namespace OC\Lockdown\Filesystem;
|
namespace OC\Lockdown\Filesystem;
|
||||||
|
|
||||||
use OC\Files\Cache\CacheEntry;
|
use OC\Files\Cache\CacheEntry;
|
||||||
|
use OC\Files\Search\SearchComparison;
|
||||||
use OCP\Constants;
|
use OCP\Constants;
|
||||||
use OCP\Files\Cache\ICache;
|
use OCP\Files\Cache\ICache;
|
||||||
use OCP\Files\Cache\ICacheEntry;
|
use OCP\Files\Cache\ICacheEntry;
|
||||||
use OCP\Files\FileInfo;
|
use OCP\Files\FileInfo;
|
||||||
|
use OCP\Files\Search\ISearchComparison;
|
||||||
|
use OCP\Files\Search\ISearchOperator;
|
||||||
use OCP\Files\Search\ISearchQuery;
|
use OCP\Files\Search\ISearchQuery;
|
||||||
|
|
||||||
class NullCache implements ICache {
|
class NullCache implements ICache {
|
||||||
|
@ -127,4 +130,12 @@ class NullCache implements ICache {
|
||||||
public function copyFromCache(ICache $sourceCache, ICacheEntry $sourceEntry, string $targetPath): int {
|
public function copyFromCache(ICache $sourceCache, ICacheEntry $sourceEntry, string $targetPath): int {
|
||||||
throw new \OC\ForbiddenException('This request is not allowed to access the filesystem');
|
throw new \OC\ForbiddenException('This request is not allowed to access the filesystem');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getQueryFilterForStorage(): ISearchOperator {
|
||||||
|
return new SearchComparison(ISearchComparison::COMPARE_EQUAL, 'storage', -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCacheEntryFromSearchResult(ICacheEntry $rawEntry): ?ICacheEntry {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,6 +23,7 @@
|
||||||
|
|
||||||
namespace OCP\Files\Cache;
|
namespace OCP\Files\Cache;
|
||||||
|
|
||||||
|
use OCP\Files\Search\ISearchOperator;
|
||||||
use OCP\Files\Search\ISearchQuery;
|
use OCP\Files\Search\ISearchQuery;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -265,4 +266,30 @@ interface ICache {
|
||||||
* @since 9.0.0
|
* @since 9.0.0
|
||||||
*/
|
*/
|
||||||
public function normalize($path);
|
public function normalize($path);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the query expression required to filter files within this storage.
|
||||||
|
*
|
||||||
|
* In the most basic case this is just comparing the storage id
|
||||||
|
* but storage wrappers can add additional expressions to filter down things further
|
||||||
|
*
|
||||||
|
* @return ISearchOperator
|
||||||
|
* @since 22.0.0
|
||||||
|
*/
|
||||||
|
public function getQueryFilterForStorage(): ISearchOperator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a cache entry from a search result row *if* the entry belongs to this storage.
|
||||||
|
*
|
||||||
|
* This method will be called for every item in the search results, including results from different storages.
|
||||||
|
* It's the responsibility of this method to return `null` for all results that don't belong to this storage.
|
||||||
|
*
|
||||||
|
* Additionally some implementations might need to further process the resulting entry such as modifying the path
|
||||||
|
* or permissions of the result.
|
||||||
|
*
|
||||||
|
* @param ICacheEntry $rawEntry
|
||||||
|
* @return ICacheEntry|null
|
||||||
|
* @since 22.0.0
|
||||||
|
*/
|
||||||
|
public function getCacheEntryFromSearchResult(ICacheEntry $rawEntry): ?ICacheEntry;
|
||||||
}
|
}
|
||||||
|
|
|
@ -363,7 +363,7 @@ class CacheTest extends \Test\TestCase {
|
||||||
$this->assertEquals(3, count($results));
|
$this->assertEquals(3, count($results));
|
||||||
|
|
||||||
usort($results, function ($value1, $value2) {
|
usort($results, function ($value1, $value2) {
|
||||||
return $value1['name'] >= $value2['name'];
|
return $value1['name'] <=> $value2['name'];
|
||||||
});
|
});
|
||||||
|
|
||||||
$this->assertEquals('folder', $results[0]['name']);
|
$this->assertEquals('folder', $results[0]['name']);
|
||||||
|
@ -376,7 +376,10 @@ class CacheTest extends \Test\TestCase {
|
||||||
static::logout();
|
static::logout();
|
||||||
$user = \OC::$server->getUserManager()->get($userId);
|
$user = \OC::$server->getUserManager()->get($userId);
|
||||||
if ($user !== null) {
|
if ($user !== null) {
|
||||||
$user->delete();
|
try {
|
||||||
|
$user->delete();
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -22,7 +22,7 @@
|
||||||
namespace Test\Files\Cache;
|
namespace Test\Files\Cache;
|
||||||
|
|
||||||
use OC\DB\QueryBuilder\Literal;
|
use OC\DB\QueryBuilder\Literal;
|
||||||
use OC\Files\Cache\QuerySearchHelper;
|
use OC\Files\Cache\SearchBuilder;
|
||||||
use OC\Files\Search\SearchBinaryOperator;
|
use OC\Files\Search\SearchBinaryOperator;
|
||||||
use OC\Files\Search\SearchComparison;
|
use OC\Files\Search\SearchComparison;
|
||||||
use OCP\DB\QueryBuilder\IQueryBuilder;
|
use OCP\DB\QueryBuilder\IQueryBuilder;
|
||||||
|
@ -35,17 +35,17 @@ use Test\TestCase;
|
||||||
/**
|
/**
|
||||||
* @group DB
|
* @group DB
|
||||||
*/
|
*/
|
||||||
class QuerySearchHelperTest extends TestCase {
|
class SearchBuilderTest extends TestCase {
|
||||||
/** @var IQueryBuilder */
|
/** @var IQueryBuilder */
|
||||||
private $builder;
|
private $builder;
|
||||||
|
|
||||||
/** @var IMimeTypeLoader|\PHPUnit\Framework\MockObject\MockObject */
|
/** @var IMimeTypeLoader|\PHPUnit\Framework\MockObject\MockObject */
|
||||||
private $mimetypeLoader;
|
private $mimetypeLoader;
|
||||||
|
|
||||||
/** @var QuerySearchHelper */
|
/** @var SearchBuilder */
|
||||||
private $querySearchHelper;
|
private $searchBuilder;
|
||||||
|
|
||||||
/** @var integer */
|
/** @var integer */
|
||||||
private $numericStorageId;
|
private $numericStorageId;
|
||||||
|
|
||||||
protected function setUp(): void {
|
protected function setUp(): void {
|
||||||
|
@ -75,7 +75,7 @@ class QuerySearchHelperTest extends TestCase {
|
||||||
[6, 'image']
|
[6, 'image']
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$this->querySearchHelper = new QuerySearchHelper($this->mimetypeLoader);
|
$this->searchBuilder = new SearchBuilder($this->mimetypeLoader);
|
||||||
$this->numericStorageId = 10000;
|
$this->numericStorageId = 10000;
|
||||||
|
|
||||||
$this->builder->select(['fileid'])
|
$this->builder->select(['fileid'])
|
||||||
|
@ -134,7 +134,7 @@ class QuerySearchHelperTest extends TestCase {
|
||||||
}
|
}
|
||||||
|
|
||||||
private function search(ISearchOperator $operator) {
|
private function search(ISearchOperator $operator) {
|
||||||
$dbOperator = $this->querySearchHelper->searchOperatorToDBExpr($this->builder, $operator);
|
$dbOperator = $this->searchBuilder->searchOperatorToDBExpr($this->builder, $operator);
|
||||||
$this->builder->andWhere($dbOperator);
|
$this->builder->andWhere($dbOperator);
|
||||||
|
|
||||||
$result = $this->builder->execute();
|
$result = $this->builder->execute();
|
|
@ -23,11 +23,11 @@ use OC\Files\Search\SearchQuery;
|
||||||
use OC\Files\Storage\Temporary;
|
use OC\Files\Storage\Temporary;
|
||||||
use OC\Files\Storage\Wrapper\Jail;
|
use OC\Files\Storage\Wrapper\Jail;
|
||||||
use OC\Files\View;
|
use OC\Files\View;
|
||||||
|
use OCP\Files\Cache\ICacheEntry;
|
||||||
use OCP\Files\Mount\IMountPoint;
|
use OCP\Files\Mount\IMountPoint;
|
||||||
use OCP\Files\NotFoundException;
|
use OCP\Files\NotFoundException;
|
||||||
use OCP\Files\Search\ISearchComparison;
|
use OCP\Files\Search\ISearchComparison;
|
||||||
use OCP\Files\Search\ISearchOrder;
|
use OCP\Files\Search\ISearchOrder;
|
||||||
use OCP\Files\Search\ISearchQuery;
|
|
||||||
use OCP\Files\Storage;
|
use OCP\Files\Storage;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -294,9 +294,10 @@ class FolderTest extends NodeTest {
|
||||||
->getMock();
|
->getMock();
|
||||||
$root->method('getUser')
|
$root->method('getUser')
|
||||||
->willReturn($this->user);
|
->willReturn($this->user);
|
||||||
$storage = $this->createMock(Storage::class);
|
/** @var Storage\IStorage $storage */
|
||||||
$storage->method('getId')->willReturn('');
|
$storage = $this->createMock(Storage\IStorage::class);
|
||||||
$cache = $this->getMockBuilder(Cache::class)->setConstructorArgs([$storage])->getMock();
|
$storage->method('getId')->willReturn('test::1');
|
||||||
|
$cache = new Cache($storage);
|
||||||
|
|
||||||
$storage->method('getCache')
|
$storage->method('getCache')
|
||||||
->willReturn($cache);
|
->willReturn($cache);
|
||||||
|
@ -307,10 +308,8 @@ class FolderTest extends NodeTest {
|
||||||
$mount->method('getInternalPath')
|
$mount->method('getInternalPath')
|
||||||
->willReturn('foo');
|
->willReturn('foo');
|
||||||
|
|
||||||
$cache->method('searchQuery')
|
$cache->insert('foo', ['size' => 200, 'mtime' => 55, 'mimetype' => ICacheEntry::DIRECTORY_MIMETYPE]);
|
||||||
->willReturn([
|
$cache->insert('foo/qwerty', ['size' => 200, 'mtime' => 55, 'mimetype' => 'text/plain']);
|
||||||
new CacheEntry(['fileid' => 3, 'path' => 'foo/qwerty', 'name' => 'qwerty', 'size' => 200, 'mtime' => 55, 'mimetype' => 'text/plain']),
|
|
||||||
]);
|
|
||||||
|
|
||||||
$root->method('getMountsIn')
|
$root->method('getMountsIn')
|
||||||
->with('/bar/foo')
|
->with('/bar/foo')
|
||||||
|
@ -322,6 +321,7 @@ class FolderTest extends NodeTest {
|
||||||
|
|
||||||
$node = new Folder($root, $view, '/bar/foo');
|
$node = new Folder($root, $view, '/bar/foo');
|
||||||
$result = $node->search('qw');
|
$result = $node->search('qw');
|
||||||
|
$cache->clear();
|
||||||
$this->assertEquals(1, count($result));
|
$this->assertEquals(1, count($result));
|
||||||
$this->assertEquals('/bar/foo/qwerty', $result[0]->getPath());
|
$this->assertEquals('/bar/foo/qwerty', $result[0]->getPath());
|
||||||
}
|
}
|
||||||
|
@ -341,8 +341,8 @@ class FolderTest extends NodeTest {
|
||||||
->willReturn($this->user);
|
->willReturn($this->user);
|
||||||
/** @var \PHPUnit\Framework\MockObject\MockObject|Storage $storage */
|
/** @var \PHPUnit\Framework\MockObject\MockObject|Storage $storage */
|
||||||
$storage = $this->createMock(Storage::class);
|
$storage = $this->createMock(Storage::class);
|
||||||
$storage->method('getId')->willReturn('');
|
$storage->method('getId')->willReturn('test::2');
|
||||||
$cache = $this->getMockBuilder(Cache::class)->setConstructorArgs([$storage])->getMock();
|
$cache = new Cache($storage);
|
||||||
|
|
||||||
$mount = $this->createMock(IMountPoint::class);
|
$mount = $this->createMock(IMountPoint::class);
|
||||||
$mount->method('getStorage')
|
$mount->method('getStorage')
|
||||||
|
@ -353,10 +353,8 @@ class FolderTest extends NodeTest {
|
||||||
$storage->method('getCache')
|
$storage->method('getCache')
|
||||||
->willReturn($cache);
|
->willReturn($cache);
|
||||||
|
|
||||||
$cache->method('searchQuery')
|
$cache->insert('files', ['size' => 200, 'mtime' => 55, 'mimetype' => ICacheEntry::DIRECTORY_MIMETYPE]);
|
||||||
->willReturn([
|
$cache->insert('files/foo', ['size' => 200, 'mtime' => 55, 'mimetype' => 'text/plain']);
|
||||||
new CacheEntry(['fileid' => 3, 'path' => 'files/foo', 'name' => 'qwerty', 'size' => 200, 'mtime' => 55, 'mimetype' => 'text/plain']),
|
|
||||||
]);
|
|
||||||
|
|
||||||
$root->method('getMountsIn')
|
$root->method('getMountsIn')
|
||||||
->with('')
|
->with('')
|
||||||
|
@ -366,7 +364,8 @@ class FolderTest extends NodeTest {
|
||||||
->with('')
|
->with('')
|
||||||
->willReturn($mount);
|
->willReturn($mount);
|
||||||
|
|
||||||
$result = $root->search('qw');
|
$result = $root->search('foo');
|
||||||
|
$cache->clear();
|
||||||
$this->assertEquals(1, count($result));
|
$this->assertEquals(1, count($result));
|
||||||
$this->assertEquals('/foo', $result[0]->getPath());
|
$this->assertEquals('/foo', $result[0]->getPath());
|
||||||
}
|
}
|
||||||
|
@ -383,8 +382,8 @@ class FolderTest extends NodeTest {
|
||||||
$root->method('getUser')
|
$root->method('getUser')
|
||||||
->willReturn($this->user);
|
->willReturn($this->user);
|
||||||
$storage = $this->createMock(Storage::class);
|
$storage = $this->createMock(Storage::class);
|
||||||
$storage->method('getId')->willReturn('');
|
$storage->method('getId')->willReturn('test::1');
|
||||||
$cache = $this->getMockBuilder(Cache::class)->setConstructorArgs([$storage])->getMock();
|
$cache = new Cache($storage);
|
||||||
|
|
||||||
$mount = $this->createMock(IMountPoint::class);
|
$mount = $this->createMock(IMountPoint::class);
|
||||||
$mount->method('getStorage')
|
$mount->method('getStorage')
|
||||||
|
@ -395,10 +394,9 @@ class FolderTest extends NodeTest {
|
||||||
$storage->method('getCache')
|
$storage->method('getCache')
|
||||||
->willReturn($cache);
|
->willReturn($cache);
|
||||||
|
|
||||||
$cache->method('searchQuery')
|
$cache->insert('foo', ['size' => 200, 'mtime' => 55, 'mimetype' => ICacheEntry::DIRECTORY_MIMETYPE]);
|
||||||
->willReturn([
|
$cache->insert('foo/qwerty', ['size' => 200, 'mtime' => 55, 'mimetype' => 'text/plain']);
|
||||||
new CacheEntry(['fileid' => 3, 'path' => 'foo/qwerty', 'name' => 'qwerty', 'size' => 200, 'mtime' => 55, 'mimetype' => 'text/plain']),
|
|
||||||
]);
|
|
||||||
|
|
||||||
$root->method('getMountsIn')
|
$root->method('getMountsIn')
|
||||||
->with('/bar')
|
->with('/bar')
|
||||||
|
@ -410,6 +408,7 @@ class FolderTest extends NodeTest {
|
||||||
|
|
||||||
$node = new Folder($root, $view, '/bar');
|
$node = new Folder($root, $view, '/bar');
|
||||||
$result = $node->search('qw');
|
$result = $node->search('qw');
|
||||||
|
$cache->clear();
|
||||||
$this->assertEquals(1, count($result));
|
$this->assertEquals(1, count($result));
|
||||||
$this->assertEquals('/bar/foo/qwerty', $result[0]->getPath());
|
$this->assertEquals('/bar/foo/qwerty', $result[0]->getPath());
|
||||||
}
|
}
|
||||||
|
@ -427,10 +426,11 @@ class FolderTest extends NodeTest {
|
||||||
->method('getUser')
|
->method('getUser')
|
||||||
->willReturn($this->user);
|
->willReturn($this->user);
|
||||||
$storage = $this->createMock(Storage::class);
|
$storage = $this->createMock(Storage::class);
|
||||||
$storage->method('getId')->willReturn('');
|
$storage->method('getId')->willReturn('test::1');
|
||||||
$cache = $this->getMockBuilder(Cache::class)->setConstructorArgs([$storage])->getMock();
|
$cache = new Cache($storage);
|
||||||
$subCache = $this->getMockBuilder(Cache::class)->setConstructorArgs([$storage])->getMock();
|
|
||||||
$subStorage = $this->createMock(Storage::class);
|
$subStorage = $this->createMock(Storage::class);
|
||||||
|
$subStorage->method('getId')->willReturn('test::2');
|
||||||
|
$subCache = new Cache($subStorage);
|
||||||
$subMount = $this->getMockBuilder(MountPoint::class)->setConstructorArgs([null, ''])->getMock();
|
$subMount = $this->getMockBuilder(MountPoint::class)->setConstructorArgs([null, ''])->getMock();
|
||||||
|
|
||||||
$mount = $this->createMock(IMountPoint::class);
|
$mount = $this->createMock(IMountPoint::class);
|
||||||
|
@ -451,15 +451,12 @@ class FolderTest extends NodeTest {
|
||||||
$subStorage->method('getCache')
|
$subStorage->method('getCache')
|
||||||
->willReturn($subCache);
|
->willReturn($subCache);
|
||||||
|
|
||||||
$cache->method('searchQuery')
|
$cache->insert('foo', ['size' => 200, 'mtime' => 55, 'mimetype' => ICacheEntry::DIRECTORY_MIMETYPE]);
|
||||||
->willReturn([
|
$cache->insert('foo/qwerty', ['size' => 200, 'mtime' => 55, 'mimetype' => 'text/plain']);
|
||||||
new CacheEntry(['fileid' => 3, 'path' => 'foo/qwerty', 'name' => 'qwerty', 'size' => 200, 'mtime' => 55, 'mimetype' => 'text/plain']),
|
|
||||||
]);
|
$subCache->insert('asd', ['size' => 200, 'mtime' => 55, 'mimetype' => ICacheEntry::DIRECTORY_MIMETYPE]);
|
||||||
|
$subCache->insert('asd/qwerty', ['size' => 200, 'mtime' => 55, 'mimetype' => 'text/plain']);
|
||||||
|
|
||||||
$subCache->method('searchQuery')
|
|
||||||
->willReturn([
|
|
||||||
new CacheEntry(['fileid' => 4, 'path' => 'asd/qweasd', 'name' => 'qweasd', 'size' => 200, 'mtime' => 55, 'mimetype' => 'text/plain']),
|
|
||||||
]);
|
|
||||||
|
|
||||||
$root->method('getMountsIn')
|
$root->method('getMountsIn')
|
||||||
->with('/bar/foo')
|
->with('/bar/foo')
|
||||||
|
@ -472,6 +469,8 @@ class FolderTest extends NodeTest {
|
||||||
|
|
||||||
$node = new Folder($root, $view, '/bar/foo');
|
$node = new Folder($root, $view, '/bar/foo');
|
||||||
$result = $node->search('qw');
|
$result = $node->search('qw');
|
||||||
|
$cache->clear();
|
||||||
|
$subCache->clear();
|
||||||
$this->assertEquals(2, count($result));
|
$this->assertEquals(2, count($result));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -944,18 +943,18 @@ class FolderTest extends NodeTest {
|
||||||
|
|
||||||
public function offsetLimitProvider() {
|
public function offsetLimitProvider() {
|
||||||
return [
|
return [
|
||||||
[0, 10, [10, 11, 12, 13, 14, 15, 16, 17], []],
|
[0, 10, ['/bar/foo/foo1', '/bar/foo/foo2', '/bar/foo/foo3', '/bar/foo/foo4', '/bar/foo/sub1/foo5', '/bar/foo/sub1/foo6', '/bar/foo/sub2/foo7', '/bar/foo/sub2/foo8'], []],
|
||||||
[0, 5, [10, 11, 12, 13, 14], []],
|
[0, 5, ['/bar/foo/foo1', '/bar/foo/foo2', '/bar/foo/foo3', '/bar/foo/foo4', '/bar/foo/sub1/foo5'], []],
|
||||||
[0, 2, [10, 11], []],
|
[0, 2, ['/bar/foo/foo1', '/bar/foo/foo2'], []],
|
||||||
[3, 2, [13, 14], []],
|
[3, 2, ['/bar/foo/foo4', '/bar/foo/sub1/foo5'], []],
|
||||||
[3, 5, [13, 14, 15, 16, 17], []],
|
[3, 5, ['/bar/foo/foo4', '/bar/foo/sub1/foo5', '/bar/foo/sub1/foo6', '/bar/foo/sub2/foo7', '/bar/foo/sub2/foo8'], []],
|
||||||
[5, 2, [15, 16], []],
|
[5, 2, ['/bar/foo/sub1/foo6', '/bar/foo/sub2/foo7'], []],
|
||||||
[6, 2, [16, 17], []],
|
[6, 2, ['/bar/foo/sub2/foo7', '/bar/foo/sub2/foo8'], []],
|
||||||
[7, 2, [17], []],
|
[7, 2, ['/bar/foo/sub2/foo8'], []],
|
||||||
[10, 2, [], []],
|
[10, 2, [], []],
|
||||||
[0, 5, [16, 10, 14, 11, 12], [new SearchOrder(ISearchOrder::DIRECTION_ASCENDING, 'mtime')]],
|
[0, 5, ['/bar/foo/sub2/foo7', '/bar/foo/foo1', '/bar/foo/sub1/foo5', '/bar/foo/foo2', '/bar/foo/foo3'], [new SearchOrder(ISearchOrder::DIRECTION_ASCENDING, 'mtime')]],
|
||||||
[3, 2, [11, 12], [new SearchOrder(ISearchOrder::DIRECTION_ASCENDING, 'mtime')]],
|
[3, 2, ['/bar/foo/foo2', '/bar/foo/foo3'], [new SearchOrder(ISearchOrder::DIRECTION_ASCENDING, 'mtime')]],
|
||||||
[0, 5, [14, 15, 16, 10, 11], [
|
[0, 5, ['/bar/foo/sub1/foo5', '/bar/foo/sub1/foo6', '/bar/foo/sub2/foo7', '/bar/foo/foo1', '/bar/foo/foo2'], [
|
||||||
new SearchOrder(ISearchOrder::DIRECTION_DESCENDING, 'size'),
|
new SearchOrder(ISearchOrder::DIRECTION_DESCENDING, 'size'),
|
||||||
new SearchOrder(ISearchOrder::DIRECTION_ASCENDING, 'mtime')
|
new SearchOrder(ISearchOrder::DIRECTION_ASCENDING, 'mtime')
|
||||||
]],
|
]],
|
||||||
|
@ -966,12 +965,16 @@ class FolderTest extends NodeTest {
|
||||||
* @dataProvider offsetLimitProvider
|
* @dataProvider offsetLimitProvider
|
||||||
* @param int $offset
|
* @param int $offset
|
||||||
* @param int $limit
|
* @param int $limit
|
||||||
* @param int[] $expectedIds
|
* @param string[] $expectedPaths
|
||||||
* @param ISearchOrder[] $ordering
|
* @param ISearchOrder[] $ordering
|
||||||
* @throws NotFoundException
|
* @throws NotFoundException
|
||||||
* @throws \OCP\Files\InvalidPathException
|
* @throws \OCP\Files\InvalidPathException
|
||||||
*/
|
*/
|
||||||
public function testSearchSubStoragesLimitOffset(int $offset, int $limit, array $expectedIds, array $ordering) {
|
public function testSearchSubStoragesLimitOffset(int $offset, int $limit, array $expectedPaths, array $ordering) {
|
||||||
|
if (!$ordering) {
|
||||||
|
$ordering = [new SearchOrder(ISearchOrder::DIRECTION_ASCENDING, 'fileid')];
|
||||||
|
}
|
||||||
|
|
||||||
$manager = $this->createMock(Manager::class);
|
$manager = $this->createMock(Manager::class);
|
||||||
/**
|
/**
|
||||||
* @var \OC\Files\View | \PHPUnit\Framework\MockObject\MockObject $view
|
* @var \OC\Files\View | \PHPUnit\Framework\MockObject\MockObject $view
|
||||||
|
@ -984,13 +987,15 @@ class FolderTest extends NodeTest {
|
||||||
->method('getUser')
|
->method('getUser')
|
||||||
->willReturn($this->user);
|
->willReturn($this->user);
|
||||||
$storage = $this->createMock(Storage::class);
|
$storage = $this->createMock(Storage::class);
|
||||||
$storage->method('getId')->willReturn('');
|
$storage->method('getId')->willReturn('test::1');
|
||||||
$cache = $this->getMockBuilder(Cache::class)->setConstructorArgs([$storage])->getMock();
|
$cache = new Cache($storage);
|
||||||
$subCache1 = $this->getMockBuilder(Cache::class)->setConstructorArgs([$storage])->getMock();
|
|
||||||
$subStorage1 = $this->createMock(Storage::class);
|
$subStorage1 = $this->createMock(Storage::class);
|
||||||
|
$subStorage1->method('getId')->willReturn('test::2');
|
||||||
|
$subCache1 = new Cache($subStorage1);
|
||||||
$subMount1 = $this->getMockBuilder(MountPoint::class)->setConstructorArgs([null, ''])->getMock();
|
$subMount1 = $this->getMockBuilder(MountPoint::class)->setConstructorArgs([null, ''])->getMock();
|
||||||
$subCache2 = $this->getMockBuilder(Cache::class)->setConstructorArgs([$storage])->getMock();
|
|
||||||
$subStorage2 = $this->createMock(Storage::class);
|
$subStorage2 = $this->createMock(Storage::class);
|
||||||
|
$subStorage2->method('getId')->willReturn('test::3');
|
||||||
|
$subCache2 = new Cache($subStorage2);
|
||||||
$subMount2 = $this->getMockBuilder(MountPoint::class)->setConstructorArgs([null, ''])->getMock();
|
$subMount2 = $this->getMockBuilder(MountPoint::class)->setConstructorArgs([null, ''])->getMock();
|
||||||
|
|
||||||
$mount = $this->createMock(IMountPoint::class);
|
$mount = $this->createMock(IMountPoint::class);
|
||||||
|
@ -1003,7 +1008,7 @@ class FolderTest extends NodeTest {
|
||||||
->willReturn($subStorage1);
|
->willReturn($subStorage1);
|
||||||
|
|
||||||
$subMount1->method('getMountPoint')
|
$subMount1->method('getMountPoint')
|
||||||
->willReturn('/bar/foo/bar/');
|
->willReturn('/bar/foo/sub1/');
|
||||||
|
|
||||||
$storage->method('getCache')
|
$storage->method('getCache')
|
||||||
->willReturn($cache);
|
->willReturn($cache);
|
||||||
|
@ -1015,36 +1020,21 @@ class FolderTest extends NodeTest {
|
||||||
->willReturn($subStorage2);
|
->willReturn($subStorage2);
|
||||||
|
|
||||||
$subMount2->method('getMountPoint')
|
$subMount2->method('getMountPoint')
|
||||||
->willReturn('/bar/foo/bar2/');
|
->willReturn('/bar/foo/sub2/');
|
||||||
|
|
||||||
$subStorage2->method('getCache')
|
$subStorage2->method('getCache')
|
||||||
->willReturn($subCache2);
|
->willReturn($subCache2);
|
||||||
|
|
||||||
$cache->method('searchQuery')
|
$cache->insert('foo/foo1', ['size' => 200, 'mtime' => 10, 'mimetype' => 'text/plain']);
|
||||||
->willReturnCallback(function (ISearchQuery $query) {
|
$cache->insert('foo/foo2', ['size' => 200, 'mtime' => 20, 'mimetype' => 'text/plain']);
|
||||||
return array_slice([
|
$cache->insert('foo/foo3', ['size' => 200, 'mtime' => 30, 'mimetype' => 'text/plain']);
|
||||||
new CacheEntry(['fileid' => 10, 'path' => 'foo/qwerty', 'name' => 'qwerty', 'size' => 200, 'mtime' => 10, 'mimetype' => 'text/plain']),
|
$cache->insert('foo/foo4', ['size' => 200, 'mtime' => 40, 'mimetype' => 'text/plain']);
|
||||||
new CacheEntry(['fileid' => 11, 'path' => 'foo/qwerty', 'name' => 'qwerty', 'size' => 200, 'mtime' => 20, 'mimetype' => 'text/plain']),
|
|
||||||
new CacheEntry(['fileid' => 12, 'path' => 'foo/qwerty', 'name' => 'qwerty', 'size' => 200, 'mtime' => 30, 'mimetype' => 'text/plain']),
|
|
||||||
new CacheEntry(['fileid' => 13, 'path' => 'foo/qwerty', 'name' => 'qwerty', 'size' => 200, 'mtime' => 40, 'mimetype' => 'text/plain']),
|
|
||||||
], $query->getOffset(), $query->getOffset() + $query->getLimit());
|
|
||||||
});
|
|
||||||
|
|
||||||
$subCache1->method('searchQuery')
|
$subCache1->insert('foo5', ['size' => 300, 'mtime' => 15, 'mimetype' => 'text/plain']);
|
||||||
->willReturnCallback(function (ISearchQuery $query) {
|
$subCache1->insert('foo6', ['size' => 300, 'mtime' => 50, 'mimetype' => 'text/plain']);
|
||||||
return array_slice([
|
|
||||||
new CacheEntry(['fileid' => 14, 'path' => 'foo/qwerty', 'name' => 'qwerty', 'size' => 300, 'mtime' => 15, 'mimetype' => 'text/plain']),
|
|
||||||
new CacheEntry(['fileid' => 15, 'path' => 'foo/qwerty', 'name' => 'qwerty', 'size' => 300, 'mtime' => 50, 'mimetype' => 'text/plain']),
|
|
||||||
], $query->getOffset(), $query->getOffset() + $query->getLimit());
|
|
||||||
});
|
|
||||||
|
|
||||||
$subCache2->method('searchQuery')
|
$subCache2->insert('foo7', ['size' => 200, 'mtime' => 5, 'mimetype' => 'text/plain']);
|
||||||
->willReturnCallback(function (ISearchQuery $query) {
|
$subCache2->insert('foo8', ['size' => 200, 'mtime' => 60, 'mimetype' => 'text/plain']);
|
||||||
return array_slice([
|
|
||||||
new CacheEntry(['fileid' => 16, 'path' => 'foo/qwerty', 'name' => 'qwerty', 'size' => 200, 'mtime' => 5, 'mimetype' => 'text/plain']),
|
|
||||||
new CacheEntry(['fileid' => 17, 'path' => 'foo/qwerty', 'name' => 'qwerty', 'size' => 200, 'mtime' => 60, 'mimetype' => 'text/plain']),
|
|
||||||
], $query->getOffset(), $query->getOffset() + $query->getLimit());
|
|
||||||
});
|
|
||||||
|
|
||||||
$root->method('getMountsIn')
|
$root->method('getMountsIn')
|
||||||
->with('/bar/foo')
|
->with('/bar/foo')
|
||||||
|
@ -1054,14 +1044,16 @@ class FolderTest extends NodeTest {
|
||||||
->with('/bar/foo')
|
->with('/bar/foo')
|
||||||
->willReturn($mount);
|
->willReturn($mount);
|
||||||
|
|
||||||
|
|
||||||
$node = new Folder($root, $view, '/bar/foo');
|
$node = new Folder($root, $view, '/bar/foo');
|
||||||
$comparison = new SearchComparison(ISearchComparison::COMPARE_LIKE, 'name', '%foo%');
|
$comparison = new SearchComparison(ISearchComparison::COMPARE_LIKE, 'name', '%foo%');
|
||||||
$query = new SearchQuery($comparison, $limit, $offset, $ordering);
|
$query = new SearchQuery($comparison, $limit, $offset, $ordering);
|
||||||
$result = $node->search($query);
|
$result = $node->search($query);
|
||||||
|
$cache->clear();
|
||||||
|
$subCache1->clear();
|
||||||
|
$subCache2->clear();
|
||||||
$ids = array_map(function (Node $info) {
|
$ids = array_map(function (Node $info) {
|
||||||
return $info->getId();
|
return $info->getPath();
|
||||||
}, $result);
|
}, $result);
|
||||||
$this->assertEquals($expectedIds, $ids);
|
$this->assertEquals($expectedPaths, $ids);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue