Implement webdav SEARCHtags/v12.0.0beta1
@@ -1 +1 @@ | |||
Subproject commit 489bcf4f81e462f4d74f0b76f58caeabd58e75de | |||
Subproject commit 98fa92c67d735f82ae012786395e660f1513bef7 |
@@ -0,0 +1,265 @@ | |||
<?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 OCA\DAV\Files; | |||
use OC\Files\Search\SearchBinaryOperator; | |||
use OC\Files\Search\SearchComparison; | |||
use OC\Files\Search\SearchOrder; | |||
use OC\Files\Search\SearchQuery; | |||
use OC\Files\View; | |||
use OCA\DAV\Connector\Sabre\Directory; | |||
use OCA\DAV\Connector\Sabre\FilesPlugin; | |||
use OCP\Files\Cache\ICacheEntry; | |||
use OCP\Files\Folder; | |||
use OCP\Files\IRootFolder; | |||
use OCP\Files\Node; | |||
use OCP\Files\Search\ISearchOperator; | |||
use OCP\Files\Search\ISearchOrder; | |||
use OCP\Files\Search\ISearchQuery; | |||
use OCP\IUser; | |||
use OCP\Share\IManager; | |||
use Sabre\DAV\Exception\NotFound; | |||
use Sabre\DAV\Tree; | |||
use SearchDAV\Backend\ISearchBackend; | |||
use SearchDAV\Backend\SearchPropertyDefinition; | |||
use SearchDAV\Backend\SearchResult; | |||
use SearchDAV\XML\BasicSearch; | |||
use SearchDAV\XML\Literal; | |||
use SearchDAV\XML\Operator; | |||
use SearchDAV\XML\Order; | |||
class FileSearchBackend implements ISearchBackend { | |||
/** @var Tree */ | |||
private $tree; | |||
/** @var IUser */ | |||
private $user; | |||
/** @var IRootFolder */ | |||
private $rootFolder; | |||
/** @var IManager */ | |||
private $shareManager; | |||
/** @var View */ | |||
private $view; | |||
/** | |||
* FileSearchBackend constructor. | |||
* | |||
* @param Tree $tree | |||
* @param IUser $user | |||
* @param IRootFolder $rootFolder | |||
* @param IManager $shareManager | |||
* @param View $view | |||
* @internal param IRootFolder $rootFolder | |||
*/ | |||
public function __construct(Tree $tree, IUser $user, IRootFolder $rootFolder, IManager $shareManager, View $view) { | |||
$this->tree = $tree; | |||
$this->user = $user; | |||
$this->rootFolder = $rootFolder; | |||
$this->shareManager = $shareManager; | |||
$this->view = $view; | |||
} | |||
/** | |||
* Search endpoint will be remote.php/dav | |||
* | |||
* @return string | |||
*/ | |||
public function getArbiterPath() { | |||
return ''; | |||
} | |||
public function isValidScope($href, $depth, $path) { | |||
// only allow scopes inside the dav server | |||
if (is_null($path)) { | |||
return false; | |||
} | |||
try { | |||
$node = $this->tree->getNodeForPath($path); | |||
return $node instanceof Directory; | |||
} catch (NotFound $e) { | |||
return false; | |||
} | |||
} | |||
public function getPropertyDefinitionsForScope($href, $path) { | |||
// all valid scopes support the same schema | |||
//todo dynamically load all propfind properties that are supported | |||
return [ | |||
// queryable properties | |||
new SearchPropertyDefinition('{DAV:}displayname', true, false, true), | |||
new SearchPropertyDefinition('{DAV:}getcontenttype', true, true, true), | |||
new SearchPropertyDefinition('{DAV:}getlastmodifed', true, true, true, SearchPropertyDefinition::DATATYPE_DATETIME), | |||
new SearchPropertyDefinition(FilesPlugin::SIZE_PROPERTYNAME, true, true, true, SearchPropertyDefinition::DATATYPE_NONNEGATIVE_INTEGER), | |||
// select only properties | |||
new SearchPropertyDefinition('{DAV:}resourcetype', false, true, false), | |||
new SearchPropertyDefinition('{DAV:}getcontentlength', false, true, false), | |||
new SearchPropertyDefinition(FilesPlugin::CHECKSUMS_PROPERTYNAME, false, true, false), | |||
new SearchPropertyDefinition(FilesPlugin::PERMISSIONS_PROPERTYNAME, false, true, false), | |||
new SearchPropertyDefinition(FilesPlugin::GETETAG_PROPERTYNAME, false, true, false), | |||
new SearchPropertyDefinition(FilesPlugin::OWNER_ID_PROPERTYNAME, false, true, false), | |||
new SearchPropertyDefinition(FilesPlugin::OWNER_DISPLAY_NAME_PROPERTYNAME, false, true, false), | |||
new SearchPropertyDefinition(FilesPlugin::DATA_FINGERPRINT_PROPERTYNAME, false, true, false), | |||
new SearchPropertyDefinition(FilesPlugin::HAS_PREVIEW_PROPERTYNAME, false, true, false, SearchPropertyDefinition::DATATYPE_BOOLEAN), | |||
new SearchPropertyDefinition(FilesPlugin::INTERNAL_FILEID_PROPERTYNAME, false, true, false, SearchPropertyDefinition::DATATYPE_NONNEGATIVE_INTEGER), | |||
new SearchPropertyDefinition(FilesPlugin::FILEID_PROPERTYNAME, false, true, false, SearchPropertyDefinition::DATATYPE_NONNEGATIVE_INTEGER), | |||
]; | |||
} | |||
/** | |||
* @param BasicSearch $search | |||
* @return SearchResult[] | |||
*/ | |||
public function search(BasicSearch $search) { | |||
if (count($search->from) !== 1) { | |||
throw new \InvalidArgumentException('Searching more than one folder is not supported'); | |||
} | |||
$query = $this->transformQuery($search); | |||
$scope = $search->from[0]; | |||
if ($scope->path === null) { | |||
throw new \InvalidArgumentException('Using uri\'s as scope is not supported, please use a path relative to the search arbiter instead'); | |||
} | |||
$node = $this->tree->getNodeForPath($scope->path); | |||
if (!$node instanceof Directory) { | |||
throw new \InvalidArgumentException('Search is only supported on directories'); | |||
} | |||
$fileInfo = $node->getFileInfo(); | |||
$folder = $this->rootFolder->get($fileInfo->getPath()); | |||
/** @var Folder $folder $results */ | |||
$results = $folder->search($query); | |||
return array_map(function (Node $node) { | |||
if ($node instanceof Folder) { | |||
return new SearchResult(new \OCA\DAV\Connector\Sabre\Directory($this->view, $node, $this->tree, $this->shareManager), $this->getHrefForNode($node)); | |||
} else { | |||
return new SearchResult(new \OCA\DAV\Connector\Sabre\File($this->view, $node, $this->shareManager), $this->getHrefForNode($node)); | |||
} | |||
}, $results); | |||
} | |||
/** | |||
* @param Node $node | |||
* @return string | |||
*/ | |||
private function getHrefForNode(Node $node) { | |||
$base = '/files/' . $this->user->getUID(); | |||
return $base . $this->view->getRelativePath($node->getPath()); | |||
} | |||
/** | |||
* @param BasicSearch $query | |||
* @return ISearchQuery | |||
*/ | |||
private function transformQuery(BasicSearch $query) { | |||
// TODO offset, limit | |||
$orders = array_map([$this, 'mapSearchOrder'], $query->orderBy); | |||
return new SearchQuery($this->transformSearchOperation($query->where), 0, 0, $orders); | |||
} | |||
/** | |||
* @param Order $order | |||
* @return ISearchOrder | |||
*/ | |||
private function mapSearchOrder(Order $order) { | |||
return new SearchOrder($order->order === Order::ASC ? ISearchOrder::DIRECTION_ASCENDING : ISearchOrder::DIRECTION_DESCENDING, $this->mapPropertyNameToCollumn($order->property)); | |||
} | |||
/** | |||
* @param Operator $operator | |||
* @return ISearchOperator | |||
*/ | |||
private function transformSearchOperation(Operator $operator) { | |||
list(, $trimmedType) = explode('}', $operator->type); | |||
switch ($operator->type) { | |||
case Operator::OPERATION_AND: | |||
case Operator::OPERATION_OR: | |||
case Operator::OPERATION_NOT: | |||
$arguments = array_map([$this, 'transformSearchOperation'], $operator->arguments); | |||
return new SearchBinaryOperator($trimmedType, $arguments); | |||
case Operator::OPERATION_EQUAL: | |||
case Operator::OPERATION_GREATER_OR_EQUAL_THAN: | |||
case Operator::OPERATION_GREATER_THAN: | |||
case Operator::OPERATION_LESS_OR_EQUAL_THAN: | |||
case Operator::OPERATION_LESS_THAN: | |||
case Operator::OPERATION_IS_LIKE: | |||
if (count($operator->arguments) !== 2) { | |||
throw new \InvalidArgumentException('Invalid number of arguments for ' . $trimmedType . ' operation'); | |||
} | |||
if (gettype($operator->arguments[0]) !== 'string') { | |||
throw new \InvalidArgumentException('Invalid argument 1 for ' . $trimmedType . ' operation, expected property'); | |||
} | |||
if (!($operator->arguments[1] instanceof Literal)) { | |||
throw new \InvalidArgumentException('Invalid argument 2 for ' . $trimmedType . ' operation, expected literal'); | |||
} | |||
return new SearchComparison($trimmedType, $this->mapPropertyNameToCollumn($operator->arguments[0]), $this->castValue($operator->arguments[0], $operator->arguments[1]->value)); | |||
case Operator::OPERATION_IS_COLLECTION: | |||
return new SearchComparison('eq', 'mimetype', ICacheEntry::DIRECTORY_MIMETYPE); | |||
default: | |||
throw new \InvalidArgumentException('Unsupported operation ' . $trimmedType. ' (' . $operator->type . ')'); | |||
} | |||
} | |||
/** | |||
* @param string $propertyName | |||
* @return string | |||
*/ | |||
private function mapPropertyNameToCollumn($propertyName) { | |||
switch ($propertyName) { | |||
case '{DAV:}displayname': | |||
return 'name'; | |||
case '{DAV:}getcontenttype': | |||
return 'mimetype'; | |||
case '{DAV:}getlastmodifed': | |||
return 'mtime'; | |||
case FilesPlugin::SIZE_PROPERTYNAME: | |||
return 'size'; | |||
default: | |||
throw new \InvalidArgumentException('Unsupported property for search or order: ' . $propertyName); | |||
} | |||
} | |||
private function castValue($propertyName, $value) { | |||
$allProps = $this->getPropertyDefinitionsForScope('', ''); | |||
foreach ($allProps as $prop) { | |||
if ($prop->name === $propertyName) { | |||
$dataType = $prop->dataType; | |||
switch ($dataType) { | |||
case SearchPropertyDefinition::DATATYPE_BOOLEAN: | |||
return $value === 'yes'; | |||
case SearchPropertyDefinition::DATATYPE_DECIMAL: | |||
case SearchPropertyDefinition::DATATYPE_INTEGER: | |||
case SearchPropertyDefinition::DATATYPE_NONNEGATIVE_INTEGER: | |||
return 0 + $value; | |||
default: | |||
return $value; | |||
} | |||
} | |||
} | |||
return $value; | |||
} | |||
} |
@@ -51,6 +51,7 @@ use OCP\SabrePluginEvent; | |||
use Sabre\CardDAV\VCFExportPlugin; | |||
use Sabre\DAV\Auth\Plugin; | |||
use OCA\DAV\Connector\Sabre\TagsPlugin; | |||
use SearchDAV\DAV\SearchPlugin; | |||
class Server { | |||
@@ -223,6 +224,13 @@ class Server { | |||
\OC::$server->getGroupManager(), | |||
$userFolder | |||
)); | |||
$this->server->addPlugin(new SearchPlugin(new \OCA\DAV\Files\FileSearchBackend( | |||
$this->server->tree, | |||
$user, | |||
\OC::$server->getRootFolder(), | |||
\OC::$server->getShareManager(), | |||
$view | |||
))); | |||
} | |||
} | |||
}); |
@@ -0,0 +1,299 @@ | |||
<?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 OCA\DAV\Tests\Files; | |||
use OC\Files\Search\SearchComparison; | |||
use OC\Files\Search\SearchQuery; | |||
use OC\Files\View; | |||
use OCA\DAV\Connector\Sabre\Directory; | |||
use OCA\DAV\Connector\Sabre\File; | |||
use OCA\DAV\Connector\Sabre\FilesPlugin; | |||
use OCA\DAV\Files\FileSearchBackend; | |||
use OCP\Files\FileInfo; | |||
use OCP\Files\Folder; | |||
use OCP\Files\IRootFolder; | |||
use OCP\Files\Search\ISearchComparison; | |||
use OCP\IUser; | |||
use OCP\Share\IManager; | |||
use Sabre\DAV\Tree; | |||
use SearchDAV\XML\BasicSearch; | |||
use SearchDAV\XML\Literal; | |||
use SearchDAV\XML\Operator; | |||
use SearchDAV\XML\Scope; | |||
use Test\TestCase; | |||
class FileSearchBackendTest extends TestCase { | |||
/** @var Tree|\PHPUnit_Framework_MockObject_MockObject */ | |||
private $tree; | |||
/** @var IUser */ | |||
private $user; | |||
/** @var IRootFolder|\PHPUnit_Framework_MockObject_MockObject */ | |||
private $rootFolder; | |||
/** @var IManager|\PHPUnit_Framework_MockObject_MockObject */ | |||
private $shareManager; | |||
/** @var View|\PHPUnit_Framework_MockObject_MockObject */ | |||
private $view; | |||
/** @var Folder|\PHPUnit_Framework_MockObject_MockObject */ | |||
private $searchFolder; | |||
/** @var FileSearchBackend */ | |||
private $search; | |||
/** @var Directory|\PHPUnit_Framework_MockObject_MockObject */ | |||
private $davFolder; | |||
protected function setUp() { | |||
parent::setUp(); | |||
$this->user = $this->createMock(IUser::class); | |||
$this->user->expects($this->any()) | |||
->method('getUID') | |||
->willReturn('test'); | |||
$this->tree = $this->getMockBuilder(Tree::class) | |||
->disableOriginalConstructor() | |||
->getMock(); | |||
$this->view = $this->getMockBuilder(View::class) | |||
->disableOriginalConstructor() | |||
->getMock(); | |||
$this->view->expects($this->any()) | |||
->method('getRelativePath') | |||
->willReturnArgument(0); | |||
$this->rootFolder = $this->createMock(IRootFolder::class); | |||
$this->shareManager = $this->createMock(IManager::class); | |||
$this->searchFolder = $this->createMock(Folder::class); | |||
$fileInfo = $this->createMock(FileInfo::class); | |||
$this->davFolder = $this->createMock(Directory::class); | |||
$this->davFolder->expects($this->any()) | |||
->method('getFileInfo') | |||
->willReturn($fileInfo); | |||
$this->rootFolder->expects($this->any()) | |||
->method('get') | |||
->willReturn($this->searchFolder); | |||
$this->search = new FileSearchBackend($this->tree, $this->user, $this->rootFolder, $this->shareManager, $this->view); | |||
} | |||
public function testSearchFilename() { | |||
$this->tree->expects($this->any()) | |||
->method('getNodeForPath') | |||
->willReturn($this->davFolder); | |||
$this->searchFolder->expects($this->once()) | |||
->method('search') | |||
->with(new SearchQuery( | |||
new SearchComparison( | |||
ISearchComparison::COMPARE_EQUAL, | |||
'name', | |||
'foo' | |||
), | |||
0, | |||
0, | |||
[] | |||
)) | |||
->will($this->returnValue([ | |||
new \OC\Files\Node\Folder($this->rootFolder, $this->view, '/test/path') | |||
])); | |||
$query = $this->getBasicQuery(Operator::OPERATION_EQUAL, '{DAV:}displayname', 'foo'); | |||
$result = $this->search->search($query); | |||
$this->assertCount(1, $result); | |||
$this->assertEquals('/files/test/test/path', $result[0]->href); | |||
} | |||
public function testSearchMimetype() { | |||
$this->tree->expects($this->any()) | |||
->method('getNodeForPath') | |||
->willReturn($this->davFolder); | |||
$this->searchFolder->expects($this->once()) | |||
->method('search') | |||
->with(new SearchQuery( | |||
new SearchComparison( | |||
ISearchComparison::COMPARE_EQUAL, | |||
'mimetype', | |||
'foo' | |||
), | |||
0, | |||
0, | |||
[] | |||
)) | |||
->will($this->returnValue([ | |||
new \OC\Files\Node\Folder($this->rootFolder, $this->view, '/test/path') | |||
])); | |||
$query = $this->getBasicQuery(Operator::OPERATION_EQUAL, '{DAV:}getcontenttype', 'foo'); | |||
$result = $this->search->search($query); | |||
$this->assertCount(1, $result); | |||
$this->assertEquals('/files/test/test/path', $result[0]->href); | |||
} | |||
public function testSearchSize() { | |||
$this->tree->expects($this->any()) | |||
->method('getNodeForPath') | |||
->willReturn($this->davFolder); | |||
$this->searchFolder->expects($this->once()) | |||
->method('search') | |||
->with(new SearchQuery( | |||
new SearchComparison( | |||
ISearchComparison::COMPARE_GREATER_THAN, | |||
'size', | |||
10 | |||
), | |||
0, | |||
0, | |||
[] | |||
)) | |||
->will($this->returnValue([ | |||
new \OC\Files\Node\Folder($this->rootFolder, $this->view, '/test/path') | |||
])); | |||
$query = $this->getBasicQuery(Operator::OPERATION_GREATER_THAN, FilesPlugin::SIZE_PROPERTYNAME, 10); | |||
$result = $this->search->search($query); | |||
$this->assertCount(1, $result); | |||
$this->assertEquals('/files/test/test/path', $result[0]->href); | |||
} | |||
public function testSearchMtime() { | |||
$this->tree->expects($this->any()) | |||
->method('getNodeForPath') | |||
->willReturn($this->davFolder); | |||
$this->searchFolder->expects($this->once()) | |||
->method('search') | |||
->with(new SearchQuery( | |||
new SearchComparison( | |||
ISearchComparison::COMPARE_GREATER_THAN, | |||
'mtime', | |||
10 | |||
), | |||
0, | |||
0, | |||
[] | |||
)) | |||
->will($this->returnValue([ | |||
new \OC\Files\Node\Folder($this->rootFolder, $this->view, '/test/path') | |||
])); | |||
$query = $this->getBasicQuery(Operator::OPERATION_GREATER_THAN, '{DAV:}getlastmodifed', 10); | |||
$result = $this->search->search($query); | |||
$this->assertCount(1, $result); | |||
$this->assertEquals('/files/test/test/path', $result[0]->href); | |||
} | |||
public function testSearchIsCollection() { | |||
$this->tree->expects($this->any()) | |||
->method('getNodeForPath') | |||
->willReturn($this->davFolder); | |||
$this->searchFolder->expects($this->once()) | |||
->method('search') | |||
->with(new SearchQuery( | |||
new SearchComparison( | |||
ISearchComparison::COMPARE_EQUAL, | |||
'mimetype', | |||
FileInfo::MIMETYPE_FOLDER | |||
), | |||
0, | |||
0, | |||
[] | |||
)) | |||
->will($this->returnValue([ | |||
new \OC\Files\Node\Folder($this->rootFolder, $this->view, '/test/path') | |||
])); | |||
$query = $this->getBasicQuery(Operator::OPERATION_IS_COLLECTION, 'yes'); | |||
$result = $this->search->search($query); | |||
$this->assertCount(1, $result); | |||
$this->assertEquals('/files/test/test/path', $result[0]->href); | |||
} | |||
/** | |||
* @expectedException \InvalidArgumentException | |||
*/ | |||
public function testSearchInvalidProp() { | |||
$this->tree->expects($this->any()) | |||
->method('getNodeForPath') | |||
->willReturn($this->davFolder); | |||
$this->searchFolder->expects($this->never()) | |||
->method('search'); | |||
$query = $this->getBasicQuery(Operator::OPERATION_EQUAL, '{DAV:}getetag', 'foo'); | |||
$this->search->search($query); | |||
} | |||
private function getBasicQuery($type, $property, $value = null) { | |||
$query = new BasicSearch(); | |||
$scope = new Scope('/', 'infinite'); | |||
$scope->path = '/'; | |||
$query->from = [$scope]; | |||
$query->orderBy = []; | |||
$query->select = []; | |||
if (is_null($value)) { | |||
$query->where = new Operator( | |||
$type, | |||
[new Literal($property)] | |||
); | |||
} else { | |||
$query->where = new Operator( | |||
$type, | |||
[$property, new Literal($value)] | |||
); | |||
} | |||
return $query; | |||
} | |||
/** | |||
* @expectedException \InvalidArgumentException | |||
*/ | |||
public function testSearchNonFolder() { | |||
$davNode = $this->createMock(File::class); | |||
$this->tree->expects($this->any()) | |||
->method('getNodeForPath') | |||
->willReturn($davNode); | |||
$query = $this->getBasicQuery(Operator::OPERATION_EQUAL, '{DAV:}displayname', 'foo'); | |||
$this->search->search($query); | |||
} | |||
} |
@@ -141,6 +141,11 @@ return array( | |||
'OCP\\Files\\Notify\\IRenameChange' => $baseDir . '/lib/public/Files/Notify/IRenameChange.php', | |||
'OCP\\Files\\ObjectStore\\IObjectStore' => $baseDir . '/lib/public/Files/ObjectStore/IObjectStore.php', | |||
'OCP\\Files\\ReservedWordException' => $baseDir . '/lib/public/Files/ReservedWordException.php', | |||
'OCP\\Files\\Search\\ISearchBinaryOperator' => $baseDir . '/lib/public/Files/Search/ISearchBinaryOperator.php', | |||
'OCP\\Files\\Search\\ISearchComparison' => $baseDir . '/lib/public/Files/Search/ISearchComparison.php', | |||
'OCP\\Files\\Search\\ISearchOperator' => $baseDir . '/lib/public/Files/Search/ISearchOperator.php', | |||
'OCP\\Files\\Search\\ISearchOrder' => $baseDir . '/lib/public/Files/Search/ISearchOrder.php', | |||
'OCP\\Files\\Search\\ISearchQuery' => $baseDir . '/lib/public/Files/Search/ISearchQuery.php', | |||
'OCP\\Files\\SimpleFS\\ISimpleFile' => $baseDir . '/lib/public/Files/SimpleFS/ISimpleFile.php', | |||
'OCP\\Files\\SimpleFS\\ISimpleFolder' => $baseDir . '/lib/public/Files/SimpleFS/ISimpleFolder.php', | |||
'OCP\\Files\\SimpleFS\\ISimpleRoot' => $baseDir . '/lib/public/Files/SimpleFS/ISimpleRoot.php', | |||
@@ -509,6 +514,7 @@ return array( | |||
'OC\\Files\\Cache\\HomePropagator' => $baseDir . '/lib/private/Files/Cache/HomePropagator.php', | |||
'OC\\Files\\Cache\\MoveFromCacheTrait' => $baseDir . '/lib/private/Files/Cache/MoveFromCacheTrait.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\\Scanner' => $baseDir . '/lib/private/Files/Cache/Scanner.php', | |||
'OC\\Files\\Cache\\Storage' => $baseDir . '/lib/private/Files/Cache/Storage.php', | |||
'OC\\Files\\Cache\\StorageGlobal' => $baseDir . '/lib/private/Files/Cache/StorageGlobal.php', | |||
@@ -548,6 +554,10 @@ return array( | |||
'OC\\Files\\ObjectStore\\S3ConnectionTrait' => $baseDir . '/lib/private/Files/ObjectStore/S3ConnectionTrait.php', | |||
'OC\\Files\\ObjectStore\\StorageObjectStore' => $baseDir . '/lib/private/Files/ObjectStore/StorageObjectStore.php', | |||
'OC\\Files\\ObjectStore\\Swift' => $baseDir . '/lib/private/Files/ObjectStore/Swift.php', | |||
'OC\\Files\\Search\\SearchBinaryOperator' => $baseDir . '/lib/private/Files/Search/SearchBinaryOperator.php', | |||
'OC\\Files\\Search\\SearchComparison' => $baseDir . '/lib/private/Files/Search/SearchComparison.php', | |||
'OC\\Files\\Search\\SearchOrder' => $baseDir . '/lib/private/Files/Search/SearchOrder.php', | |||
'OC\\Files\\Search\\SearchQuery' => $baseDir . '/lib/private/Files/Search/SearchQuery.php', | |||
'OC\\Files\\SimpleFS\\SimpleFile' => $baseDir . '/lib/private/Files/SimpleFS/SimpleFile.php', | |||
'OC\\Files\\SimpleFS\\SimpleFolder' => $baseDir . '/lib/private/Files/SimpleFS/SimpleFolder.php', | |||
'OC\\Files\\Storage\\Common' => $baseDir . '/lib/private/Files/Storage/Common.php', |
@@ -171,6 +171,11 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c | |||
'OCP\\Files\\Notify\\IRenameChange' => __DIR__ . '/../../..' . '/lib/public/Files/Notify/IRenameChange.php', | |||
'OCP\\Files\\ObjectStore\\IObjectStore' => __DIR__ . '/../../..' . '/lib/public/Files/ObjectStore/IObjectStore.php', | |||
'OCP\\Files\\ReservedWordException' => __DIR__ . '/../../..' . '/lib/public/Files/ReservedWordException.php', | |||
'OCP\\Files\\Search\\ISearchBinaryOperator' => __DIR__ . '/../../..' . '/lib/public/Files/Search/ISearchBinaryOperator.php', | |||
'OCP\\Files\\Search\\ISearchComparison' => __DIR__ . '/../../..' . '/lib/public/Files/Search/ISearchComparison.php', | |||
'OCP\\Files\\Search\\ISearchOperator' => __DIR__ . '/../../..' . '/lib/public/Files/Search/ISearchOperator.php', | |||
'OCP\\Files\\Search\\ISearchOrder' => __DIR__ . '/../../..' . '/lib/public/Files/Search/ISearchOrder.php', | |||
'OCP\\Files\\Search\\ISearchQuery' => __DIR__ . '/../../..' . '/lib/public/Files/Search/ISearchQuery.php', | |||
'OCP\\Files\\SimpleFS\\ISimpleFile' => __DIR__ . '/../../..' . '/lib/public/Files/SimpleFS/ISimpleFile.php', | |||
'OCP\\Files\\SimpleFS\\ISimpleFolder' => __DIR__ . '/../../..' . '/lib/public/Files/SimpleFS/ISimpleFolder.php', | |||
'OCP\\Files\\SimpleFS\\ISimpleRoot' => __DIR__ . '/../../..' . '/lib/public/Files/SimpleFS/ISimpleRoot.php', | |||
@@ -539,6 +544,7 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c | |||
'OC\\Files\\Cache\\HomePropagator' => __DIR__ . '/../../..' . '/lib/private/Files/Cache/HomePropagator.php', | |||
'OC\\Files\\Cache\\MoveFromCacheTrait' => __DIR__ . '/../../..' . '/lib/private/Files/Cache/MoveFromCacheTrait.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\\Scanner' => __DIR__ . '/../../..' . '/lib/private/Files/Cache/Scanner.php', | |||
'OC\\Files\\Cache\\Storage' => __DIR__ . '/../../..' . '/lib/private/Files/Cache/Storage.php', | |||
'OC\\Files\\Cache\\StorageGlobal' => __DIR__ . '/../../..' . '/lib/private/Files/Cache/StorageGlobal.php', | |||
@@ -578,6 +584,10 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c | |||
'OC\\Files\\ObjectStore\\S3ConnectionTrait' => __DIR__ . '/../../..' . '/lib/private/Files/ObjectStore/S3ConnectionTrait.php', | |||
'OC\\Files\\ObjectStore\\StorageObjectStore' => __DIR__ . '/../../..' . '/lib/private/Files/ObjectStore/StorageObjectStore.php', | |||
'OC\\Files\\ObjectStore\\Swift' => __DIR__ . '/../../..' . '/lib/private/Files/ObjectStore/Swift.php', | |||
'OC\\Files\\Search\\SearchBinaryOperator' => __DIR__ . '/../../..' . '/lib/private/Files/Search/SearchBinaryOperator.php', | |||
'OC\\Files\\Search\\SearchComparison' => __DIR__ . '/../../..' . '/lib/private/Files/Search/SearchComparison.php', | |||
'OC\\Files\\Search\\SearchOrder' => __DIR__ . '/../../..' . '/lib/private/Files/Search/SearchOrder.php', | |||
'OC\\Files\\Search\\SearchQuery' => __DIR__ . '/../../..' . '/lib/private/Files/Search/SearchQuery.php', | |||
'OC\\Files\\SimpleFS\\SimpleFile' => __DIR__ . '/../../..' . '/lib/private/Files/SimpleFS/SimpleFile.php', | |||
'OC\\Files\\SimpleFS\\SimpleFolder' => __DIR__ . '/../../..' . '/lib/private/Files/SimpleFS/SimpleFolder.php', | |||
'OC\\Files\\Storage\\Common' => __DIR__ . '/../../..' . '/lib/private/Files/Storage/Common.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. |
@@ -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 []; | |||
} |
@@ -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); | |||
} | |||
} |
@@ -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 | |||
* |
@@ -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 | |||
* |
@@ -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)); | |||
} | |||
} | |||
/** |
@@ -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; | |||
} | |||
} |
@@ -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; | |||
} | |||
} |
@@ -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; | |||
} | |||
} |
@@ -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; | |||
} | |||
} |
@@ -24,6 +24,7 @@ use OCP\Constants; | |||
use OCP\Files\Cache\ICache; | |||
use OCP\Files\Cache\ICacheEntry; | |||
use OCP\Files\FileInfo; | |||
use OCP\Files\Search\ISearchQuery; | |||
class NullCache implements ICache { | |||
public function getNumericStorageId() { | |||
@@ -103,6 +104,10 @@ class NullCache implements ICache { | |||
return []; | |||
} | |||
public function searchQuery(ISearchQuery $query) { | |||
return []; | |||
} | |||
public function searchByTag($tag, $userId) { | |||
return []; | |||
} |
@@ -21,6 +21,8 @@ | |||
*/ | |||
namespace OCP\Files\Cache; | |||
use OCP\Files\Search\ISearchOperator; | |||
use OCP\Files\Search\ISearchQuery; | |||
/** | |||
* Metadata cache for a storage | |||
@@ -212,6 +214,16 @@ interface ICache { | |||
*/ | |||
public function searchByMime($mimetype); | |||
/** | |||
* Search for files with a flexible query | |||
* | |||
* @param ISearchQuery $query | |||
* @return ICacheEntry[] | |||
* @throw \InvalidArgumentException if the cache is unable to perform the query | |||
* @since 12.0.0 | |||
*/ | |||
public function searchQuery(ISearchQuery $query); | |||
/** | |||
* Search for files by tag of a given users. | |||
* |
@@ -30,6 +30,7 @@ | |||
// use OCP namespace for all classes that are considered public. | |||
// This means that they should be used by apps instead of the internal ownCloud classes | |||
namespace OCP\Files; | |||
use OCP\Files\Search\ISearchQuery; | |||
/** | |||
* @since 6.0.0 | |||
@@ -115,7 +116,7 @@ interface Folder extends Node { | |||
/** | |||
* search for files with the name matching $query | |||
* | |||
* @param string $query | |||
* @param string|ISearchQuery $query | |||
* @return \OCP\Files\Node[] | |||
* @since 6.0.0 | |||
*/ |
@@ -0,0 +1,51 @@ | |||
<?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 OCP\Files\Search; | |||
/** | |||
* @since 12.0.0 | |||
*/ | |||
interface ISearchBinaryOperator extends ISearchOperator { | |||
const OPERATOR_AND = 'and'; | |||
const OPERATOR_OR = 'or'; | |||
const OPERATOR_NOT = 'not'; | |||
/** | |||
* The type of binary operator | |||
* | |||
* One of the ISearchBinaryOperator::OPERATOR_* constants | |||
* | |||
* @return string | |||
* @since 12.0.0 | |||
*/ | |||
public function getType(); | |||
/** | |||
* The arguments for the binary operator | |||
* | |||
* One argument for the 'not' operator and two for 'and' and 'or' | |||
* | |||
* @return ISearchOperator[] | |||
* @since 12.0.0 | |||
*/ | |||
public function getArguments(); | |||
} |
@@ -0,0 +1,60 @@ | |||
<?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 OCP\Files\Search; | |||
/** | |||
* @since 12.0.0 | |||
*/ | |||
interface ISearchComparison extends ISearchOperator { | |||
const COMPARE_EQUAL = 'eq'; | |||
const COMPARE_GREATER_THAN = 'gt'; | |||
const COMPARE_GREATER_THAN_EQUAL = 'gte'; | |||
const COMPARE_LESS_THAN = 'lt'; | |||
const COMPARE_LESS_THAN_EQUAL = 'lte'; | |||
const COMPARE_LIKE = 'like'; | |||
/** | |||
* Get the type of comparison, one of the ISearchComparison::COMPARE_* constants | |||
* | |||
* @return string | |||
* @since 12.0.0 | |||
*/ | |||
public function getType(); | |||
/** | |||
* Get the name of the field to compare with | |||
* | |||
* i.e. 'size', 'name' or 'mimetype' | |||
* | |||
* @return string | |||
* @since 12.0.0 | |||
*/ | |||
public function getField(); | |||
/** | |||
* Get the value to compare the field with | |||
* | |||
* @return string|integer|\DateTime | |||
* @since 12.0.0 | |||
*/ | |||
public function getValue(); | |||
} |
@@ -0,0 +1,29 @@ | |||
<?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 OCP\Files\Search; | |||
/** | |||
* @since 12.0.0 | |||
*/ | |||
interface ISearchOperator { | |||
} |
@@ -0,0 +1,46 @@ | |||
<?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 OCP\Files\Search; | |||
/** | |||
* @since 12.0.0 | |||
*/ | |||
interface ISearchOrder { | |||
const DIRECTION_ASCENDING = 'asc'; | |||
const DIRECTION_DESCENDING = 'desc'; | |||
/** | |||
* The direction to sort in, either ISearchOrder::DIRECTION_ASCENDING or ISearchOrder::DIRECTION_DESCENDING | |||
* | |||
* @return string | |||
* @since 12.0.0 | |||
*/ | |||
public function getDirection(); | |||
/** | |||
* The field to sort on | |||
* | |||
* @return string | |||
* @since 12.0.0 | |||
*/ | |||
public function getField(); | |||
} |
@@ -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 OCP\Files\Search; | |||
/** | |||
* @since 12.0.0 | |||
*/ | |||
interface ISearchQuery { | |||
/** | |||
* @return ISearchOperator | |||
* @since 12.0.0 | |||
*/ | |||
public function getSearchOperation(); | |||
/** | |||
* Get the maximum number of results to return | |||
* | |||
* @return integer | |||
* @since 12.0.0 | |||
*/ | |||
public function getLimit(); | |||
/** | |||
* Get the offset for returned results | |||
* | |||
* @return integer | |||
* @since 12.0.0 | |||
*/ | |||
public function getOffset(); | |||
/** | |||
* The fields and directions to order by | |||
* | |||
* @return ISearchOrder[] | |||
* @since 12.0.0 | |||
*/ | |||
public function getOrder(); | |||
} |
@@ -11,6 +11,9 @@ namespace Test\Files\Cache; | |||
use Doctrine\DBAL\Platforms\MySqlPlatform; | |||
use OC\Files\Cache\Cache; | |||
use OC\Files\Search\SearchComparison; | |||
use OC\Files\Search\SearchQuery; | |||
use OCP\Files\Search\ISearchComparison; | |||
class LongId extends \OC\Files\Storage\Temporary { | |||
public function getId() { | |||
@@ -111,15 +114,15 @@ class CacheTest extends \Test\TestCase { | |||
* @dataProvider folderDataProvider | |||
*/ | |||
public function testFolder($folder) { | |||
if(strpos($folder, 'F09F9890')) { | |||
if (strpos($folder, 'F09F9890')) { | |||
// 4 byte UTF doesn't work on mysql | |||
$params = \OC::$server->getDatabaseConnection()->getParams(); | |||
if(\OC::$server->getDatabaseConnection()->getDatabasePlatform() instanceof MySqlPlatform && $params['charset'] !== 'utf8mb4') { | |||
if (\OC::$server->getDatabaseConnection()->getDatabasePlatform() instanceof MySqlPlatform && $params['charset'] !== 'utf8mb4') { | |||
$this->markTestSkipped('MySQL doesn\'t support 4 byte UTF-8'); | |||
} | |||
} | |||
$file2 = $folder.'/bar'; | |||
$file3 = $folder.'/foo'; | |||
$file2 = $folder . '/bar'; | |||
$file3 = $folder . '/foo'; | |||
$data1 = array('size' => 100, 'mtime' => 50, 'mimetype' => 'httpd/unix-directory'); | |||
$fileData = array(); | |||
$fileData['bar'] = array('size' => 1000, 'mtime' => 20, 'mimetype' => 'foo/file'); | |||
@@ -138,7 +141,7 @@ class CacheTest extends \Test\TestCase { | |||
} | |||
} | |||
$file4 = $folder.'/unkownSize'; | |||
$file4 = $folder . '/unkownSize'; | |||
$fileData['unkownSize'] = array('size' => -1, 'mtime' => 25, 'mimetype' => 'foo/file'); | |||
$this->cache->put($file4, $fileData['unkownSize']); | |||
@@ -155,8 +158,8 @@ class CacheTest extends \Test\TestCase { | |||
$this->assertEquals(0, $this->cache->calculateFolderSize($folder)); | |||
$this->cache->remove($folder); | |||
$this->assertFalse($this->cache->inCache($folder.'/foo')); | |||
$this->assertFalse($this->cache->inCache($folder.'/bar')); | |||
$this->assertFalse($this->cache->inCache($folder . '/foo')); | |||
$this->assertFalse($this->cache->inCache($folder . '/bar')); | |||
} | |||
public function testRemoveRecursive() { | |||
@@ -165,7 +168,7 @@ class CacheTest extends \Test\TestCase { | |||
$folders = ['folder', 'folder/subfolder', 'folder/sub2', 'folder/sub2/sub3']; | |||
$files = ['folder/foo.txt', 'folder/bar.txt', 'folder/subfolder/asd.txt', 'folder/sub2/qwerty.txt', 'folder/sub2/sub3/foo.txt']; | |||
foreach($folders as $folder){ | |||
foreach ($folders as $folder) { | |||
$this->cache->put($folder, $folderData); | |||
} | |||
foreach ($files as $file) { | |||
@@ -360,7 +363,9 @@ class CacheTest extends \Test\TestCase { | |||
$this->assertEquals(2, count($results)); | |||
usort($results, function($value1, $value2) { return $value1['name'] >= $value2['name']; }); | |||
usort($results, function ($value1, $value2) { | |||
return $value1['name'] >= $value2['name']; | |||
}); | |||
$this->assertEquals('folder', $results[0]['name']); | |||
$this->assertEquals('foo', $results[1]['name']); | |||
@@ -368,11 +373,15 @@ class CacheTest extends \Test\TestCase { | |||
// use tag id | |||
$tags = $tagManager->getTagsForUser($userId); | |||
$this->assertNotEmpty($tags); | |||
$tags = array_filter($tags, function($tag) { return $tag->getName() === 'tag2'; }); | |||
$tags = array_filter($tags, function ($tag) { | |||
return $tag->getName() === 'tag2'; | |||
}); | |||
$results = $this->cache->searchByTag(current($tags)->getId(), $userId); | |||
$this->assertEquals(3, count($results)); | |||
usort($results, function($value1, $value2) { return $value1['name'] >= $value2['name']; }); | |||
usort($results, function ($value1, $value2) { | |||
return $value1['name'] >= $value2['name']; | |||
}); | |||
$this->assertEquals('folder', $results[0]['name']); | |||
$this->assertEquals('foo2', $results[1]['name']); | |||
@@ -383,7 +392,42 @@ class CacheTest extends \Test\TestCase { | |||
$this->logout(); | |||
$user = \OC::$server->getUserManager()->get($userId); | |||
if ($user !== null) { $user->delete(); } | |||
if ($user !== null) { | |||
$user->delete(); | |||
} | |||
} | |||
function testSearchByQuery() { | |||
$file1 = 'folder'; | |||
$file2 = 'folder/foobar'; | |||
$file3 = 'folder/foo'; | |||
$data1 = array('size' => 100, 'mtime' => 50, 'mimetype' => 'foo/folder'); | |||
$fileData = array(); | |||
$fileData['foobar'] = array('size' => 1000, 'mtime' => 20, 'mimetype' => 'foo/file'); | |||
$fileData['foo'] = array('size' => 20, 'mtime' => 25, 'mimetype' => 'foo/file'); | |||
$this->cache->put($file1, $data1); | |||
$this->cache->put($file2, $fileData['foobar']); | |||
$this->cache->put($file3, $fileData['foo']); | |||
$this->assertCount(1, $this->cache->searchQuery(new SearchQuery( | |||
new SearchComparison(ISearchComparison::COMPARE_EQUAL, 'name', 'foo') | |||
, 10, 0, []))); | |||
$this->assertCount(2, $this->cache->searchQuery(new SearchQuery( | |||
new SearchComparison(ISearchComparison::COMPARE_LIKE, 'name', 'foo%') | |||
, 10, 0, []))); | |||
$this->assertCount(2, $this->cache->searchQuery(new SearchQuery( | |||
new SearchComparison(ISearchComparison::COMPARE_EQUAL, 'mimetype', 'foo/file') | |||
, 10, 0, []))); | |||
$this->assertCount(3, $this->cache->searchQuery(new SearchQuery( | |||
new SearchComparison(ISearchComparison::COMPARE_LIKE, 'mimetype', 'foo/%') | |||
, 10, 0, []))); | |||
$this->assertCount(1, $this->cache->searchQuery(new SearchQuery( | |||
new SearchComparison(ISearchComparison::COMPARE_GREATER_THAN, 'size', 100) | |||
, 10, 0, []))); | |||
$this->assertCount(2, $this->cache->searchQuery(new SearchQuery( | |||
new SearchComparison(ISearchComparison::COMPARE_GREATER_THAN_EQUAL, 'size', 100) | |||
, 10, 0, []))); | |||
} | |||
function testMove() { | |||
@@ -626,9 +670,9 @@ class CacheTest extends \Test\TestCase { | |||
public function escapingProvider() { | |||
return [ | |||
['foo'], | |||
['o%'], | |||
['oth_r'], | |||
['foo'], | |||
['o%'], | |||
['oth_r'], | |||
]; | |||
} | |||
@@ -0,0 +1,204 @@ | |||
<?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 Test\Files\Cache; | |||
use OC\DB\QueryBuilder\Literal; | |||
use OC\Files\Cache\QuerySearchHelper; | |||
use OC\Files\Search\SearchBinaryOperator; | |||
use OC\Files\Search\SearchComparison; | |||
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 Test\TestCase; | |||
/** | |||
* @group DB | |||
*/ | |||
class QuerySearchHelperTest extends TestCase { | |||
/** @var IQueryBuilder */ | |||
private $builder; | |||
/** @var IMimeTypeLoader|\PHPUnit_Framework_MockObject_MockObject */ | |||
private $mimetypeLoader; | |||
/** @var QuerySearchHelper */ | |||
private $querySearchHelper; | |||
/** @var integer */ | |||
private $numericStorageId; | |||
public function setUp() { | |||
parent::setUp(); | |||
$this->builder = \OC::$server->getDatabaseConnection()->getQueryBuilder(); | |||
$this->mimetypeLoader = $this->createMock(IMimeTypeLoader::class); | |||
$this->mimetypeLoader->expects($this->any()) | |||
->method('getId') | |||
->willReturnMap([ | |||
['text', 1], | |||
['text/plain', 2], | |||
['text/xml', 3], | |||
['image/jpg', 4], | |||
['image/png', 5], | |||
['image', 6], | |||
]); | |||
$this->mimetypeLoader->expects($this->any()) | |||
->method('getMimetypeById') | |||
->willReturnMap([ | |||
[1, 'text'], | |||
[2, 'text/plain'], | |||
[3, 'text/xml'], | |||
[4, 'image/jpg'], | |||
[5, 'image/png'], | |||
[6, 'image'] | |||
]); | |||
$this->querySearchHelper = new QuerySearchHelper($this->mimetypeLoader); | |||
$this->numericStorageId = 10000; | |||
$this->builder->select(['fileid']) | |||
->from('filecache') | |||
->where($this->builder->expr()->eq('storage', new Literal($this->numericStorageId))); | |||
} | |||
public function tearDown() { | |||
parent::tearDown(); | |||
$builder = \OC::$server->getDatabaseConnection()->getQueryBuilder(); | |||
$builder->delete('filecache') | |||
->where($builder->expr()->eq('storage', $builder->createNamedParameter($this->numericStorageId, IQueryBuilder::PARAM_INT))); | |||
$builder->execute(); | |||
} | |||
private function addCacheEntry(array $data) { | |||
$data['storage'] = $this->numericStorageId; | |||
$data['etag'] = 'unimportant'; | |||
$data['storage_mtime'] = $data['mtime']; | |||
if (!isset($data['path'])) { | |||
$data['path'] = 'random/' . $this->getUniqueID(); | |||
} | |||
$data['path_hash'] = md5($data['path']); | |||
if (!isset($data['mtime'])) { | |||
$data['mtime'] = 100; | |||
} | |||
if (!isset($data['size'])) { | |||
$data['size'] = 100; | |||
} | |||
$data['name'] = basename($data['path']); | |||
$data['parent'] = -1; | |||
if (isset($data['mimetype'])) { | |||
list($mimepart,) = explode('/', $data['mimetype']); | |||
$data['mimepart'] = $this->mimetypeLoader->getId($mimepart); | |||
$data['mimetype'] = $this->mimetypeLoader->getId($data['mimetype']); | |||
} else { | |||
$data['mimepart'] = 1; | |||
$data['mimetype'] = 1; | |||
} | |||
$builder = \OC::$server->getDatabaseConnection()->getQueryBuilder(); | |||
$values = []; | |||
foreach ($data as $key => $value) { | |||
$values[$key] = $builder->createNamedParameter($value); | |||
} | |||
$builder->insert('filecache') | |||
->values($values) | |||
->execute(); | |||
} | |||
private function search(ISearchOperator $operator) { | |||
$dbOperator = $this->querySearchHelper->searchOperatorToDBExpr($this->builder, $operator); | |||
$this->builder->andWhere($dbOperator); | |||
return $this->builder->execute()->fetchAll(\PDO::FETCH_COLUMN); | |||
} | |||
public function comparisonProvider() { | |||
return [ | |||
[new SearchComparison(ISearchComparison::COMPARE_GREATER_THAN, 'mtime', 125), [1002]], | |||
[new SearchComparison(ISearchComparison::COMPARE_LESS_THAN, 'mtime', 125), [1001]], | |||
[new SearchComparison(ISearchComparison::COMPARE_EQUAL, 'size', 125), []], | |||
[new SearchComparison(ISearchComparison::COMPARE_EQUAL, 'size', 50), [1001, 1002]], | |||
[new SearchComparison(ISearchComparison::COMPARE_EQUAL, 'name', 'foobar'), [1001]], | |||
[new SearchComparison(ISearchComparison::COMPARE_LIKE, 'name', 'foo%'), [1001, 1002]], | |||
[new SearchComparison(ISearchComparison::COMPARE_EQUAL, 'mimetype', 'image/jpg'), [1001]], | |||
[new SearchComparison(ISearchComparison::COMPARE_LIKE, 'mimetype', 'image/%'), [1001, 1002]], | |||
[new SearchBinaryOperator(ISearchBinaryOperator::OPERATOR_AND, [ | |||
new SearchComparison(ISearchComparison::COMPARE_EQUAL, 'size', 50), | |||
new SearchComparison(ISearchComparison::COMPARE_LESS_THAN, 'mtime', 125), [1001] | |||
]), [1001]], | |||
[new SearchBinaryOperator(ISearchBinaryOperator::OPERATOR_OR, [ | |||
new SearchComparison(ISearchComparison::COMPARE_EQUAL, 'mtime', 100), | |||
new SearchComparison(ISearchComparison::COMPARE_EQUAL, 'mtime', 150), | |||
]), [1001, 1002]], | |||
[new SearchBinaryOperator(ISearchBinaryOperator::OPERATOR_NOT, [ | |||
new SearchComparison(ISearchComparison::COMPARE_EQUAL, 'mtime', 150), | |||
]), [1001]], | |||
[new SearchBinaryOperator(ISearchBinaryOperator::OPERATOR_NOT, [ | |||
new SearchComparison(ISearchComparison::COMPARE_GREATER_THAN, 'mtime', 125), | |||
]), [1001]], | |||
[new SearchBinaryOperator(ISearchBinaryOperator::OPERATOR_NOT, [ | |||
new SearchComparison(ISearchComparison::COMPARE_LESS_THAN, 'mtime', 125), | |||
]), [1002]], | |||
[new SearchBinaryOperator(ISearchBinaryOperator::OPERATOR_NOT, [ | |||
new SearchComparison(ISearchComparison::COMPARE_LIKE, 'name', '%bar'), | |||
]), [1002]], | |||
]; | |||
} | |||
/** | |||
* @dataProvider comparisonProvider | |||
* | |||
* @param ISearchOperator $operator | |||
* @param array $fileIds | |||
*/ | |||
public function testComparison(ISearchOperator $operator, array $fileIds) { | |||
$this->addCacheEntry([ | |||
'path' => 'foobar', | |||
'fileid' => 1001, | |||
'mtime' => 100, | |||
'size' => 50, | |||
'mimetype' => 'image/jpg' | |||
]); | |||
$this->addCacheEntry([ | |||
'path' => 'fooasd', | |||
'fileid' => 1002, | |||
'mtime' => 150, | |||
'size' => 50, | |||
'mimetype' => 'image/png' | |||
]); | |||
$results = $this->search($operator); | |||
sort($fileIds); | |||
sort($results); | |||
$this->assertEquals($fileIds, $results); | |||
} | |||
} |