diff --git a/apps/files/appinfo/routes.php b/apps/files/appinfo/routes.php index cea960b1a499..b80a6ddab685 100644 --- a/apps/files/appinfo/routes.php +++ b/apps/files/appinfo/routes.php @@ -59,6 +59,11 @@ 'url' => '/api/v1/showhidden', 'verb' => 'POST' ), + [ + 'name' => 'Search#search', + 'url' => '/search', + 'verb' => 'GET', + ], [ 'name' => 'view#index', 'url' => '/', diff --git a/apps/files/lib/AppInfo/Application.php b/apps/files/lib/AppInfo/Application.php index 4ebf67a2374c..e35568e8cd9a 100644 --- a/apps/files/lib/AppInfo/Application.php +++ b/apps/files/lib/AppInfo/Application.php @@ -24,6 +24,7 @@ namespace OCA\Files\AppInfo; use OCA\Files\Controller\ApiController; +use OCA\Files\Controller\SearchController; use OCP\AppFramework\App; use \OCA\Files\Service\TagService; use \OCP\IContainer; @@ -50,6 +51,16 @@ public function __construct(array $urlParams=array()) { ); }); + $container->registerService('SearchController', function (IContainer $c) use ($server) { + return new SearchController( + $c->query('AppName'), + $c->query('Request'), + $server->getUserSession(), + $server->getConfig(), + $server->getRootFolder() + ); + }); + $container->registerService('ViewController', function (IContainer $c) use ($server) { return new ViewController( $c->query('AppName'), diff --git a/apps/files/lib/Controller/SearchController.php b/apps/files/lib/Controller/SearchController.php new file mode 100644 index 000000000000..456bee105731 --- /dev/null +++ b/apps/files/lib/Controller/SearchController.php @@ -0,0 +1,152 @@ + + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * 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, version 3, + * along with this program. If not, see + * + */ + +namespace OCA\Files\Controller; + +use OCP\AppFramework\Http; +use OCP\AppFramework\Controller; +use OCP\Files\NotFoundException; +use OCP\Files\Search\IIndexer; +use OCP\IConfig; +use OCP\IRequest; +use OCP\AppFramework\Http\DataResponse; +use OCP\AppFramework\Http\DataDisplayResponse; +use OCP\AppFramework\Http\Response; +use OCP\AppFramework\Http\NotFoundResponse; +use OCP\Files\Folder; +use OCP\IUserSession; +use \OCP\Files\IRootFolder; +use OCP\Search\ScoredResult; + + +/** + * Class SearchController + * + * @package OCA\Files\Controller + */ +class SearchController extends Controller { + + /** IUserSession */ + private $userSession; + /** string */ + private $userId; + /** IConfig */ + private $config; + /** IRootFolder */ + protected $rootFolder; + + /** + * SearchController constructor. + * + * @param string $appName + * @param IRequest $request + * @param IUserSession $userSession + * @param IConfig $config + * @param IRootFolder $rootFolder + */ + public function __construct($appName, + IRequest $request, + IUserSession $userSession, + IConfig $config, + IRootFolder $rootFolder) { + parent::__construct($appName, $request); + $this->userSession = $userSession; + $this->config = $config; + $this->rootFolder = $rootFolder; + + $user = $this->userSession->getUser(); + if (!$user) { + // TODO - throw some exception + } + + $this->userId = $user->getUID(); + } + + /** + * @NoCSRFRequired + * @NoAdminRequired + * + * @param string $phrase phrase to search + * @param string $path limit path to search + * @param integer $page page of pagination + * @param integer $size size of pagination + */ + public function search($phrase, $path='/', $page=null, $size=null) { + if ($path === '') { + $path = '/'; + } + $path = '/' . $this->userId . '/files' . $path; + + $indexStorageMap = []; + + // Collect mounts to search + try { + $mounts = $this->rootFolder->getMountsIn($path); + foreach($mounts as $mount) { + /** @var \OCP\Files\Mount\IMountPoint $mount */ + /** @var \OCP\Files\Storage $storage */ + $storage = $mount->getStorage(); + $indexerIdentifier = $storage->getIndexerIdentifier(); + + if (!isset($indexStorageMap[$indexerIdentifier])) { + $indexStorageMap[$indexerIdentifier] = []; + } + $indexStorageMap[$indexerIdentifier][] = $storage; + } + } catch(NotFoundException $ex) { + + } + + $indexers = []; + foreach($indexStorageMap as $singleIndexer => $storages) { + if (!class_exists($singleIndexer)) { + continue; + } + + try { + /** @var IIndexer $instance */ + $instance = new $singleIndexer(); + $instance->setStorages($storages); + $indexers[] = $instance; + } catch(\Exception $ex) { + //TODO + } + } + + // Search Indexers from 0 to $page + $size + // Some better approximation of what $page should look like + // should be added in a later version + $indexerSize = $page + $size; + + $results = []; + foreach($indexers as $indexer) { + /** @var IIndexer $indexer */ + $results = array_merge($results, $indexer->search($phrase, $path, 0, $indexerSize)); + } + + usort($results, function(ScoredResult $a, ScoredResult $b) { + return ($a->score < $b->score) ? -1 : 1; + }); + $results = array_slice($results, $page, $size); + + } + +} diff --git a/lib/private/Files/Search/DatabaseIndixer.php b/lib/private/Files/Search/DatabaseIndixer.php new file mode 100644 index 000000000000..37467216bcc6 --- /dev/null +++ b/lib/private/Files/Search/DatabaseIndixer.php @@ -0,0 +1,50 @@ + + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * 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, version 3, + * along with this program. If not, see + * + */ + +namespace OCA\Files\Search; + +use OCP\Files\Search\IIndexer; + +class DatabaseIndexer implements IIndexer { + + /** + * @var \OCP\Files\Storage\IStorage[] + */ + protected $storages; + + /** + * @param \OCP\Files\Storage\IStorage[] $storages + */ + public function setStorages($storages) { + $this->storages = $storages; + } + + /** + * @param $query + * @param null|integer $page + * @param null|integer $size + * @return \OCP\Search\ScoredResult[] + */ + public function search($query, $page=null, $size=null) { + return []; + } + +} diff --git a/lib/private/Files/Storage/Common.php b/lib/private/Files/Storage/Common.php index cec6a42a2c06..1c0f9c9c118c 100644 --- a/lib/private/Files/Storage/Common.php +++ b/lib/private/Files/Storage/Common.php @@ -72,6 +72,7 @@ abstract class Common implements Storage, ILockingStorage { protected $propagator; protected $storageCache; protected $updater; + protected $indexerIdentifier; protected $mountOptions = []; protected $owner = null; @@ -343,6 +344,12 @@ public function getWatcher($path = '', $storage = null) { return $this->watcher; } + public function getIndexerIdentifier() { + return $this->indexerIdentifier ? + $this->indexerIdentifier : + '\OCA\Files\Search\DatabaseIndexer'; + } + /** * get a propagator instance for the cache * diff --git a/lib/private/Files/Storage/Wrapper/Wrapper.php b/lib/private/Files/Storage/Wrapper/Wrapper.php index 21d7db1099be..fbaf0f93ed9f 100644 --- a/lib/private/Files/Storage/Wrapper/Wrapper.php +++ b/lib/private/Files/Storage/Wrapper/Wrapper.php @@ -440,6 +440,13 @@ public function getUpdater($storage = null) { return $this->storage->getUpdater($storage); } + /** + * @return string + */ + public function getIndexerIdentifier() { + return $this->storage->getIndexerIdentifier(); + } + /** * @return \OC\Files\Cache\Storage */ diff --git a/lib/public/Files/Storage/IStorage.php b/lib/public/Files/Storage/IStorage.php index ab1915bb93ec..7ae1f7cc90fc 100644 --- a/lib/public/Files/Storage/IStorage.php +++ b/lib/public/Files/Storage/IStorage.php @@ -454,4 +454,10 @@ public function getUpdater(); * @since 9.0.0 */ public function getWatcher(); + + /** + * @return string + * @since 9.1.0 + */ + public function getIndexerIdentifier(); } diff --git a/lib/public/files/search/IIndexer.php b/lib/public/files/search/IIndexer.php new file mode 100644 index 000000000000..a4a5d2c4d664 --- /dev/null +++ b/lib/public/files/search/IIndexer.php @@ -0,0 +1,48 @@ + + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * 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, version 3, + * along with this program. If not, see + * + */ + +namespace OCP\Files\Search; + +/** + * indexes storage to provide performant search + * + * @since 9.1.0 + */ +interface IIndexer { + + /** + * list of storages indexer is supposed to query + * @param \OCP\Files\Storage\IStorage[] $storages + */ + public function setStorages($storages); + + /** + * search storages for query + * @param string $query + * @param string $path limit search to a certain subdirectory + * @param null|integer $page + * @param null|integer $size + * @return \OCP\Search\ScoredResult[] + * @since 9.1.0 + */ + public function search($query, $path, $page=null, $size=null); + +} diff --git a/lib/public/search/ScoredResult.php b/lib/public/search/ScoredResult.php new file mode 100644 index 000000000000..ea4c2210dc2e --- /dev/null +++ b/lib/public/search/ScoredResult.php @@ -0,0 +1,58 @@ + + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * 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, version 3, + * along with this program. If not, see + * + */ + +namespace OCP\Search; + +/** + * The generic result of a search + * @since 9.1.0 + */ +class ScoredResult extends Result { + + /** + * A score for the result, ranks between 0 and 1 + * @var float + * @since 9.1.0 + */ + public $score; + + /** + * Highlight to what part of the result matches the search + * @var string + * @since 9.1.0 + */ + public $highlight; + + /** + * Create a new scored search result + * @param string $id unique identifier from application: '[app_name]/[item_identifier_in_app]' + * @param string $name displayed text of result + * @param string $link URL to the result within its app + * @param float $score score the result + * @param string $highlight for little preview of result + * @since 9.1.0 + */ + public function __construct($id = null, $name = null, $link = null, $score = null, $highlight = null) { + parent::__construct($id, $name, $link); + $this->score = $score; + $this->highlight = $highlight; + } +}