aboutsummaryrefslogtreecommitdiffstats
path: root/apps/files_external
diff options
context:
space:
mode:
authorRobin Appelman <icewind@owncloud.com>2014-09-28 17:09:07 +0200
committerVincent Petry <pvince81@owncloud.com>2014-10-08 14:22:17 +0200
commit5d7bd8be422d9e9904500869182d344f18aa8710 (patch)
tree3cb312b7df64a6358c436a35b36d4f1e1740affa /apps/files_external
parent9a5d0f6084c7ecb491e4a1c971b125ac57eeb5da (diff)
downloadnextcloud-server-5d7bd8be422d9e9904500869182d344f18aa8710.tar.gz
nextcloud-server-5d7bd8be422d9e9904500869182d344f18aa8710.zip
Add EtagPropagator to handle etag changes when external storages are changed
Diffstat (limited to 'apps/files_external')
-rw-r--r--apps/files_external/lib/etagpropagator.php107
-rw-r--r--apps/files_external/tests/etagpropagator.php328
2 files changed, 435 insertions, 0 deletions
diff --git a/apps/files_external/lib/etagpropagator.php b/apps/files_external/lib/etagpropagator.php
new file mode 100644
index 00000000000..00571657504
--- /dev/null
+++ b/apps/files_external/lib/etagpropagator.php
@@ -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);
+ }
+ }
+}
diff --git a/apps/files_external/tests/etagpropagator.php b/apps/files_external/tests/etagpropagator.php
new file mode 100644
index 00000000000..7fa1863f962
--- /dev/null
+++ b/apps/files_external/tests/etagpropagator.php
@@ -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'));
+ }
+}