diff options
20 files changed, 1037 insertions, 33 deletions
diff --git a/apps/files_external/appinfo/application.php b/apps/files_external/appinfo/application.php index 0c8b90935d3..c755b6a29b0 100644 --- a/apps/files_external/appinfo/application.php +++ b/apps/files_external/appinfo/application.php @@ -26,6 +26,7 @@ namespace OCA\Files_External\AppInfo; use \OCP\AppFramework\App; +use OCP\AppFramework\IAppContainer; use \OCP\IContainer; use \OCA\Files_External\Service\BackendService; @@ -33,9 +34,13 @@ use \OCA\Files_External\Service\BackendService; * @package OCA\Files_External\Appinfo */ class Application extends App { - public function __construct(array $urlParams=array()) { + public function __construct(array $urlParams = array()) { parent::__construct('files_external', $urlParams); + $this->getContainer()->registerService('OCP\Files\Config\IUserMountCache', function (IAppContainer $c) { + return $c->getServer()->query('UserMountCache'); + }); + $this->loadBackends(); $this->loadAuthMechanisms(); diff --git a/apps/files_external/migration/storagemigrator.php b/apps/files_external/migration/storagemigrator.php index 2da47decf9f..ba81810a4fd 100644 --- a/apps/files_external/migration/storagemigrator.php +++ b/apps/files_external/migration/storagemigrator.php @@ -29,6 +29,7 @@ use OCA\Files_external\Service\LegacyStoragesService; use OCA\Files_external\Service\StoragesService; use OCA\Files_external\Service\UserLegacyStoragesService; use OCA\Files_external\Service\UserStoragesService; +use OCP\Files\Config\IUserMountCache; use OCP\IConfig; use OCP\IDBConnection; use OCP\ILogger; @@ -64,6 +65,9 @@ class StorageMigrator { */ private $logger; + /** @var IUserMountCache */ + private $userMountCache; + /** * StorageMigrator constructor. * @@ -72,19 +76,22 @@ class StorageMigrator { * @param IConfig $config * @param IDBConnection $connection * @param ILogger $logger + * @param IUserMountCache $userMountCache */ public function __construct( BackendService $backendService, DBConfigService $dbConfig, IConfig $config, IDBConnection $connection, - ILogger $logger + ILogger $logger, + IUserMountCache $userMountCache ) { $this->backendService = $backendService; $this->dbConfig = $dbConfig; $this->config = $config; $this->connection = $connection; $this->logger = $logger; + $this->userMountCache = $userMountCache; } private function migrate(LegacyStoragesService $legacyService, StoragesService $storageService) { @@ -107,7 +114,7 @@ class StorageMigrator { */ public function migrateGlobal() { $legacyService = new GlobalLegacyStoragesService($this->backendService); - $storageService = new GlobalStoragesService($this->backendService, $this->dbConfig); + $storageService = new GlobalStoragesService($this->backendService, $this->dbConfig, $this->userMountCache); $this->migrate($legacyService, $storageService); } @@ -125,7 +132,7 @@ class StorageMigrator { if (version_compare($userVersion, '0.5.0', '<')) { $this->config->setUserValue($userId, 'files_external', 'config_version', '0.5.0'); $legacyService = new UserLegacyStoragesService($this->backendService, $dummySession); - $storageService = new UserStoragesService($this->backendService, $this->dbConfig, $dummySession); + $storageService = new UserStoragesService($this->backendService, $this->dbConfig, $dummySession, $this->userMountCache); $this->migrate($legacyService, $storageService); } diff --git a/apps/files_external/service/storagesservice.php b/apps/files_external/service/storagesservice.php index c0dd263ed66..678b91c0109 100644 --- a/apps/files_external/service/storagesservice.php +++ b/apps/files_external/service/storagesservice.php @@ -31,6 +31,7 @@ use \OCA\Files_external\Lib\StorageConfig; use \OCA\Files_external\NotFoundException; use \OCA\Files_External\Lib\Backend\Backend; use \OCA\Files_External\Lib\Auth\AuthMechanism; +use OCP\Files\Config\IUserMountCache; use \OCP\Files\StorageNotAvailableException; /** @@ -47,12 +48,19 @@ abstract class StoragesService { protected $dbConfig; /** + * @var IUserMountCache + */ + protected $userMountCache; + + /** * @param BackendService $backendService * @param DBConfigService $dbConfigService + * @param IUserMountCache $userMountCache */ - public function __construct(BackendService $backendService, DBConfigService $dbConfigService) { + public function __construct(BackendService $backendService, DBConfigService $dbConfigService, IUserMountCache $userMountCache) { $this->backendService = $backendService; $this->dbConfig = $dbConfigService; + $this->userMountCache = $userMountCache; } protected function readDBConfig() { @@ -416,6 +424,15 @@ abstract class StoragesService { $this->triggerChangeHooks($oldStorage, $updatedStorage); + if (($wasGlobal && !$isGlobal) || count($removedGroups) > 0) { // to expensive to properly handle these on the fly + $this->userMountCache->remoteStorageMounts($this->getStorageId($updatedStorage)); + } else { + $storageId = $this->getStorageId($updatedStorage); + foreach ($removedUsers as $userId) { + $this->userMountCache->removeUserStorageMount($storageId, $userId); + } + } + return $this->getStorage($id); } @@ -480,4 +497,25 @@ abstract class StoragesService { return $storageImpl->getId(); } + /** + * Construct the storage implementation + * + * @param StorageConfig $storageConfig + * @return int + */ + private function getStorageId(StorageConfig $storageConfig) { + try { + $class = $storageConfig->getBackend()->getStorageClass(); + /** @var \OC\Files\Storage\Storage $storage */ + $storage = new $class($storageConfig->getBackendOptions()); + + // auth mechanism should fire first + $storage = $storageConfig->getBackend()->wrapStorage($storage); + $storage = $storageConfig->getAuthMechanism()->wrapStorage($storage); + + return $storage->getStorageCache()->getNumericId(); + } catch (\Exception $e) { + return -1; + } + } } diff --git a/apps/files_external/service/userglobalstoragesservice.php b/apps/files_external/service/userglobalstoragesservice.php index 6407db2dd54..03c831fe971 100644 --- a/apps/files_external/service/userglobalstoragesservice.php +++ b/apps/files_external/service/userglobalstoragesservice.php @@ -25,6 +25,7 @@ namespace OCA\Files_External\Service; use \OCA\Files_external\Service\GlobalStoragesService; use \OCA\Files_External\Service\BackendService; +use OCP\Files\Config\IUserMountCache; use \OCP\IUserSession; use \OCP\IGroupManager; use \OCA\Files_External\Service\UserTrait; @@ -46,14 +47,16 @@ class UserGlobalStoragesService extends GlobalStoragesService { * @param DBConfigService $dbConfig * @param IUserSession $userSession * @param IGroupManager $groupManager + * @param IUserMountCache $userMountCache */ public function __construct( BackendService $backendService, DBConfigService $dbConfig, IUserSession $userSession, - IGroupManager $groupManager + IGroupManager $groupManager, + IUserMountCache $userMountCache ) { - parent::__construct($backendService, $dbConfig); + parent::__construct($backendService, $dbConfig, $userMountCache); $this->userSession = $userSession; $this->groupManager = $groupManager; } diff --git a/apps/files_external/service/userstoragesservice.php b/apps/files_external/service/userstoragesservice.php index 2805d9e6935..d4b04de609d 100644 --- a/apps/files_external/service/userstoragesservice.php +++ b/apps/files_external/service/userstoragesservice.php @@ -23,6 +23,7 @@ namespace OCA\Files_external\Service; +use OCP\Files\Config\IUserMountCache; use \OCP\IUserSession; use \OC\Files\Filesystem; @@ -44,14 +45,16 @@ class UserStoragesService extends StoragesService { * @param BackendService $backendService * @param DBConfigService $dbConfig * @param IUserSession $userSession user session + * @param IUserMountCache $userMountCache */ public function __construct( BackendService $backendService, DBConfigService $dbConfig, - IUserSession $userSession + IUserSession $userSession, + IUserMountCache $userMountCache ) { $this->userSession = $userSession; - parent::__construct($backendService, $dbConfig); + parent::__construct($backendService, $dbConfig, $userMountCache); } protected function readDBConfig() { diff --git a/apps/files_external/tests/service/globalstoragesservicetest.php b/apps/files_external/tests/service/globalstoragesservicetest.php index 7fc60efca08..6cdfbef82d4 100644 --- a/apps/files_external/tests/service/globalstoragesservicetest.php +++ b/apps/files_external/tests/service/globalstoragesservicetest.php @@ -34,7 +34,7 @@ use \OCA\Files_external\Lib\StorageConfig; class GlobalStoragesServiceTest extends StoragesServiceTest { public function setUp() { parent::setUp(); - $this->service = new GlobalStoragesService($this->backendService, $this->dbConfig); + $this->service = new GlobalStoragesService($this->backendService, $this->dbConfig, $this->mountCache); } public function tearDown() { diff --git a/apps/files_external/tests/service/storagesservicetest.php b/apps/files_external/tests/service/storagesservicetest.php index 710d804fd39..68671b599bd 100644 --- a/apps/files_external/tests/service/storagesservicetest.php +++ b/apps/files_external/tests/service/storagesservicetest.php @@ -76,6 +76,11 @@ abstract class StoragesServiceTest extends \Test\TestCase { */ protected static $hookCalls; + /** + * @var \PHPUnit_Framework_MockObject_MockObject|\OCP\Files\Config\IUserMountCache + */ + protected $mountCache; + public function setUp() { parent::setUp(); $this->dbConfig = new CleaningDBConfig(\OC::$server->getDatabaseConnection()); @@ -87,6 +92,8 @@ abstract class StoragesServiceTest extends \Test\TestCase { ); \OC_Mount_Config::$skipTest = true; + $this->mountCache = $this->getMock('OCP\Files\Config\IUserMountCache'); + // prepare BackendService mock $this->backendService = $this->getMockBuilder('\OCA\Files_External\Service\BackendService') diff --git a/apps/files_external/tests/service/userglobalstoragesservicetest.php b/apps/files_external/tests/service/userglobalstoragesservicetest.php index a22e2874073..baecf143c66 100644 --- a/apps/files_external/tests/service/userglobalstoragesservicetest.php +++ b/apps/files_external/tests/service/userglobalstoragesservicetest.php @@ -94,7 +94,8 @@ class UserGlobalStoragesServiceTest extends GlobalStoragesServiceTest { $this->backendService, $this->dbConfig, $userSession, - $this->groupManager + $this->groupManager, + $this->mountCache ); } diff --git a/apps/files_external/tests/service/userstoragesservicetest.php b/apps/files_external/tests/service/userstoragesservicetest.php index bf0efc13cf5..37423cb8d74 100644 --- a/apps/files_external/tests/service/userstoragesservicetest.php +++ b/apps/files_external/tests/service/userstoragesservicetest.php @@ -49,7 +49,7 @@ class UserStoragesServiceTest extends StoragesServiceTest { public function setUp() { parent::setUp(); - $this->globalStoragesService = new GlobalStoragesService($this->backendService, $this->dbConfig); + $this->globalStoragesService = new GlobalStoragesService($this->backendService, $this->dbConfig, $this->mountCache); $this->userId = $this->getUniqueID('user_'); $this->createUser($this->userId, $this->userId); @@ -62,7 +62,7 @@ class UserStoragesServiceTest extends StoragesServiceTest { ->method('getUser') ->will($this->returnValue($this->user)); - $this->service = new UserStoragesService($this->backendService, $this->dbConfig, $userSession); + $this->service = new UserStoragesService($this->backendService, $this->dbConfig, $userSession, $this->mountCache); } private function makeTestStorageData() { diff --git a/db_structure.xml b/db_structure.xml index e4bd8d998ee..058322f78a3 100644 --- a/db_structure.xml +++ b/db_structure.xml @@ -127,6 +127,93 @@ </table> + <!-- a list of all mounted storage per user, populated on filesystem setup --> + <table> + + <name>*dbprefix*mounts</name> + + <declaration> + + <field> + <name>id</name> + <type>integer</type> + <default>0</default> + <notnull>true</notnull> + <autoincrement>1</autoincrement> + <length>4</length> + </field> + + <field> + <name>storage_id</name> + <type>integer</type> + <notnull>true</notnull> + </field> + + <!-- fileid of the root of the mount, foreign key: oc_filecache.fileid --> + <field> + <name>root_id</name> + <type>integer</type> + <notnull>true</notnull> + </field> + + <field> + <name>user_id</name> + <type>text</type> + <notnull>true</notnull> + <length>64</length> + </field> + + <field> + <name>mount_point</name> + <type>text</type> + <notnull>true</notnull> + <length>4000</length> + </field> + + <index> + <name>mounts_user_index</name> + <unique>false</unique> + <field> + <name>user_id</name> + <sorting>ascending</sorting> + </field> + </index> + + <index> + <name>mounts_storage_index</name> + <unique>false</unique> + <field> + <name>storage_id</name> + <sorting>ascending</sorting> + </field> + </index> + + <index> + <name>mounts_root_index</name> + <unique>false</unique> + <field> + <name>root_id</name> + <sorting>ascending</sorting> + </field> + </index> + + <index> + <name>mounts_user_root_index</name> + <unique>true</unique> + <field> + <name>user_id</name> + <sorting>ascending</sorting> + </field> + <field> + <name>root_id</name> + <sorting>ascending</sorting> + </field> + </index> + + </declaration> + + </table> + <table> <!-- diff --git a/lib/private/files/config/cachedmountinfo.php b/lib/private/files/config/cachedmountinfo.php new file mode 100644 index 00000000000..dba07715edc --- /dev/null +++ b/lib/private/files/config/cachedmountinfo.php @@ -0,0 +1,107 @@ +<?php +/** + * @author Robin Appelman <icewind@owncloud.com> + * + * @copyright Copyright (c) 2015, 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 <http://www.gnu.org/licenses/> + * + */ + +namespace OC\Files\Config; + +use OC\Files\Filesystem; +use OCP\Files\Config\ICachedMountInfo; +use OCP\Files\Node; +use OCP\IUser; + +class CachedMountInfo implements ICachedMountInfo { + /** + * @var IUser + */ + private $user; + + /** + * @var int + */ + private $storageId; + + /** + * @var int + */ + private $rootId; + + /** + * @var string + */ + private $mountPoint; + + /** + * CachedMountInfo constructor. + * + * @param IUser $user + * @param int $storageId + * @param int $rootId + * @param string $mountPoint + */ + public function __construct(IUser $user, $storageId, $rootId, $mountPoint) { + $this->user = $user; + $this->storageId = $storageId; + $this->rootId = $rootId; + $this->mountPoint = $mountPoint; + } + + /** + * @return IUser + */ + public function getUser() { + return $this->user; + } + + /** + * @return int the numeric storage id of the mount + */ + public function getStorageId() { + return $this->storageId; + } + + /** + * @return int the fileid of the root of the mount + */ + public function getRootId() { + return $this->rootId; + } + + /** + * @return Node the root node of the mount + */ + public function getMountPointNode() { + // TODO injection etc + Filesystem::initMountPoints($this->user->getUID()); + $userNode = \OC::$server->getUserFolder($this->user->getUID()); + $nodes = $userNode->getById($this->rootId); + if (count($nodes) > 0) { + return $nodes[0]; + } else { + return null; + } + } + + /** + * @return string the mount point of the mount for the user + */ + public function getMountPoint() { + return $this->mountPoint; + } +} diff --git a/lib/private/files/config/mountprovidercollection.php b/lib/private/files/config/mountprovidercollection.php index eb61ec3f5d5..499fa576fbc 100644 --- a/lib/private/files/config/mountprovidercollection.php +++ b/lib/private/files/config/mountprovidercollection.php @@ -26,6 +26,8 @@ use OC\Hooks\Emitter; use OC\Hooks\EmitterTrait; use OCP\Files\Config\IMountProviderCollection; use OCP\Files\Config\IMountProvider; +use OCP\Files\Config\IUserMountCache; +use OCP\Files\Mount\IMountPoint; use OCP\Files\Storage\IStorageFactory; use OCP\IUser; @@ -43,10 +45,17 @@ class MountProviderCollection implements IMountProviderCollection, Emitter { private $loader; /** + * @var \OCP\Files\Config\IUserMountCache + */ + private $mountCache; + + /** * @param \OCP\Files\Storage\IStorageFactory $loader + * @param IUserMountCache $mountCache */ - public function __construct(IStorageFactory $loader) { + public function __construct(IStorageFactory $loader, IUserMountCache $mountCache) { $this->loader = $loader; + $this->mountCache = $mountCache; } /** @@ -77,4 +86,23 @@ class MountProviderCollection implements IMountProviderCollection, Emitter { $this->providers[] = $provider; $this->emit('\OC\Files\Config', 'registerMountProvider', [$provider]); } + + /** + * Cache mounts for user + * + * @param IUser $user + * @param IMountPoint[] $mountPoints + */ + public function registerMounts(IUser $user, array $mountPoints) { + $this->mountCache->registerMounts($user, $mountPoints); + } + + /** + * Get the mount cache which can be used to search for mounts without setting up the filesystem + * + * @return IUserMountCache + */ + public function getMountCache() { + return $this->mountCache; + } } diff --git a/lib/private/files/config/usermountcache.php b/lib/private/files/config/usermountcache.php new file mode 100644 index 00000000000..e3a494e93a1 --- /dev/null +++ b/lib/private/files/config/usermountcache.php @@ -0,0 +1,232 @@ +<?php +/** + * @author Robin Appelman <icewind@owncloud.com> + * + * @copyright Copyright (c) 2015, 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 <http://www.gnu.org/licenses/> + * + */ + +namespace OC\Files\Config; + +use Doctrine\DBAL\Exception\UniqueConstraintViolationException; +use OCP\Files\Config\ICachedMountInfo; +use OCP\Files\Config\IUserMountCache; +use OCP\Files\Mount\IMountPoint; +use OCP\ICache; +use OCP\IDBConnection; +use OCP\ILogger; +use OCP\IUser; +use OCP\IUserManager; + +/** + * Cache mounts points per user in the cache so we can easilly look them up + */ +class UserMountCache implements IUserMountCache { + /** + * @var IDBConnection + */ + private $connection; + + /** + * @var IUserManager + */ + private $userManager; + + /** @var ICachedMountInfo[][] [$userId => [$cachedMountInfo, ....], ...] */ + private $mountsForUsers = []; + + /** + * @var ILogger + */ + private $logger; + + /** + * UserMountCache constructor. + * + * @param IDBConnection $connection + * @param IUserManager $userManager + * @param ILogger $logger + */ + public function __construct(IDBConnection $connection, IUserManager $userManager, ILogger $logger) { + $this->connection = $connection; + $this->userManager = $userManager; + $this->logger = $logger; + } + + public function registerMounts(IUser $user, array $mounts) { + // filter out non-proper storages coming from unit tests + $mounts = array_filter($mounts, function (IMountPoint $mount) { + return $mount->getStorage()->getCache(); + }); + /** @var ICachedMountInfo[] $newMounts */ + $newMounts = array_map(function (IMountPoint $mount) use ($user) { + $storage = $mount->getStorage(); + $rootId = (int)$storage->getCache()->getId(''); + $storageId = (int)$storage->getStorageCache()->getNumericId(); + // filter out any storages which aren't scanned yet since we aren't interested in files from those storages (yet) + if ($rootId === -1) { + return null; + } else { + return new CachedMountInfo($user, $storageId, $rootId, $mount->getMountPoint()); + } + }, $mounts); + $newMounts = array_values(array_filter($newMounts)); + + $cachedMounts = $this->getMountsForUser($user); + $mountDiff = function (ICachedMountInfo $mount1, ICachedMountInfo $mount2) { + // since we are only looking for mounts for a specific user comparing on root id is enough + return $mount1->getRootId() - $mount2->getRootId(); + }; + + /** @var ICachedMountInfo[] $addedMounts */ + $addedMounts = array_udiff($newMounts, $cachedMounts, $mountDiff); + /** @var ICachedMountInfo[] $removedMounts */ + $removedMounts = array_udiff($cachedMounts, $newMounts, $mountDiff); + + $changedMounts = array_uintersect($newMounts, $cachedMounts, function (ICachedMountInfo $mount1, ICachedMountInfo $mount2) { + // filter mounts with the same root id and different mountpoints + if ($mount1->getRootId() !== $mount2->getRootId()) { + return -1; + } + return ($mount1->getMountPoint() !== $mount2->getMountPoint()) ? 0 : 1; + }); + + foreach ($addedMounts as $mount) { + $this->addToCache($mount); + $this->mountsForUsers[$user->getUID()][] = $mount; + } + foreach ($removedMounts as $mount) { + $this->removeFromCache($mount); + $index = array_search($mount, $this->mountsForUsers[$user->getUID()]); + unset($this->mountsForUsers[$user->getUID()][$index]); + } + foreach ($changedMounts as $mount) { + $this->setMountPoint($mount); + } + } + + private function addToCache(ICachedMountInfo $mount) { + $this->connection->insertIfNotExist('*PREFIX*mounts', [ + 'storage_id' => $mount->getStorageId(), + 'root_id' => $mount->getRootId(), + 'user_id' => $mount->getUser()->getUID(), + 'mount_point' => $mount->getMountPoint() + ]); + } + + private function setMountPoint(ICachedMountInfo $mount) { + $builder = $this->connection->getQueryBuilder(); + + $query = $builder->update('mounts') + ->set('mount_point', $builder->createNamedParameter($mount->getMountPoint())) + ->where($builder->expr()->eq('user_id', $builder->createNamedParameter($mount->getUser()->getUID()))) + ->andWhere($builder->expr()->eq('root_id', $builder->createNamedParameter($mount->getRootId(), \PDO::PARAM_INT))); + + $query->execute(); + } + + private function removeFromCache(ICachedMountInfo $mount) { + $builder = $this->connection->getQueryBuilder(); + + $query = $builder->delete('mounts') + ->where($builder->expr()->eq('user_id', $builder->createNamedParameter($mount->getUser()->getUID()))) + ->andWhere($builder->expr()->eq('root_id', $builder->createNamedParameter($mount->getRootId(), \PDO::PARAM_INT))); + $query->execute(); + } + + private function dbRowToMountInfo(array $row) { + $user = $this->userManager->get($row['user_id']); + return new CachedMountInfo($user, (int)$row['storage_id'], (int)$row['root_id'], $row['mount_point']); + } + + /** + * @param IUser $user + * @return ICachedMountInfo[] + */ + public function getMountsForUser(IUser $user) { + if (!isset($this->mountsForUsers[$user->getUID()])) { + $builder = $this->connection->getQueryBuilder(); + $query = $builder->select('storage_id', 'root_id', 'user_id', 'mount_point') + ->from('mounts') + ->where($builder->expr()->eq('user_id', $builder->createPositionalParameter($user->getUID()))); + + $rows = $query->execute()->fetchAll(); + + $this->mountsForUsers[$user->getUID()] = array_map([$this, 'dbRowToMountInfo'], $rows); + } + return $this->mountsForUsers[$user->getUID()]; + } + + /** + * @param int $numericStorageId + * @return CachedMountInfo[] + */ + public function getMountsForStorageId($numericStorageId) { + $builder = $this->connection->getQueryBuilder(); + $query = $builder->select('storage_id', 'root_id', 'user_id', 'mount_point') + ->from('mounts') + ->where($builder->expr()->eq('storage_id', $builder->createPositionalParameter($numericStorageId, \PDO::PARAM_INT))); + + $rows = $query->execute()->fetchAll(); + + return array_map([$this, 'dbRowToMountInfo'], $rows); + } + + /** + * @param int $rootFileId + * @return CachedMountInfo[] + */ + public function getMountsForRootId($rootFileId) { + $builder = $this->connection->getQueryBuilder(); + $query = $builder->select('storage_id', 'root_id', 'user_id', 'mount_point') + ->from('mounts') + ->where($builder->expr()->eq('root_id', $builder->createPositionalParameter($rootFileId, \PDO::PARAM_INT))); + + $rows = $query->execute()->fetchAll(); + + return array_map([$this, 'dbRowToMountInfo'], $rows); + } + + /** + * Remove all cached mounts for a user + * + * @param IUser $user + */ + public function removeUserMounts(IUser $user) { + $builder = $this->connection->getQueryBuilder(); + + $query = $builder->delete('mounts') + ->where($builder->expr()->eq('user_id', $builder->createNamedParameter($user->getUID()))); + $query->execute(); + } + + public function removeUserStorageMount($storageId, $userId) { + $builder = $this->connection->getQueryBuilder(); + + $query = $builder->delete('mounts') + ->where($builder->expr()->eq('user_id', $builder->createNamedParameter($userId))) + ->andWhere($builder->expr()->eq('storage_id', $builder->createNamedParameter($storageId, \PDO::PARAM_INT))); + $query->execute(); + } + + public function remoteStorageMounts($storageId) { + $builder = $this->connection->getQueryBuilder(); + + $query = $builder->delete('mounts') + ->where($builder->expr()->eq('storage_id', $builder->createNamedParameter($storageId, \PDO::PARAM_INT))); + $query->execute(); + } +} diff --git a/lib/private/files/config/usermountcachelistener.php b/lib/private/files/config/usermountcachelistener.php new file mode 100644 index 00000000000..344bebe342d --- /dev/null +++ b/lib/private/files/config/usermountcachelistener.php @@ -0,0 +1,48 @@ +<?php +/** + * @author Robin Appelman <icewind@owncloud.com> + * + * @copyright Copyright (c) 2015, 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 <http://www.gnu.org/licenses/> + * + */ + +namespace OC\Files\Config; + +use OC\User\Manager; +use OCP\Files\Config\IUserMountCache; + +/** + * Listen to hooks and update the mount cache as needed + */ +class UserMountCacheListener { + /** + * @var IUserMountCache + */ + private $userMountCache; + + /** + * UserMountCacheListener constructor. + * + * @param IUserMountCache $userMountCache + */ + public function __construct(IUserMountCache $userMountCache) { + $this->userMountCache = $userMountCache; + } + + public function listen(Manager $manager) { + $manager->listen('\OC\User', 'postDelete', [$this->userMountCache, 'removeUserMounts']); + } +} diff --git a/lib/private/files/filesystem.php b/lib/private/files/filesystem.php index ffe3a594ba8..9d4a2c0aa05 100644 --- a/lib/private/files/filesystem.php +++ b/lib/private/files/filesystem.php @@ -59,8 +59,10 @@ namespace OC\Files; use OC\Files\Config\MountProviderCollection; +use OC\Files\Mount\MountPoint; use OC\Files\Storage\StorageFactory; use OCP\Files\Config\IMountProvider; +use OCP\Files\Mount\IMountPoint; use OCP\Files\NotFoundException; use OCP\IUserManager; @@ -412,7 +414,8 @@ class Filesystem { $homeStorage['arguments']['legacy'] = true; } - self::mount($homeStorage['class'], $homeStorage['arguments'], $user); + $mount = new MountPoint($homeStorage['class'], '/' . $user, $homeStorage['arguments'], self::getLoader()); + self::getMountManager()->addMount($mount); $home = \OC\Files\Filesystem::getStorage($user); @@ -424,6 +427,8 @@ class Filesystem { if ($userObject) { $mounts = $mountConfigManager->getMountsForUser($userObject); array_walk($mounts, array(self::$mounts, 'addMount')); + $mounts[] = $mount; + $mountConfigManager->registerMounts($userObject, $mounts); } self::listenForNewMountProviders($mountConfigManager, $userManager); diff --git a/lib/private/server.php b/lib/private/server.php index a16e6b9839b..69f403308b3 100644 --- a/lib/private/server.php +++ b/lib/private/server.php @@ -47,6 +47,8 @@ use OC\Diagnostics\EventLogger; use OC\Diagnostics\NullEventLogger; use OC\Diagnostics\NullQueryLogger; use OC\Diagnostics\QueryLogger; +use OC\Files\Config\UserMountCache; +use OC\Files\Config\UserMountCacheListener; use OC\Files\Node\HookConnector; use OC\Files\Node\Root; use OC\Files\View; @@ -136,7 +138,7 @@ class Server extends ServerContainer implements IServerContainer { return new Encryption\Keys\Storage($view, $util); }); - $this->registerService('TagMapper', function(Server $c) { + $this->registerService('TagMapper', function (Server $c) { return new TagMapper($c->getDatabaseConnection()); }); $this->registerService('TagManager', function (Server $c) { @@ -276,13 +278,13 @@ class Server extends ServerContainer implements IServerContainer { $this->registerService('MemCacheFactory', function (Server $c) { $config = $c->getConfig(); - if($config->getSystemValue('installed', false) && !(defined('PHPUNIT_RUN') && PHPUNIT_RUN)) { + if ($config->getSystemValue('installed', false) && !(defined('PHPUNIT_RUN') && PHPUNIT_RUN)) { $v = \OC_App::getAppVersions(); $v['core'] = md5(file_get_contents(\OC::$SERVERROOT . '/version.php')); $version = implode(',', $v); $instanceId = \OC_Util::getInstanceId(); $path = \OC::$SERVERROOT; - $prefix = md5($instanceId.'-'.$version.'-'.$path); + $prefix = md5($instanceId . '-' . $version . '-' . $path); return new \OC\Memcache\Factory($prefix, $c->getLogger(), $config->getSystemValue('memcache.local', null), $config->getSystemValue('memcache.distributed', null), @@ -393,7 +395,7 @@ class Server extends ServerContainer implements IServerContainer { $c->getConfig() ); }); - $this->registerService('AppManager', function(Server $c) { + $this->registerService('AppManager', function (Server $c) { return new \OC\App\AppManager( $c->getUserSession(), $c->getAppConfig(), @@ -401,13 +403,13 @@ class Server extends ServerContainer implements IServerContainer { $c->getMemCacheFactory() ); }); - $this->registerService('DateTimeZone', function(Server $c) { + $this->registerService('DateTimeZone', function (Server $c) { return new DateTimeZone( $c->getConfig(), $c->getSession() ); }); - $this->registerService('DateTimeFormatter', function(Server $c) { + $this->registerService('DateTimeFormatter', function (Server $c) { $language = $c->getConfig()->getUserValue($c->getSession()->get('user_id'), 'core', 'lang', null); return new DateTimeFormatter( @@ -415,9 +417,16 @@ class Server extends ServerContainer implements IServerContainer { $c->getL10N('lib', $language) ); }); - $this->registerService('MountConfigManager', function () { + $this->registerService('UserMountCache', function (Server $c) { + $mountCache = new UserMountCache($c->getDatabaseConnection(), $c->getUserManager(), $c->getLogger()); + $listener = new UserMountCacheListener($mountCache); + $listener->listen($c->getUserManager()); + return $mountCache; + }); + $this->registerService('MountConfigManager', function (Server $c) { $loader = \OC\Files\Filesystem::getLoader(); - return new \OC\Files\Config\MountProviderCollection($loader); + $mountCache = $c->query('UserMountCache'); + return new \OC\Files\Config\MountProviderCollection($loader, $mountCache); }); $this->registerService('IniWrapper', function ($c) { return new IniGetWrapper(); @@ -489,14 +498,14 @@ class Server extends ServerContainer implements IServerContainer { $stream ); }); - $this->registerService('Mailer', function(Server $c) { + $this->registerService('Mailer', function (Server $c) { return new Mailer( $c->getConfig(), $c->getLogger(), new \OC_Defaults() ); }); - $this->registerService('OcsClient', function(Server $c) { + $this->registerService('OcsClient', function (Server $c) { return new OCSClient( $this->getHTTPClientService(), $this->getConfig(), @@ -518,24 +527,24 @@ class Server extends ServerContainer implements IServerContainer { $this->registerService('MountManager', function () { return new \OC\Files\Mount\Manager(); }); - $this->registerService('MimeTypeDetector', function(Server $c) { + $this->registerService('MimeTypeDetector', function (Server $c) { return new \OC\Files\Type\Detection( $c->getURLGenerator(), \OC::$SERVERROOT . '/config/', \OC::$SERVERROOT . '/resources/config/' - ); + ); }); - $this->registerService('MimeTypeLoader', function(Server $c) { + $this->registerService('MimeTypeLoader', function (Server $c) { return new \OC\Files\Type\Loader( $c->getDatabaseConnection() ); }); - $this->registerService('NotificationManager', function() { + $this->registerService('NotificationManager', function () { return new Manager(); }); $this->registerService('CapabilitiesManager', function (Server $c) { $manager = new \OC\CapabilitiesManager(); - $manager->registerCapability(function() use ($c) { + $manager->registerCapability(function () use ($c) { return new \OC\OCS\CoreCapabilities($c->getConfig()); }); return $manager; @@ -547,7 +556,7 @@ class Server extends ServerContainer implements IServerContainer { $factory = new $factoryClass($this); return $factory->getManager(); }); - $this->registerService('EventDispatcher', function() { + $this->registerService('EventDispatcher', function () { return new EventDispatcher(); }); $this->registerService('CryptoWrapper', function (Server $c) { @@ -932,6 +941,7 @@ class Server extends ServerContainer implements IServerContainer { /** * Returns an instance of the db facade + * * @deprecated use getDatabaseConnection, will be removed in ownCloud 10 * @return \OCP\IDb */ @@ -941,6 +951,7 @@ class Server extends ServerContainer implements IServerContainer { /** * Returns an instance of the HTTP helper class + * * @deprecated Use getHTTPClientService() * @return \OC\HTTPHelper */ @@ -1066,7 +1077,7 @@ class Server extends ServerContainer implements IServerContainer { /** * @return \OCP\Files\Config\IMountProviderCollection */ - public function getMountProviderCollection(){ + public function getMountProviderCollection() { return $this->query('MountConfigManager'); } @@ -1082,7 +1093,7 @@ class Server extends ServerContainer implements IServerContainer { /** * @return \OCP\Command\IBus */ - public function getCommandBus(){ + public function getCommandBus() { return $this->query('AsyncCommandBus'); } @@ -1182,6 +1193,7 @@ class Server extends ServerContainer implements IServerContainer { /** * Not a public API as of 8.2, wait for 9.0 + * * @return \OCA\Files_External\Service\BackendService */ public function getStoragesBackendService() { @@ -1190,6 +1202,7 @@ class Server extends ServerContainer implements IServerContainer { /** * Not a public API as of 8.2, wait for 9.0 + * * @return \OCA\Files_External\Service\GlobalStoragesService */ public function getGlobalStoragesService() { @@ -1198,6 +1211,7 @@ class Server extends ServerContainer implements IServerContainer { /** * Not a public API as of 8.2, wait for 9.0 + * * @return \OCA\Files_External\Service\UserGlobalStoragesService */ public function getUserGlobalStoragesService() { @@ -1206,6 +1220,7 @@ class Server extends ServerContainer implements IServerContainer { /** * Not a public API as of 8.2, wait for 9.0 + * * @return \OCA\Files_External\Service\UserStoragesService */ public function getUserStoragesService() { @@ -1219,4 +1234,5 @@ class Server extends ServerContainer implements IServerContainer { public function getShareManager() { return $this->query('ShareManager'); } + } diff --git a/lib/public/files/config/icachedmountinfo.php b/lib/public/files/config/icachedmountinfo.php new file mode 100644 index 00000000000..a587427f1f2 --- /dev/null +++ b/lib/public/files/config/icachedmountinfo.php @@ -0,0 +1,62 @@ +<?php +/** + * @author Robin Appelman <icewind@owncloud.com> + * + * @copyright Copyright (c) 2015, 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 <http://www.gnu.org/licenses/> + * + */ + +namespace OCP\Files\Config; + +use OCP\Files\Node; +use OCP\IUser; + +/** + * Holds information about a mount for a user + * + * @since 9.0.0 + */ +interface ICachedMountInfo { + /** + * @return IUser + * @since 9.0.0 + */ + public function getUser(); + + /** + * @return int the numeric storage id of the mount + * @since 9.0.0 + */ + public function getStorageId(); + + /** + * @return int the fileid of the root of the mount + * @since 9.0.0 + */ + public function getRootId(); + + /** + * @return Node the root node of the mount + * @since 9.0.0 + */ + public function getMountPointNode(); + + /** + * @return string the mount point of the mount for the user + * @since 9.0.0 + */ + public function getMountPoint(); +} diff --git a/lib/public/files/config/imountprovidercollection.php b/lib/public/files/config/imountprovidercollection.php index 43b4bd0ce00..39da61812a9 100644 --- a/lib/public/files/config/imountprovidercollection.php +++ b/lib/public/files/config/imountprovidercollection.php @@ -22,6 +22,7 @@ namespace OCP\Files\Config; +use OCP\Files\Mount\IMountPoint; use OCP\IUser; /** @@ -45,4 +46,12 @@ interface IMountProviderCollection { * @since 8.0.0 */ public function registerProvider(IMountProvider $provider); + + /** + * Get the mount cache which can be used to search for mounts without setting up the filesystem + * + * @return IUserMountCache + * @since 9.0.0 + */ + public function getMountCache(); } diff --git a/lib/public/files/config/iusermountcache.php b/lib/public/files/config/iusermountcache.php new file mode 100644 index 00000000000..f722ad16310 --- /dev/null +++ b/lib/public/files/config/iusermountcache.php @@ -0,0 +1,89 @@ +<?php +/** + * @author Robin Appelman <icewind@owncloud.com> + * + * @copyright Copyright (c) 2015, 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 <http://www.gnu.org/licenses/> + * + */ + +namespace OCP\Files\Config; + +use OCP\Files\Mount\IMountPoint; +use OCP\IUser; + +/** + * Cache mounts points per user in the cache so we can easily look them up + * + * @since 9.0.0 + */ +interface IUserMountCache { + /** + * Register mounts for a user to the cache + * + * @param IUser $user + * @param IMountPoint[] $mounts + * @since 9.0.0 + */ + public function registerMounts(IUser $user, array $mounts); + + /** + * @param IUser $user + * @return ICachedMountInfo[] + * @since 9.0.0 + */ + public function getMountsForUser(IUser $user); + + /** + * @param int $numericStorageId + * @return ICachedMountInfo[] + * @since 9.0.0 + */ + public function getMountsForStorageId($numericStorageId); + + /** + * @param int $rootFileId + * @return ICachedMountInfo[] + * @since 9.0.0 + */ + public function getMountsForRootId($rootFileId); + + /** + * Remove all cached mounts for a user + * + * @param IUser $user + * @since 9.0.0 + */ + public function removeUserMounts(IUser $user); + + /** + * Remove all mounts for a user and storage + * + * @param $storageId + * @param string $userId + * @return mixed + * @since 9.0.0 + */ + public function removeUserStorageMount($storageId, $userId); + + /** + * Remove all cached mounts for a storage + * + * @param $storageId + * @return mixed + * @since 9.0.0 + */ + public function remoteStorageMounts($storageId); +} diff --git a/tests/lib/files/config/usermountcache.php b/tests/lib/files/config/usermountcache.php new file mode 100644 index 00000000000..26449b5dd23 --- /dev/null +++ b/tests/lib/files/config/usermountcache.php @@ -0,0 +1,257 @@ +<?php +/** + * Copyright (c) 2015 Robin Appelman <icewind@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace Test\Files\Config; + +use OC\Files\Mount\MountPoint; +use OC\Files\Storage\Temporary; +use OC\Log; +use OC\User\Manager; +use OCP\Files\Config\ICachedMountInfo; +use OCP\IDBConnection; +use OCP\IUserManager; +use Test\TestCase; +use Test\Util\User\Dummy; + +/** + * @group DB + */ +class UserMountCache extends TestCase { + /** + * @var IDBConnection + */ + private $connection; + + /** + * @var IUserManager + */ + private $userManager; + + /** + * @var \OC\Files\Config\UserMountCache + */ + private $cache; + + public function setUp() { + $this->connection = \OC::$server->getDatabaseConnection(); + $this->userManager = new Manager(null); + $userBackend = new Dummy(); + $userBackend->createUser('u1', ''); + $userBackend->createUser('u2', ''); + $this->userManager->registerBackend($userBackend); + $this->cache = new \OC\Files\Config\UserMountCache($this->connection, $this->userManager, $this->getMock('\OC\Log')); + } + + public function tearDown() { + $builder = $this->connection->getQueryBuilder(); + + $builder->delete('mounts')->execute(); + } + + private function getStorage($storageId, $rootId) { + $storageCache = $this->getMockBuilder('\OC\Files\Cache\Storage') + ->disableOriginalConstructor() + ->getMock(); + $storageCache->expects($this->any()) + ->method('getNumericId') + ->will($this->returnValue($storageId)); + + $cache = $this->getMockBuilder('\OC\Files\Cache\Cache') + ->disableOriginalConstructor() + ->getMock(); + $cache->expects($this->any()) + ->method('getId') + ->will($this->returnValue($rootId)); + + $storage = $this->getMockBuilder('\OC\Files\Storage\Storage') + ->disableOriginalConstructor() + ->getMock(); + $storage->expects($this->any()) + ->method('getStorageCache') + ->will($this->returnValue($storageCache)); + $storage->expects($this->any()) + ->method('getCache') + ->will($this->returnValue($cache)); + + return $storage; + } + + private function clearCache() { + $this->invokePrivate($this->cache, 'mountsForUsers', [[]]); + } + + public function testNewMounts() { + $user = $this->userManager->get('u1'); + + $storage = $this->getStorage(10, 20); + $mount = new MountPoint($storage, '/asd/'); + + $this->cache->registerMounts($user, [$mount]); + + $this->clearCache(); + + $cachedMounts = $this->cache->getMountsForUser($user); + + $this->assertCount(1, $cachedMounts); + $cachedMount = $cachedMounts[0]; + $this->assertEquals('/asd/', $cachedMount->getMountPoint()); + $this->assertEquals($user, $cachedMount->getUser()); + $this->assertEquals($storage->getCache()->getId(''), $cachedMount->getRootId()); + $this->assertEquals($storage->getStorageCache()->getNumericId(), $cachedMount->getStorageId()); + } + + public function testSameMounts() { + $user = $this->userManager->get('u1'); + + $storage = $this->getStorage(10, 20); + $mount = new MountPoint($storage, '/asd/'); + + $this->cache->registerMounts($user, [$mount]); + + $this->clearCache(); + + $this->cache->registerMounts($user, [$mount]); + + $this->clearCache(); + + $cachedMounts = $this->cache->getMountsForUser($user); + + $this->assertCount(1, $cachedMounts); + $cachedMount = $cachedMounts[0]; + $this->assertEquals('/asd/', $cachedMount->getMountPoint()); + $this->assertEquals($user, $cachedMount->getUser()); + $this->assertEquals($storage->getCache()->getId(''), $cachedMount->getRootId()); + $this->assertEquals($storage->getStorageCache()->getNumericId(), $cachedMount->getStorageId()); + } + + public function testRemoveMounts() { + $user = $this->userManager->get('u1'); + + $storage = $this->getStorage(10, 20); + $mount = new MountPoint($storage, '/asd/'); + + $this->cache->registerMounts($user, [$mount]); + + $this->clearCache(); + + $this->cache->registerMounts($user, []); + + $this->clearCache(); + + $cachedMounts = $this->cache->getMountsForUser($user); + + $this->assertCount(0, $cachedMounts); + } + + public function testChangeMounts() { + $user = $this->userManager->get('u1'); + + $storage = $this->getStorage(10, 20); + $mount = new MountPoint($storage, '/foo/'); + + $this->cache->registerMounts($user, [$mount]); + + $this->clearCache(); + + $this->cache->registerMounts($user, [$mount]); + + $this->clearCache(); + + $cachedMounts = $this->cache->getMountsForUser($user); + + $this->assertCount(1, $cachedMounts); + $cachedMount = $cachedMounts[0]; + $this->assertEquals('/foo/', $cachedMount->getMountPoint()); + } + + public function testGetMountsForUser() { + $user1 = $this->userManager->get('u1'); + $user2 = $this->userManager->get('u2'); + + $mount1 = new MountPoint($this->getStorage(1, 2), '/foo/'); + $mount2 = new MountPoint($this->getStorage(3, 4), '/bar/'); + + $this->cache->registerMounts($user1, [$mount1, $mount2]); + $this->cache->registerMounts($user2, [$mount2]); + + $this->clearCache(); + + $cachedMounts = $this->cache->getMountsForUser($user1); + + $this->assertCount(2, $cachedMounts); + $this->assertEquals('/foo/', $cachedMounts[0]->getMountPoint()); + $this->assertEquals($user1, $cachedMounts[0]->getUser()); + $this->assertEquals(2, $cachedMounts[0]->getRootId()); + $this->assertEquals(1, $cachedMounts[0]->getStorageId()); + + $this->assertEquals('/bar/', $cachedMounts[1]->getMountPoint()); + $this->assertEquals($user1, $cachedMounts[1]->getUser()); + $this->assertEquals(4, $cachedMounts[1]->getRootId()); + $this->assertEquals(3, $cachedMounts[1]->getStorageId()); + } + + public function testGetMountsByStorageId() { + $user1 = $this->userManager->get('u1'); + $user2 = $this->userManager->get('u2'); + + $mount1 = new MountPoint($this->getStorage(1, 2), '/foo/'); + $mount2 = new MountPoint($this->getStorage(3, 4), '/bar/'); + + $this->cache->registerMounts($user1, [$mount1, $mount2]); + $this->cache->registerMounts($user2, [$mount2]); + + $this->clearCache(); + + $cachedMounts = $this->cache->getMountsForStorageId(3); + usort($cachedMounts, function (ICachedMountInfo $a, ICachedMountInfo $b) { + return strcmp($a->getUser()->getUID(), $b->getUser()->getUID()); + }); + + $this->assertCount(2, $cachedMounts); + + $this->assertEquals('/bar/', $cachedMounts[0]->getMountPoint()); + $this->assertEquals($user1, $cachedMounts[0]->getUser()); + $this->assertEquals(4, $cachedMounts[0]->getRootId()); + $this->assertEquals(3, $cachedMounts[0]->getStorageId()); + + $this->assertEquals('/bar/', $cachedMounts[1]->getMountPoint()); + $this->assertEquals($user2, $cachedMounts[1]->getUser()); + $this->assertEquals(4, $cachedMounts[1]->getRootId()); + $this->assertEquals(3, $cachedMounts[1]->getStorageId()); + } + + public function testGetMountsByRootId() { + $user1 = $this->userManager->get('u1'); + $user2 = $this->userManager->get('u2'); + + $mount1 = new MountPoint($this->getStorage(1, 2), '/foo/'); + $mount2 = new MountPoint($this->getStorage(3, 4), '/bar/'); + + $this->cache->registerMounts($user1, [$mount1, $mount2]); + $this->cache->registerMounts($user2, [$mount2]); + + $this->clearCache(); + + $cachedMounts = $this->cache->getMountsForRootId(4); + usort($cachedMounts, function (ICachedMountInfo $a, ICachedMountInfo $b) { + return strcmp($a->getUser()->getUID(), $b->getUser()->getUID()); + }); + + $this->assertCount(2, $cachedMounts); + + $this->assertEquals('/bar/', $cachedMounts[0]->getMountPoint()); + $this->assertEquals($user1, $cachedMounts[0]->getUser()); + $this->assertEquals(4, $cachedMounts[0]->getRootId()); + $this->assertEquals(3, $cachedMounts[0]->getStorageId()); + + $this->assertEquals('/bar/', $cachedMounts[1]->getMountPoint()); + $this->assertEquals($user2, $cachedMounts[1]->getUser()); + $this->assertEquals(4, $cachedMounts[1]->getRootId()); + $this->assertEquals(3, $cachedMounts[1]->getStorageId()); + } +} |