cache mountpoints in the db

This commit is contained in:
Robin Appelman 2015-11-26 17:47:53 +01:00
parent 647d8ea5de
commit cf6ee1c866
10 changed files with 769 additions and 5 deletions

View File

@ -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>
<!--

View File

@ -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;
}
}

View File

@ -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;
/**
* @param \OCP\Files\Storage\IStorageFactory $loader
* @var \OCP\Files\Config\IUserMountCache
*/
public function __construct(IStorageFactory $loader) {
private $mountCache;
/**
* @param \OCP\Files\Storage\IStorageFactory $loader
* @param IUserMountCache $mountCache
*/
public function __construct(IStorageFactory $loader, IUserMountCache $mountCache) {
$this->loader = $loader;
$this->mountCache = $mountCache;
}
/**
@ -77,4 +86,21 @@ 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 cacheMounts(IUser $user, array $mountPoints) {
$this->mountCache->registerMounts($user, $mountPoints);
}
/**
* @return IUserMountCache
*/
public function getMountCache() {
return $this->mountCache;
}
}

View File

@ -0,0 +1,167 @@
<?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 OCP\Files\Config\ICachedMountInfo;
use OCP\Files\Config\IUserMountCache;
use OCP\Files\Mount\IMountPoint;
use OCP\ICache;
use OCP\IDBConnection;
use OCP\IUser;
use OCP\IUserManager;
class UserMountCache implements IUserMountCache {
/**
* @var IDBConnection
*/
private $connection;
/**
* @var IUserManager
*/
private $userManager;
/** @var ICachedMountInfo[][] [$userId => [$cachedMountInfo, ....], ...] */
private $mountsForUsers = [];
/**
* UserMountCache constructor.
*
* @param IDBConnection $connection
* @param IUserManager $userManager
*/
public function __construct(IDBConnection $connection, IUserManager $userManager) {
$this->connection = $connection;
$this->userManager = $userManager;
}
public function registerMounts(IUser $user, array $mounts) {
$mounts = array_values($mounts);
/** @var ICachedMountInfo[] $newMounts */
$newMounts = array_map(function (IMountPoint $mount) use ($user) {
$storage = $mount->getStorage();
$rootId = (int)$storage->getCache()->getId('');
$storageId = (int)$storage->getStorageCache()->getNumericId();
return new CachedMountInfo($user, $storageId, $rootId, $mount->getMountPoint());
}, $mounts);
$cachedMounts = $this->getMountsForUser($user);
$mountDiff = function (ICachedMountInfo $mount1, ICachedMountInfo $mount2) {
return $mount1->getRootId() - $mount2->getRootId();
};
/** @var ICachedMountInfo[] $addedMounts */
$addedMounts = array_udiff($newMounts, $cachedMounts, $mountDiff);
/** @var ICachedMountInfo[] $removedMounts */
$removedMounts = array_udiff($cachedMounts, $newMounts, $mountDiff);
foreach ($addedMounts as $mount) {
$this->addToCache($mount);
$this->mountsForUsers[$user->getUID()][] = $mount;
}
foreach ($removedMounts as $mount) {
$this->removeFromCache($mount);
$this->mountsForUsers[$user->getUID()] = [];
}
}
private function addToCache(ICachedMountInfo $mount) {
$builder = $this->connection->getQueryBuilder();
$query = $builder->insert('mounts')
->values([
'storage_id' => ':storage',
'root_id' => ':root',
'user_id' => ':user',
'mount_point' => ':mount'
]);
$query->setParameters([
':storage' => $mount->getStorageId(),
':root' => $mount->getRootId(),
':user' => $mount->getUser()->getUID(),
':mount' => $mount->getMountPoint()
]);
$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);
}
}

View File

@ -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::$mounts->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->cacheMounts($userObject, $mounts);
}
self::listenForNewMountProviders($mountConfigManager, $userManager);

View File

@ -47,6 +47,7 @@ use OC\Diagnostics\EventLogger;
use OC\Diagnostics\NullEventLogger;
use OC\Diagnostics\NullQueryLogger;
use OC\Diagnostics\QueryLogger;
use OC\Files\Config\UserMountCache;
use OC\Files\Node\HookConnector;
use OC\Files\Node\Root;
use OC\Files\View;
@ -404,9 +405,10 @@ class Server extends ServerContainer implements IServerContainer {
$c->getL10N('lib', $language)
);
});
$this->registerService('MountConfigManager', function () {
$this->registerService('MountConfigManager', function (Server $c) {
$loader = \OC\Files\Filesystem::getLoader();
return new \OC\Files\Config\MountProviderCollection($loader);
$mountCache = new UserMountCache($c->getDatabaseConnection(), $c->getUserManager());
return new \OC\Files\Config\MountProviderCollection($loader, $mountCache);
});
$this->registerService('IniWrapper', function ($c) {
return new IniGetWrapper();

View File

@ -0,0 +1,60 @@
<?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;
/**
* @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();
}

View File

@ -22,6 +22,7 @@
namespace OCP\Files\Config;
use OCP\Files\Mount\IMountPoint;
use OCP\IUser;
/**

View File

@ -0,0 +1,60 @@
<?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;
/**
* @since 9.0.0
*/
interface IUserMountCache {
/**
* Register a mount 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);
}

View File

@ -0,0 +1,249 @@
<?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\User\Manager;
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);
}
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);
$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);
$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());
}
}