Add EtagPropagator to handle etag changes when external storages are changed

This commit is contained in:
Robin Appelman 2014-09-28 17:09:07 +02:00 committed by Vincent Petry
parent 9a5d0f6084
commit 5d7bd8be42
2 changed files with 435 additions and 0 deletions

View File

@ -0,0 +1,107 @@
<?php
/**
* Copyright (c) 2014 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 OCA\Files_External;
use OC\Files\Filesystem;
class EtagPropagator {
/**
* @var \OCP\IUser
*/
protected $user;
/**
* @var \OC\Files\Cache\ChangePropagator
*/
protected $changePropagator;
/**
* @var \OCP\IConfig
*/
protected $config;
/**
* @param \OCP\IUser $user
* @param \OC\Files\Cache\ChangePropagator $changePropagator
* @param \OCP\IConfig $config
*/
public function __construct($user, $changePropagator, $config) {
$this->user = $user;
$this->changePropagator = $changePropagator;
$this->config = $config;
}
public function propagateDirtyMountPoints($time = null) {
if ($time === null) {
$time = time();
}
$mountPoints = $this->getDirtyMountPoints();
foreach ($mountPoints as $mountPoint) {
$this->changePropagator->addChange($mountPoint);
$this->config->setUserValue($this->user->getUID(), 'files_external', $mountPoint, $time);
}
if (count($mountPoints)) {
$this->changePropagator->propagateChanges($time);
}
}
/**
* Get all mountpoints we need to update the etag for
*
* @return string[]
*/
protected function getDirtyMountPoints() {
$dirty = array();
$mountPoints = $this->config->getAppKeys('files_external');
foreach ($mountPoints as $mountPoint) {
if (substr($mountPoint, 0, 1) === '/') {
$updateTime = $this->config->getAppValue('files_external', $mountPoint);
$userTime = $this->config->getUserValue($this->user->getUID(), 'files_external', $mountPoint);
if ($updateTime > $userTime) {
$dirty[] = $mountPoint;
}
}
}
return $dirty;
}
/**
* @param string $mountPoint
* @param int $time
*/
protected function markDirty($mountPoint, $time = null) {
if ($time === null) {
$time = time();
}
$this->config->setAppValue('files_external', $mountPoint, $time);
}
/**
* Update etags for mount points for known user
* For global or group mount points, updating the etag for every user is not feasible
* instead we mark the mount point as dirty and update the etag when the filesystem is loaded for the user
*
* @param array $params
* @param int $time
*/
public function updateHook($params, $time = null) {
if ($time === null) {
$time = time();
}
$users = $params[Filesystem::signal_param_users];
$type = $params[Filesystem::signal_param_mount_type];
$mountPoint = $params[Filesystem::signal_param_path];
if ($type === \OC_Mount_Config::MOUNT_TYPE_GROUP or $users === 'all') {
$this->markDirty($mountPoint, $time);
} else {
$this->changePropagator->addChange($mountPoint);
$this->changePropagator->propagateChanges($time);
}
}
}

View File

