take the etag of child mounts into account for the folder etag

this replaces shared etag propagation
This commit is contained in:
Robin Appelman 2015-11-10 16:14:08 +01:00
parent d006a7c723
commit 888df3933d
21 changed files with 243 additions and 815 deletions

View File

@ -42,7 +42,6 @@ $l = \OC::$server->getL10N('files_sharing');
$application = new Application();
$application->registerMountProviders();
$application->setupPropagation();
\OCP\App::registerAdmin('files_sharing', 'settings-admin');
\OCP\App::registerPersonal('files_sharing', 'settings-personal');

View File

@ -27,8 +27,6 @@ namespace OCA\Files_Sharing\AppInfo;
use OCA\Files_Sharing\Helper;
use OCA\Files_Sharing\MountProvider;
use OCA\Files_Sharing\Propagation\PropagationManager;
use OCA\Files_Sharing\Propagation\GroupPropagationManager;
use OCP\AppFramework\App;
use OC\AppFramework\Utility\SimpleContainer;
use OCA\Files_Sharing\Controllers\ExternalSharesController;
@ -116,8 +114,7 @@ class Application extends App {
/** @var \OCP\IServerContainer $server */
$server = $c->query('ServerContainer');
return new MountProvider(
$server->getConfig(),
$c->query('PropagationManager')
$server->getConfig()
);
});
@ -132,25 +129,6 @@ class Application extends App {
);
});
$container->registerService('PropagationManager', function (IContainer $c) {
/** @var \OCP\IServerContainer $server */
$server = $c->query('ServerContainer');
return new PropagationManager(
$server->getUserSession(),
$server->getConfig()
);
});
$container->registerService('GroupPropagationManager', function (IContainer $c) {
/** @var \OCP\IServerContainer $server */
$server = $c->query('ServerContainer');
return new GroupPropagationManager(
$server->getUserSession(),
$server->getGroupManager(),
$c->query('PropagationManager')
);
});
/*
* Register capabilities
*/
@ -164,11 +142,4 @@ class Application extends App {
$mountProviderCollection->registerProvider($this->getContainer()->query('MountProvider'));
$mountProviderCollection->registerProvider($this->getContainer()->query('ExternalMountProvider'));
}
public function setupPropagation() {
$propagationManager = $this->getContainer()->query('PropagationManager');
\OCP\Util::connectHook('OC_Filesystem', 'setup', $propagationManager, 'globalSetup');
$this->getContainer()->query('GroupPropagationManager')->globalSetup();
}
}

View File

