]> source.dussan.org Git - nextcloud-server.git/commitdiff
Add EtagPropagator to handle etag changes when external storages are changed
authorRobin Appelman <icewind@owncloud.com>
Sun, 28 Sep 2014 15:09:07 +0000 (17:09 +0200)
committerVincent Petry <pvince81@owncloud.com>
Wed, 8 Oct 2014 12:22:17 +0000 (14:22 +0200)
apps/files_external/lib/etagpropagator.php [new file with mode: 0644]
apps/files_external/tests/etagpropagator.php [new file with mode: 0644]

diff --git a/apps/files_external/lib/etagpropagator.php b/apps/files_external/lib/etagpropagator.php
new file mode 100644 (file)
index 0000000..0057165
--- /dev/null
@@ -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 (file)
index 0000000..7fa1863
--- /dev/null
@@ -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'));
+       }
+}