@ -0,0 +1,328 @@
<?php
/**
* Copyright (c) 2014 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 Tests\Files_External;
use OC\Files\Filesystem;
use OC\User\User;
class EtagPropagator extends \PHPUnit_Framework_TestCase {
protected function getUser() {
return new User(uniqid(), null);
}
/**
* @return \PHPUnit_Framework_MockObject_MockObject | \OC\Files\Cache\ChangePropagator
*/
protected function getChangePropagator() {
return $this->getMockBuilder('\OC\Files\Cache\ChangePropagator')
->disableOriginalConstructor()
->getMock();
}
/**
* @return \PHPUnit_Framework_MockObject_MockObject | \OCP\IConfig
*/
protected function getConfig() {
$appConfig = array();
$userConfig = array();
$mock = $this->getMockBuilder('\OCP\IConfig')
->disableOriginalConstructor()
->getMock();
$mock->expects($this->any())
->method('getAppValue')
->will($this->returnCallback(function ($appId, $key, $default = null) use (&$appConfig) {
if (isset($appConfig[$appId]) and isset($appConfig[$appId][$key])) {
return $appConfig[$appId][$key];
} else {
return $default;
}
}));
$mock->expects($this->any())
->method('setAppValue')
->will($this->returnCallback(function ($appId, $key, $value) use (&$appConfig) {
if (!isset($appConfig[$appId])) {
$appConfig[$appId] = array();
}
$appConfig[$appId][$key] = $value;
}));
$mock->expects($this->any())
->method('getAppKeys')
->will($this->returnCallback(function ($appId) use (&$appConfig) {
if (!isset($appConfig[$appId])) {
$appConfig[$appId] = array();
}
return array_keys($appConfig[$appId]);
}));
$mock->expects($this->any())
->method('getUserValue')
->will($this->returnCallback(function ($userId, $appId, $key, $default = null) use (&$userConfig) {
if (isset($userConfig[$userId]) and isset($userConfig[$userId][$appId]) and isset($userConfig[$userId][$appId][$key])) {
return $userConfig[$userId][$appId][$key];
} else {
return $default;
}
}));
$mock->expects($this->any())
->method('setUserValue')
->will($this->returnCallback(function ($userId, $appId, $key, $value) use (&$userConfig) {
if (!isset($userConfig[$userId])) {
$userConfig[$userId] = array();
}
if (!isset($userConfig[$userId][$appId])) {
$userConfig[$userId][$appId] = array();
}
$userConfig[$userId][$appId][$key] = $value;
}));
return $mock;
}
public function testSingleUserMount() {
$time = time();
$user = $this->getUser();
$config = $this->getConfig();
$changePropagator = $this->getChangePropagator();
$propagator = new \OCA\Files_External\EtagPropagator($user, $changePropagator, $config);
$changePropagator->expects($this->once())
->method('addChange')
->with('/test');
$changePropagator->expects($this->once())
->method('propagateChanges')
->with($time);
$propagator->updateHook(array(
Filesystem::signal_param_path => '/test',
Filesystem::signal_param_mount_type => \OC_Mount_Config::MOUNT_TYPE_USER,
Filesystem::signal_param_users => $user->getUID(),
), $time);
}
public function testGlobalMountNoDirectUpdate() {
$time = time();
$user = $this->getUser();
$config = $this->getConfig();
$changePropagator = $this->getChangePropagator();
$propagator = new \OCA\Files_External\EtagPropagator($user, $changePropagator, $config);
// not updated directly
$changePropagator->expects($this->never())
->method('addChange');
$changePropagator->expects($this->never())
->method('propagateChanges');
$propagator->updateHook(array(
Filesystem::signal_param_path => '/test',
Filesystem::signal_param_mount_type => \OC_Mount_Config::MOUNT_TYPE_USER,
Filesystem::signal_param_users => 'all',
), $time);
// mount point marked as dirty
$this->assertEquals(array('/test'), $config->getAppKeys('files_external'));
$this->assertEquals($time, $config->getAppValue('files_external', '/test'));
}
public function testGroupMountNoDirectUpdate() {
$time = time();
$user = $this->getUser();
$config = $this->getConfig();
$changePropagator = $this->getChangePropagator();
$propagator = new \OCA\Files_External\EtagPropagator($user, $changePropagator, $config);
// not updated directly
$changePropagator->expects($this->never())
->method('addChange');
$changePropagator->expects($this->never())
->method('propagateChanges');
$propagator->updateHook(array(
Filesystem::signal_param_path => '/test',
Filesystem::signal_param_mount_type => \OC_Mount_Config::MOUNT_TYPE_GROUP,
Filesystem::signal_param_users => 'test',
), $time);
// mount point marked as dirty
$this->assertEquals(array('/test'), $config->getAppKeys('files_external'));
$this->assertEquals($time, $config->getAppValue('files_external', '/test'));
}
public function testGlobalMountNoDirtyMountPoint() {
$time = time();
$user = $this->getUser();
$config = $this->getConfig();
$changePropagator = $this->getChangePropagator();
$propagator = new \OCA\Files_External\EtagPropagator($user, $changePropagator, $config);
$changePropagator->expects($this->never())
->method('addChange');
$changePropagator->expects($this->never())
->method('propagateChanges');
$propagator->propagateDirtyMountPoints($time);
$this->assertEquals(0, $config->getUserValue($user->getUID(), 'files_external', '/test', 0));
}
public function testGlobalMountDirtyMountPointFirstTime() {
$time = time();
$user = $this->getUser();
$config = $this->getConfig();
$changePropagator = $this->getChangePropagator();
$propagator = new \OCA\Files_External\EtagPropagator($user, $changePropagator, $config);
$config->setAppValue('files_external', '/test', $time - 10);
$changePropagator->expects($this->once())
->method('addChange')
->with('/test');
$changePropagator->expects($this->once())
->method('propagateChanges')
->with($time);
$propagator->propagateDirtyMountPoints($time);
$this->assertEquals($time, $config->getUserValue($user->getUID(), 'files_external', '/test'));
}
public function testGlobalMountNonDirtyMountPoint() {
$time = time();
$user = $this->getUser();
$config = $this->getConfig();
$changePropagator = $this->getChangePropagator();
$propagator = new \OCA\Files_External\EtagPropagator($user, $changePropagator, $config);
$config->setAppValue('files_external', '/test', $time - 10);
$config->setUserValue($user->getUID(), 'files_external', '/test', $time - 10);
$changePropagator->expects($this->never())
->method('addChange');
$changePropagator->expects($this->never())
->method('propagateChanges');
$propagator->propagateDirtyMountPoints($time);
$this->assertEquals($time - 10, $config->getUserValue($user->getUID(), 'files_external', '/test'));
}
public function testGlobalMountNonDirtyMountPointOtherUser() {
$time = time();
$user = $this->getUser();
$user2 = $this->getUser();
$config = $this->getConfig();
$changePropagator = $this->getChangePropagator();
$propagator = new \OCA\Files_External\EtagPropagator($user, $changePropagator, $config);
$config->setAppValue('files_external', '/test', $time - 10);
$config->setUserValue($user2->getUID(), 'files_external', '/test', $time - 10);
$changePropagator->expects($this->once())
->method('addChange')
->with('/test');
$changePropagator->expects($this->once())
->method('propagateChanges')
->with($time);
$propagator->propagateDirtyMountPoints($time);
$this->assertEquals($time, $config->getUserValue($user->getUID(), 'files_external', '/test'));
}
public function testGlobalMountDirtyMountPointSecondTime() {
$time = time();
$user = $this->getUser();
$config = $this->getConfig();
$changePropagator = $this->getChangePropagator();
$propagator = new \OCA\Files_External\EtagPropagator($user, $changePropagator, $config);
$config->setAppValue('files_external', '/test', $time - 10);
$config->setUserValue($user->getUID(), 'files_external', '/test', $time - 20);
$changePropagator->expects($this->once())
->method('addChange')
->with('/test');
$changePropagator->expects($this->once())
->method('propagateChanges')
->with($time);
$propagator->propagateDirtyMountPoints($time);
$this->assertEquals($time, $config->getUserValue($user->getUID(), 'files_external', '/test'));
}
public function testGlobalMountMultipleUsers() {
$time = time();
$config = $this->getConfig();
$user1 = $this->getUser();
$user2 = $this->getUser();
$user3 = $this->getUser();
$changePropagator1 = $this->getChangePropagator();
$changePropagator2 = $this->getChangePropagator();
$changePropagator3 = $this->getChangePropagator();
$propagator1 = new \OCA\Files_External\EtagPropagator($user1, $changePropagator1, $config);
$propagator2 = new \OCA\Files_External\EtagPropagator($user2, $changePropagator2, $config);
$propagator3 = new \OCA\Files_External\EtagPropagator($user3, $changePropagator3, $config);
$config->setAppValue('files_external', '/test', $time - 10);
$changePropagator1->expects($this->once())
->method('addChange')
->with('/test');
$changePropagator1->expects($this->once())
->method('propagateChanges')
->with($time);
$propagator1->propagateDirtyMountPoints($time);
$this->assertEquals($time, $config->getUserValue($user1->getUID(), 'files_external', '/test'));
$this->assertEquals(0, $config->getUserValue($user2->getUID(), 'files_external', '/test', 0));
$this->assertEquals(0, $config->getUserValue($user3->getUID(), 'files_external', '/test', 0));
$changePropagator2->expects($this->once())
->method('addChange')
->with('/test');
$changePropagator2->expects($this->once())
->method('propagateChanges')
->with($time);
$propagator2->propagateDirtyMountPoints($time);
$this->assertEquals($time, $config->getUserValue($user1->getUID(), 'files_external', '/test'));
$this->assertEquals($time, $config->getUserValue($user2->getUID(), 'files_external', '/test', 0));
$this->assertEquals(0, $config->getUserValue($user3->getUID(), 'files_external', '/test', 0));
}
public function testGlobalMountMultipleDirtyMountPoints() {
$time = time();
$user = $this->getUser();
$config = $this->getConfig();
$changePropagator = $this->getChangePropagator();
$propagator = new \OCA\Files_External\EtagPropagator($user, $changePropagator, $config);
$config->setAppValue('files_external', '/test', $time - 10);
$config->setAppValue('files_external', '/foo', $time - 50);
$config->setAppValue('files_external', '/bar', $time - 70);
$config->setUserValue($user->getUID(), 'files_external', '/foo', $time - 70);
$config->setUserValue($user->getUID(), 'files_external', '/bar', $time - 70);
$changePropagator->expects($this->exactly(2))
->method('addChange');
$changePropagator->expects($this->once())
->method('propagateChanges')
->with($time);
$propagator->propagateDirtyMountPoints($time);
$this->assertEquals($time, $config->getUserValue($user->getUID(), 'files_external', '/test'));
$this->assertEquals($time, $config->getUserValue($user->getUID(), 'files_external', '/foo'));
$this->assertEquals($time - 70, $config->getUserValue($user->getUID(), 'files_external', '/bar'));
}
}