@ -24,7 +24,6 @@ namespace OCA\Files_Sharing;
use OC\Files\Filesystem;
use OC\User\NoUserException;
use OCA\Files_Sharing\Propagation\PropagationManager;
use OCP\Files\Config\IMountProvider;
use OCP\Files\Storage\IStorageFactory;
use OCP\IConfig;
@ -36,18 +35,11 @@ class MountProvider implements IMountProvider {
*/
protected $config;
/**
* @var \OCA\Files_Sharing\Propagation\PropagationManager
*/
protected $propagationManager;
/**
* @param \OCP\IConfig $config
* @param \OCA\Files_Sharing\Propagation\PropagationManager $propagationManager
*/
public function __construct(IConfig $config, PropagationManager $propagationManager) {
public function __construct(IConfig $config) {
$this->config = $config;
$this->propagationManager = $propagationManager;
}
@ -60,21 +52,15 @@ class MountProvider implements IMountProvider {
*/
public function getMountsForUser(IUser $user, IStorageFactory $storageFactory) {
$shares = \OCP\Share::getItemsSharedWithUser('file', $user->getUID());
$propagator = $this->propagationManager->getSharePropagator($user->getUID());
$propagator->propagateDirtyMountPoints($shares);
$shares = array_filter($shares, function ($share) {
return $share['permissions'] > 0;
});
$shares = array_map(function ($share) use ($user, $storageFactory) {
// for updating etags for the share owner when we make changes to this share.
$ownerPropagator = $this->propagationManager->getChangePropagator($share['uid_owner']);
return new SharedMount(
'\OC\Files\Storage\Shared',
'/' . $user->getUID() . '/' . $share['file_target'],
array(
'propagationManager' => $this->propagationManager,
'propagator' => $ownerPropagator,
'share' => $share,
'user' => $user->getUID()
),

View File

@ -1,110 +0,0 @@
<?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 OCA\Files_Sharing\Propagation;
use OC\Files\Cache\ChangePropagator;
use OC\Files\Filesystem;
use OC\Files\View;
use OCA\Files_Sharing\SharedMount;
/**
* Watch for changes made in a shared mount and propagate the changes to the share owner
*/
class ChangeWatcher {
/**
* The user view for the logged in user
*
* @var \OC\Files\View
*/
private $baseView;
/**
* @var RecipientPropagator
*/
private $recipientPropagator;
/**
* @param \OC\Files\View $baseView the view for the logged in user
* @param RecipientPropagator $recipientPropagator
*/
public function __construct(View $baseView, RecipientPropagator $recipientPropagator) {
$this->baseView = $baseView;
$this->recipientPropagator = $recipientPropagator;
}
public function writeHook($params) {
$path = $params['path'];
$fullPath = $this->baseView->getAbsolutePath($path);
$mount = $this->baseView->getMount($path);
if ($mount instanceof SharedMount) {
$this->propagateForOwner($mount->getShare(), $mount->getInternalPath($fullPath), $mount->getOwnerPropagator());
}
$info = $this->baseView->getFileInfo($path);
if ($info) {
// trigger propagation if the subject of the write hook is shared.
// if a parent folder of $path is shared the propagation will be triggered from the change propagator hooks
$this->recipientPropagator->propagateById($info->getId());
}
}
public function renameHook($params) {
$path1 = $params['oldpath'];
$path2 = $params['newpath'];
$fullPath1 = $this->baseView->getAbsolutePath($path1);
$fullPath2 = $this->baseView->getAbsolutePath($path2);
$mount1 = $this->baseView->getMount($path1);
$mount2 = $this->baseView->getMount($path2);
if ($mount1 instanceof SharedMount and $mount1->getInternalPath($fullPath1) !== '') {
$this->propagateForOwner($mount1->getShare(), $mount1->getInternalPath($fullPath1), $mount1->getOwnerPropagator());
}
if ($mount1 !== $mount2 and $mount2 instanceof SharedMount and $mount2->getInternalPath($fullPath2) !== '') {
$this->propagateForOwner($mount2->getShare(), $mount2->getInternalPath($fullPath2), $mount2->getOwnerPropagator());
}
}
/**
* @param array $share
* @param string $internalPath
* @param \OC\Files\Cache\ChangePropagator $propagator
*/
private function propagateForOwner($share, $internalPath, ChangePropagator $propagator) {
// note that we have already set up the filesystem for the owner when mounting the share
$view = new View('/' . $share['uid_owner'] . '/files');
$shareRootPath = $view->getPath($share['item_source']);
if (!is_null($shareRootPath)) {
$path = $shareRootPath . '/' . $internalPath;
$path = Filesystem::normalizePath($path);
$propagator->addChange($path);
$propagator->propagateChanges();
}
}
public function permissionsHook($params) {
$share = $params['share'];
if ($share['item_type'] === 'file' || $share['item_type'] === 'folder') {
$this->recipientPropagator->markDirty($share, microtime(true));
}
}
}

View File

@ -1,133 +0,0 @@
<?php
/**
* @author Vincent Petry <pvince81@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 OCA\Files_Sharing\Propagation;
use OC\Files\Filesystem;
use OC\Files\View;
use OCP\IConfig;
use OCP\IUserSession;
use OCP\IGroup;
use OCP\IUser;
use OCP\IGroupManager;
use OCA\Files_Sharing\Propagation\PropagationManager;
/**
* Propagate changes on group changes
*/
class GroupPropagationManager {
/**
* @var \OCP\IUserSession
*/
private $userSession;
/**
* @var \OCP\IGroupManager
*/
private $groupManager;
/**
* @var PropagationManager
*/
private $propagationManager;
/**
* Items shared with a given user.
* Key is user id and value is an array of shares.
*
* @var array
*/
private $userShares = [];
public function __construct(IUserSession $userSession, IGroupManager $groupManager, PropagationManager $propagationManager) {
$this->userSession = $userSession;
$this->groupManager = $groupManager;
$this->propagationManager = $propagationManager;
}
public function onPreProcessUser(IGroup $group, IUser $targetUser) {
$this->userShares[$targetUser->getUID()] = $this->getUserShares($targetUser->getUID());
}
public function onPostAddUser(IGroup $group, IUser $targetUser) {
$targetUserId = $targetUser->getUID();
$sharesAfter = $this->getUserShares($targetUserId);
$this->propagateSharesDiff($targetUserId, $sharesAfter, $this->userShares[$targetUserId]);
unset($this->userShares[$targetUserId]);
}
public function onPostRemoveUser(IGroup $group, IUser $targetUser) {
$targetUserId = $targetUser->getUID();
$sharesAfter = $this->getUserShares($targetUserId);
$this->propagateSharesDiff($targetUserId, $this->userShares[$targetUserId], $sharesAfter);
unset($this->userShares[$targetUserId]);
}
private function getUserShares($targetUserId) {
return \OCP\Share::getItemsSharedWithUser('file', $targetUserId);
}
/**
* Propagate etag for the shares that are in $shares1 but not in $shares2.
*
* @param string $targetUserId user id for which to propagate shares
* @param array $shares1
* @param array $shares2
*/
private function propagateSharesDiff($targetUserId, $shares1, $shares2) {
$sharesToPropagate = array_udiff(
$shares1,
$shares2,
function($share1, $share2) {
return ($share2['id'] - $share1['id']);
}
);
\OC\Files\Filesystem::initMountPoints($targetUserId);
$this->propagationManager->propagateSharesToUser($sharesToPropagate, $targetUserId);
}
/**
* To be called from setupFS trough a hook
*
* Sets up listening to changes made to shares owned by the current user
*/
public function globalSetup() {
$user = $this->userSession->getUser();
if (!$user) {
return;
}
$this->groupManager->listen('\OC\Group', 'preAddUser', [$this, 'onPreProcessUser']);
$this->groupManager->listen('\OC\Group', 'postAddUser', [$this, 'onPostAddUser']);
$this->groupManager->listen('\OC\Group', 'preRemoveUser', [$this, 'onPreProcessUser']);
$this->groupManager->listen('\OC\Group', 'postRemoveUser', [$this, 'onPostRemoveUser']);
}
public function tearDown() {
$this->groupManager->removeListener('\OC\Group', 'preAddUser', [$this, 'onPreProcessUser']);
$this->groupManager->removeListener('\OC\Group', 'postAddUser', [$this, 'onPostAddUser']);
$this->groupManager->removeListener('\OC\Group', 'preRemoveUser', [$this, 'onPreProcessUser']);
$this->groupManager->removeListener('\OC\Group', 'postRemoveUser', [$this, 'onPostRemoveUser']);
}
}

View File

@ -1,144 +0,0 @@
<?php
/**
* @author Robin Appelman <icewind@owncloud.com>
* @author Vincent Petry <pvince81@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 OCA\Files_Sharing\Propagation;
use OC\Files\Filesystem;
use OC\Files\View;
use OCP\IConfig;
use OCP\IUserSession;
use OCP\Util;
/**
* Keep track of all change and share propagators by owner
*/
class PropagationManager {
/**
* @var \OCP\IUserSession
*/
private $userSession;
/**
* @var \OCP\IConfig
*/
private $config;
/**
* Change propagators for share owner
*
* @var \OC\Files\Cache\ChangePropagator[]
*/
private $changePropagators = [];
/**
* Recipient propagators
*
* @var \OCA\Files_Sharing\Propagation\RecipientPropagator[]
*/
private $sharePropagators = [];
public function __construct(IUserSession $userSession, IConfig $config) {
$this->userSession = $userSession;
$this->config = $config;
}
/**
* @param string $user
* @return \OC\Files\Cache\ChangePropagator
*/
public function getChangePropagator($user) {
$activeUser = $this->userSession->getUser();
// for the local user we want to propagator from the active view, not any cached one
if ($activeUser && $activeUser->getUID() === $user && Filesystem::getView() instanceof View) {
// it's important that we take the existing propagator here to make sure we can listen to external changes
$this->changePropagators[$user] = Filesystem::getView()->getUpdater()->getPropagator();
}
if (isset($this->changePropagators[$user])) {
return $this->changePropagators[$user];
}
$view = new View('/' . $user . '/files');
$this->changePropagators[$user] = $view->getUpdater()->getPropagator();
return $this->changePropagators[$user];
}
/**
* Propagates etag changes for the given shares to the given user
*
* @param array array of shares for which to trigger etag change
* @param string $user
*/
public function propagateSharesToUser($shares, $user) {
$changePropagator = $this->getChangePropagator($user);
foreach ($shares as $share) {
$changePropagator->addChange($share['file_target']);
}
$time = microtime(true);
$changePropagator->propagateChanges(floor($time));
}
/**
* @param string $user
* @return \OCA\Files_Sharing\Propagation\RecipientPropagator
*/
public function getSharePropagator($user) {
if (isset($this->sharePropagators[$user])) {
return $this->sharePropagators[$user];
}
$this->sharePropagators[$user] = new RecipientPropagator($user, $this->getChangePropagator($user), $this->config, $this);
return $this->sharePropagators[$user];
}
/**
* Attach the recipient propagator for $user to the change propagator of a share owner to mark shares as dirty when the owner makes a change to a share
*
* @param string $shareOwner
* @param string $user
*/
public function listenToOwnerChanges($shareOwner, $user) {
$sharePropagator = $this->getSharePropagator($user);
$ownerPropagator = $this->getChangePropagator($shareOwner);
$sharePropagator->attachToPropagator($ownerPropagator, $shareOwner);
}
/**
* To be called from setupFS trough a hook
*
* Sets up listening to changes made to shares owned by the current user
*/
public function globalSetup() {
$user = $this->userSession->getUser();
if (!$user) {
return;
}
$recipientPropagator = $this->getSharePropagator($user->getUID());
$watcher = new ChangeWatcher(Filesystem::getView(), $recipientPropagator);
// for marking shares owned by the active user as dirty when a file inside them changes
$this->listenToOwnerChanges($user->getUID(), $user->getUID());
Util::connectHook('OC_Filesystem', 'post_write', $watcher, 'writeHook');
Util::connectHook('OC_Filesystem', 'post_delete', $watcher, 'writeHook');
Util::connectHook('OC_Filesystem', 'post_rename', $watcher, 'renameHook');
Util::connectHook('OCP\Share', 'post_update_permissions', $watcher, 'permissionsHook');
}
}

View File

@ -1,164 +0,0 @@
<?php
/**
* @author Lukas Reschke <lukas@owncloud.com>
* @author Morris Jobke <hey@morrisjobke.de>
* @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 OCA\Files_Sharing\Propagation;
use OC\Files\Cache\ChangePropagator;
use OC\Files\View;
use OC\Share\Share;
use OCP\Files\NotFoundException;
/**
* Propagate etags for share recipients
*/
class RecipientPropagator {
/**
* @var string
*/
protected $userId;
/**
* @var \OC\Files\Cache\ChangePropagator
*/
protected $changePropagator;
/**
* @var \OCP\IConfig
*/
protected $config;
/**
* @var PropagationManager
*/
private $manager;
/**
* @param string $userId current user, must match the propagator's
* user
* @param \OC\Files\Cache\ChangePropagator $changePropagator change propagator
* initialized with a view for $user
* @param \OCP\IConfig $config
* @param PropagationManager $manager
*/
public function __construct($userId, $changePropagator, $config, PropagationManager $manager) {
$this->userId = $userId;
$this->changePropagator = $changePropagator;
$this->config = $config;
$this->manager = $manager;
}
/**
* Propagate the etag changes for all shares marked as dirty and mark the shares as clean
*
* @param array $shares the shares for the users
* @param int $time
*/
public function propagateDirtyMountPoints(array $shares, $time = null) {
if ($time === null) {
$time = microtime(true);
}
$dirtyShares = $this->getDirtyShares($shares);
foreach ($dirtyShares as $share) {
$this->changePropagator->addChange($share['file_target']);
}
if (count($dirtyShares)) {
$this->config->setUserValue($this->userId, 'files_sharing', 'last_propagate', $time);
$this->changePropagator->propagateChanges(floor($time));
}
}
/**
* Get all shares we need to update the etag for
*
* @param array $shares the shares for the users
* @return string[]
*/
protected function getDirtyShares($shares) {
$dirty = [];
$userTime = $this->config->getUserValue($this->userId, 'files_sharing', 'last_propagate', 0);
foreach ($shares as $share) {
$updateTime = $this->config->getAppValue('files_sharing', $share['id'], 0);
if ($updateTime >= $userTime) {
$dirty[] = $share;
}
}
return $dirty;
}
/**
* @param array $share
* @param float $time
*/
public function markDirty($share, $time = null) {
if ($time === null) {
$time = microtime(true);
}
$this->config->setAppValue('files_sharing', $share['id'], $time);
}
/**
* Listen on the propagator for updates made to shares owned by a user
*
* @param \OC\Files\Cache\ChangePropagator $propagator
* @param string $owner
*/
public function attachToPropagator(ChangePropagator $propagator, $owner) {
$propagator->listen('\OC\Files', 'propagate', function ($path, $entry) use ($owner) {
$this->propagateById($entry['fileid']);
});
}
protected $propagatingIds = [];
/**
* @param int $id
*/
public function propagateById($id) {
if (isset($this->propagatingIds[$id])) {
return;
}
$this->propagatingIds[$id] = true;
$shares = Share::getAllSharesForFileId($id);
foreach ($shares as $share) {
// propagate down the share tree
$this->markDirty($share, microtime(true));
// propagate up the share tree
if ($share['share_with'] === $this->userId) {
$user = $share['uid_owner'];
$view = new View('/' . $user . '/files');
try {
$path = $view->getPath($share['file_source']);
} catch (NotFoundException $e) {
$path = null;
}
$watcher = new ChangeWatcher($view, $this->manager->getSharePropagator($user));
$watcher->writeHook(['path' => $path]);
}
}
unset($this->propagatingIds[$id]);
}
}

View File

@ -38,11 +38,6 @@ class SharedMount extends MountPoint implements MoveableMount {
*/
protected $storage = null;
/**
* @var \OC\Files\Cache\ChangePropagator
*/
protected $ownerPropagator;
/**
* @var \OC\Files\View
*/
@ -54,8 +49,6 @@ class SharedMount extends MountPoint implements MoveableMount {
private $user;
public function __construct($storage, $mountpoint, $arguments = null, $loader = null) {
// first update the mount point before creating the parent
$this->ownerPropagator = $arguments['propagator'];
$this->user = $arguments['user'];
$this->recipientView = new View('/' . $this->user . '/files');
$newMountPoint = $this->verifyMountPoint($arguments['share']);
@ -201,11 +194,4 @@ class SharedMount extends MountPoint implements MoveableMount {
public function getShare() {
return $this->getStorage()->getShare();
}
/**
* @return \OC\Files\Cache\ChangePropagator
*/
public function getOwnerPropagator() {
return $this->ownerPropagator;
}
}

View File

@ -0,0 +1,43 @@
<?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 OCA\Files_Sharing;
use OC\Files\Cache\Propagator;
class SharedPropagator extends Propagator {
/**
* @var \OC\Files\Storage\Shared
*/
protected $storage;
/**
* @param string $internalPath
* @param int $time
* @return array[] all propagated entries
*/
public function propagateChange($internalPath, $time) {
$source = $this->storage->getSourcePath($internalPath);
/** @var \OC\Files\Storage\Storage $storage */
list($storage, $sourceInternalPath) = \OC\Files\Filesystem::resolvePath($source);
return $storage->getPropagator()->propagateChange($sourceInternalPath, $time);
}
}

View File

@ -50,11 +50,6 @@ class Shared extends \OC\Files\Storage\Common implements ISharedStorage {
*/
private $ownerView;
/**
* @var \OCA\Files_Sharing\Propagation\PropagationManager
*/
private $propagationManager;
/**
* @var string
*/
@ -65,7 +60,6 @@ class Shared extends \OC\Files\Storage\Common implements ISharedStorage {
public function __construct($arguments) {
$this->share = $arguments['share'];
$this->ownerView = $arguments['ownerView'];
$this->propagationManager = $arguments['propagationManager'];
$this->user = $arguments['user'];
}
@ -75,9 +69,6 @@ class Shared extends \OC\Files\Storage\Common implements ISharedStorage {
}
$this->initialized = true;
Filesystem::initMountPoints($this->share['uid_owner']);
// for updating our etags when changes are made to the share from the owners side (probably indirectly by us trough another share)
$this->propagationManager->listenToOwnerChanges($this->share['uid_owner'], $this->user);
}
/**
@ -571,6 +562,13 @@ class Shared extends \OC\Files\Storage\Common implements ISharedStorage {
return new \OC\Files\Cache\Shared_Watcher($storage);
}
public function getPropagator($storage = null) {
if (!$storage) {
$storage = $this;
}
return new \OCA\Files_Sharing\SharedPropagator($storage);
}
public function getOwner($path) {
if ($path == '') {
$path = $this->getMountPoint();

View File

@ -1,173 +0,0 @@
<?php
/**
* @author Vincent Petry <pvince81@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 OCA\Files_sharing\Tests;
use OC\Files\View;
use OCP\IGroupManager;
use OCP\IGroup;
use OCP\IUser;
use OCP\Share;
use OCA\Files_Sharing\Propagation\GroupPropagationManager;
use OCA\Files_Sharing\Propagation\PropagationManager;
class GroupPropagationManagerTest extends TestCase {
/**
* @var GroupPropagationManager
*/
private $groupPropagationManager;
/**
* @var IGroupManager
*/
private $groupManager;
/**
* @var PropagationManager
*/
private $propagationManager;
/**
* @var IGroup
*/
private $recipientGroup;
/**
* @var IUser
*/
private $recipientUser;
/**
* @var array
*/
private $fileInfo;
protected function setUp() {
parent::setUp();
$user = $this->getMockBuilder('\OCP\IUser')
->disableOriginalConstructor()
->getMock();
$user->method('getUID')->willReturn(self::TEST_FILES_SHARING_API_USER1);
$userSession = $this->getMockBuilder('\OCP\IUserSession')
->disableOriginalConstructor()
->getMock();
$userSession->method('getUser')->willReturn(selF::TEST_FILES_SHARING_API_USER1);
$this->propagationManager = $this->getMockBuilder('OCA\Files_Sharing\Propagation\PropagationManager')
->disableOriginalConstructor()
->getMock();
$this->groupManager = \OC::$server->getGroupManager();
$this->groupPropagationManager = new GroupPropagationManager(
$userSession,
$this->groupManager,
$this->propagationManager
);
$this->groupPropagationManager->globalSetup();
// since the sharing code is not mockable, we have to create a real folder
$this->loginAsUser(self::TEST_FILES_SHARING_API_USER1);
$view1 = new View('/' . self::TEST_FILES_SHARING_API_USER1 . '/files');
$view1->mkdir('/folder');
$this->fileInfo = $view1->getFileInfo('/folder');
$this->recipientGroup = $this->groupManager->get(self::TEST_FILES_SHARING_API_GROUP1);
$this->recipientUser = \OC::$server->getUserManager()->get(self::TEST_FILES_SHARING_API_USER3);
Share::shareItem(
'folder',
$this->fileInfo['fileid'],
Share::SHARE_TYPE_GROUP,
$this->recipientGroup->getGID(),
\OCP\Constants::PERMISSION_READ
);
$this->loginAsUser($this->recipientUser->getUID());
}
protected function tearDown() {
$this->groupPropagationManager->tearDown();
$this->recipientGroup->removeUser($this->recipientUser);
parent::tearDown();
}
public function testPropagateWhenAddedToGroup() {
$this->propagationManager->expects($this->once())
->method('propagateSharesToUser')
->with($this->callback(function($shares) {
if (count($shares) !== 1) {
return false;
}
$share = array_values($shares)[0];
return $share['file_source'] === $this->fileInfo['fileid'] &&
$share['share_with'] === $this->recipientGroup->getGID() &&
$share['file_target'] === '/folder';
}), $this->recipientUser->getUID());
$this->recipientGroup->addUser($this->recipientUser);
}
public function testPropagateWhenRemovedFromGroup() {
$this->recipientGroup->addUser($this->recipientUser);
$this->propagationManager->expects($this->once())
->method('propagateSharesToUser')
->with($this->callback(function($shares) {
if (count($shares) !== 1) {
return false;
}
$share = array_values($shares)[0];
return $share['file_source'] === $this->fileInfo['fileid'] &&
$share['share_with'] === $this->recipientGroup->getGID() &&
$share['file_target'] === '/folder';
}), $this->recipientUser->getUID());
$this->recipientGroup->removeUser($this->recipientUser);
}
public function testPropagateWhenRemovedFromGroupWithSubdirTarget() {
$this->recipientGroup->addUser($this->recipientUser);
// relogin to refresh mount points
$this->loginAsUser($this->recipientUser->getUID());
$recipientView = new View('/' . $this->recipientUser->getUID() . '/files');
$this->assertTrue($recipientView->mkdir('sub'));
$this->assertTrue($recipientView->rename('folder', 'sub/folder'));
$this->propagationManager->expects($this->once())
->method('propagateSharesToUser')
->with($this->callback(function($shares) {
if (count($shares) !== 1) {
return false;
}
$share = array_values($shares)[0];
return $share['file_source'] === $this->fileInfo['fileid'] &&
$share['share_with'] === $this->recipientGroup->getGID() &&
$share['file_target'] === '/sub/folder';
}), $this->recipientUser->getUID());
$this->recipientGroup->removeUser($this->recipientUser);
}
}

View File

@ -61,7 +61,6 @@ abstract class TestCase extends \Test\TestCase {
$application = new Application();
$application->registerMountProviders();
$application->setupPropagation();
// reset backend
\OC_User::clearBackends();

View File

@ -160,6 +160,7 @@ class Cache {
} else {
//fix types
$data['fileid'] = (int)$data['fileid'];
$data['parent'] = (int)$data['parent'];
$data['size'] = 0 + $data['size'];
$data['mtime'] = (int)$data['mtime'];
$data['storage_mtime'] = (int)$data['storage_mtime'];
@ -391,14 +392,19 @@ class Cache {
if ($file === '') {
return -1;
} else {
$parent = dirname($file);
if ($parent === '.') {
$parent = '';
}
return $this->getId($parent);
$parent = $this->getParentPath($file);
return (int) $this->getId($parent);
}
}
private function getParentPath($path) {
$parent = dirname($path);
if ($parent === '.') {
$parent = '';
}
return $parent;
}
/**
* check if a file is available in the cache
*

View File

@ -22,6 +22,7 @@
namespace OC\Files\Cache;
use OC\Files\Filesystem;
use OC\Hooks\BasicEmitter;
/**
@ -61,23 +62,30 @@ class ChangePropagator extends BasicEmitter {
* @param int $time (optional) the mtime to set for the folders, if not set the current time is used
*/
public function propagateChanges($time = null) {
$parents = $this->getAllParents();
$this->changedFiles = array();
$changes = $this->getChanges();
$this->changedFiles = [];
if (!$time) {
$time = time();
}
foreach ($parents as $parent) {
foreach ($changes as $change) {
/**
* @var \OC\Files\Storage\Storage $storage
* @var string $internalPath
*/
list($storage, $internalPath) = $this->view->resolvePath($parent);
$absolutePath = $this->view->getAbsolutePath($change);
$mount = $this->view->getMount($change);
$storage = $mount->getStorage();
$internalPath = $mount->getInternalPath($absolutePath);
if ($storage) {
$cache = $storage->getCache();
$entry = $cache->get($internalPath);
$cache->update($entry['fileid'], array('mtime' => max($time, $entry['mtime']), 'etag' => $storage->getETag($internalPath)));
$this->emit('\OC\Files', 'propagate', [$parent, $entry]);
$propagator = $storage->getPropagator();
$propagatedEntries = $propagator->propagateChange($internalPath, $time);
foreach ($propagatedEntries as $entry) {
$absolutePath = Filesystem::normalizePath($mount->getMountPoint() . '/' . $entry['path']);
$relativePath = $this->view->getRelativePath($absolutePath);
$this->emit('\OC\Files', 'propagate', [$relativePath, $entry]);
}
}
}
}

66
lib/private/files/cache/propagator.php vendored Normal file
View File

@ -0,0 +1,66 @@
<?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\Cache;
/**
* Propagate etags and mtimes within the storage
*/
class Propagator {
/**
* @var \OC\Files\Storage\Storage
*/
protected $storage;
/**
* @param \OC\Files\Storage\Storage $storage
*/
public function __construct(\OC\Files\Storage\Storage $storage) {
$this->storage = $storage;
}
/**
* @param string $internalPath
* @param int $time
* @return array[] all propagated entries
*/
public function propagateChange($internalPath, $time) {
$cache = $this->storage->getCache($internalPath);
$parentId = $cache->getParentId($internalPath);
$propagatedEntries = [];
while ($parentId !== -1) {
$entry = $cache->get($parentId);
$propagatedEntries[] = $entry;
if (!$entry) {
return $propagatedEntries;
}
$mtime = max($time, $entry['mtime']);
$cache->update($parentId, ['mtime' => $mtime, 'etag' => $this->storage->getETag($entry['path'])]);
$parentId = $entry['parent'];
}
return $propagatedEntries;
}
}

View File

@ -61,6 +61,11 @@ class FileInfo implements \OCP\Files\FileInfo, \ArrayAccess {
*/
private $owner;
/**
* @var string[]
*/
private $childEtags = [];
/**
* @param string|boolean $path
* @param Storage\Storage $storage
@ -93,6 +98,8 @@ class FileInfo implements \OCP\Files\FileInfo, \ArrayAccess {
public function offsetGet($offset) {
if ($offset === 'type') {
return $this->getType();
} else if ($offset === 'etag') {
return $this->getEtag();
} elseif (isset($this->data[$offset])) {
return $this->data[$offset];
} else {
@ -153,7 +160,12 @@ class FileInfo implements \OCP\Files\FileInfo, \ArrayAccess {
* @return string
*/
public function getEtag() {
return $this->data['etag'];
if (count($this->childEtags) > 0) {
$combinedEtag = $this->data['etag'] . '::' . implode('::', $this->childEtags);
return md5($combinedEtag);
} else {
return $this->data['etag'];
}
}
/**
@ -292,8 +304,19 @@ class FileInfo implements \OCP\Files\FileInfo, \ArrayAccess {
* Sets the size, etag and size to for cross-storage childs
*
* @param array $data cache entry for the child
* @param string $entryPath full path of the child entry
*/
public function addSubEntry($data) {
public function addSubEntry($data, $entryPath) {
$this->data['size'] += isset($data['size']) ? $data['size'] : 0;
if (isset($data['mtime'])) {
$this->data['mtime'] = max($this->data['mtime'], $data['mtime']);
}
if (isset($data['etag'])) {
// prefix the etag with the relative path of the subentry to propagate etag on mount moves
$relativeEntryPath = substr($entryPath, strlen($this->getPath()));
// attach the permissions to propagate etag on permision changes of submounts
$permissions = isset($data['permissions']) ? $data['permissions'] : 0;
$this->childEtags[] = $relativeEntryPath . '/' . $data['etag'] . $permissions;
}
}
}

View File

@ -37,6 +37,7 @@
namespace OC\Files\Storage;
use OC\Files\Cache\Cache;
use OC\Files\Cache\Propagator;
use OC\Files\Cache\Scanner;
use OC\Files\Filesystem;
use OC\Files\Cache\Watcher;
@ -64,6 +65,7 @@ abstract class Common implements Storage {
protected $cache;
protected $scanner;
protected $watcher;
protected $propagator;
protected $storageCache;
protected $mountOptions = [];
@ -345,6 +347,22 @@ abstract class Common implements Storage {
return $this->watcher;
}
/**
* get a propagator instance for the cache
*
* @param \OC\Files\Storage\Storage (optional) the storage to pass to the watcher
* @return \OC\Files\Cache\Propagator
*/
public function getPropagator($storage = null) {
if (!$storage) {
$storage = $this;
}
if (!isset($this->propagator)) {
$this->propagator = new Propagator($storage);
}
return $this->propagator;
}
public function getStorageCache($storage = null) {
if (!$storage) {
$storage = $this;

View File

@ -67,6 +67,14 @@ interface Storage extends \OCP\Files\Storage {
*/
public function getWatcher($path = '', $storage = null);
/**
* get a propagator instance for the cache
*
* @param \OC\Files\Storage\Storage (optional) the storage to pass to the watcher
* @return \OC\Files\Cache\Propagator
*/
public function getPropagator($storage = null);
/**
* @return \OC\Files\Cache\Storage
*/

View File

@ -430,6 +430,13 @@ class Wrapper implements \OC\Files\Storage\Storage {
return $this->storage->getWatcher($path, $storage);
}
public function getPropagator($storage = null) {
if (!$storage) {
$storage = $this;
}
return $this->storage->getPropagator($storage);
}
/**
* @return \OC\Files\Cache\Storage
*/

View File

@ -707,10 +707,6 @@ class View {
} else if ($result) {
if ($internalPath1 !== '') { // dont do a cache update for moved mounts
$this->updater->rename($path1, $path2);
} else { // only do etag propagation
$this->getUpdater()->getPropagator()->addChange($path1);
$this->getUpdater()->getPropagator()->addChange($path2);
$this->getUpdater()->getPropagator()->propagateChanges();
}
}
@ -1179,6 +1175,11 @@ class View {
}
/**
* Get file info from cache
*
* If the file is not in cached it will be scanned
* If the file has changed on storage the cache will be updated
*
* @param \OC\Files\Storage\Storage $storage
* @param string $internalPath
* @param string $relativePath
@ -1266,7 +1267,7 @@ class View {
}
$subCache = $subStorage->getCache('');
$rootEntry = $subCache->get('');
$info->addSubEntry($rootEntry);
$info->addSubEntry($rootEntry, $mount->getMountPoint());
}
}
}
@ -1357,7 +1358,7 @@ class View {
$entryName = substr($relativePath, 0, $pos);
foreach ($files as &$entry) {
if ($entry->getName() === $entryName) {
$entry->addSubEntry($rootEntry);
$entry->addSubEntry($rootEntry, $mountPoint);
}
}
} else { //mountpoint in this folder, add an entry for it

View File

@ -94,4 +94,37 @@ class ChangePropagator extends \Test\TestCase {
$this->assertEquals(100, $time - $cache->get('foo')['mtime']);
$this->assertEquals(100, $time - $cache->get('foo/bar')['mtime']);
}
public function testPropagateCrossStorage() {
$storage = new Temporary();
$this->view->mkdir('/foo');
Filesystem::mount($storage, [], $this->view->getAbsolutePath('/foo/submount'));
$this->view->mkdir('/foo/submount/bar');
$this->view->file_put_contents('/foo/submount/bar/sad.txt', 'qwerty');
$oldInfo1 = $this->view->getFileInfo('/');
$oldInfo2 = $this->view->getFileInfo('/foo');
$oldInfo3 = $this->view->getFileInfo('/foo/submount');
$oldInfo4 = $this->view->getFileInfo('/foo/submount/bar');
$time = time() + 50;
$this->propagator->addChange('/foo/submount/bar/sad.txt');
$this->propagator->propagateChanges($time);
$newInfo1 = $this->view->getFileInfo('/');
$newInfo2 = $this->view->getFileInfo('/foo');
$newInfo3 = $this->view->getFileInfo('/foo/submount');
$newInfo4 = $this->view->getFileInfo('/foo/submount/bar');
$this->assertEquals($newInfo1->getMTime(), $time);
$this->assertEquals($newInfo2->getMTime(), $time);
$this->assertEquals($newInfo3->getMTime(), $time);
$this->assertEquals($newInfo4->getMTime(), $time);
$this->assertNotSame($oldInfo1->getEtag(), $newInfo1->getEtag());
$this->assertNotSame($oldInfo2->getEtag(), $newInfo2->getEtag());
$this->assertNotSame($oldInfo3->getEtag(), $newInfo3->getEtag());
$this->assertNotSame($oldInfo4->getEtag(), $newInfo3->getEtag());
}
}