Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Store avatars outside of the home #26124

Merged
merged 10 commits into from
Oct 7, 2016
40 changes: 31 additions & 9 deletions lib/private/AvatarManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -84,15 +84,37 @@ public function getAvatar($userId) {
throw new \Exception('user does not exist');
}

/*
* Fix for #22119
* Basically we do not want to copy the skeleton folder
*/
\OC\Files\Filesystem::initMountPoints($userId);
$dir = '/' . $userId;
/** @var Folder $folder */
$folder = $this->rootFolder->get($dir);
$avatarsFolder = $this->getAvatarFolder($userId);
return new Avatar($avatarsFolder, $this->l, $user, $this->logger);
}

private function getFolder(Folder $folder, $path) {
try {
return $folder->get($path);
} catch (NotFoundException $e) {
return $folder->newFolder($path);
}
}

private function buildAvatarPath($userId) {
$avatar = substr_replace(substr_replace(md5($userId), '/', 4, 0), '/', 2, 0);
return explode('/', $avatar);
}

return new Avatar($folder, $this->l, $user, $this->logger);
/**
* Returns the avatar folder for the given user
*
* @param $userId user id
* @return Folder|\OCP\Files\Node
*
* @internal
*/
public function getAvatarFolder($userId) {
$avatarsFolder = $this->getFolder($this->rootFolder, 'avatars');
$parts = $this->buildAvatarPath($userId);
foreach ($parts as $part) {
$avatarsFolder = $this->getFolder($avatarsFolder, $part);
}
return $avatarsFolder;
}
}
3 changes: 3 additions & 0 deletions lib/private/Encryption/Util.php
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,9 @@ public function __construct(
$this->config = $config;

$this->excludedPaths[] = 'files_encryption';
$this->excludedPaths[] = 'avatars';
$this->excludedPaths[] = 'avatar.png';
$this->excludedPaths[] = 'avatar.jpg';
}

/**
Expand Down
2 changes: 1 addition & 1 deletion lib/private/Files/Node/LazyRoot.php
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ public function unMount($mount) {
* @inheritDoc
*/
public function get($path) {
$this->__call(__FUNCTION__, func_get_args());
return $this->__call(__FUNCTION__, func_get_args());
}

/**
Expand Down
10 changes: 10 additions & 0 deletions lib/private/Repair.php
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
use OCP\Migration\IRepairStep;
use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Component\EventDispatcher\GenericEvent;
use OC\Repair\MoveAvatarOutsideHome;

class Repair implements IOutput{
/* @var IRepairStep[] */
Expand Down Expand Up @@ -139,6 +140,15 @@ public static function getRepairSteps() {
new SharePropagation(\OC::$server->getConfig()),
new RemoveOldShares(\OC::$server->getDatabaseConnection()),
new AvatarPermissions(\OC::$server->getDatabaseConnection()),
new MoveAvatarOutsideHome(
\OC::$server->getConfig(),
\OC::$server->getDatabaseConnection(),
\OC::$server->getUserManager(),
\OC::$server->getAvatarManager(),
\OC::$server->getLazyRootFolder(),
\OC::$server->getL10N('core'),
\OC::$server->getLogger()
),
new RemoveRootShares(\OC::$server->getDatabaseConnection(), \OC::$server->getUserManager(), \OC::$server->getLazyRootFolder()),
new RepairUnmergedShares(
\OC::$server->getConfig(),
Expand Down
163 changes: 163 additions & 0 deletions lib/private/Repair/MoveAvatarOutsideHome.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
<?php
/**
* @author Vincent Petry <pvince81@owncloud.com>
*
* @copyright Copyright (c) 2016, 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\Repair;

use OCP\Files\IRootFolder;
use OCP\IDBConnection;
use OCP\IL10N;
use OCP\ILogger;
use OCP\IUserManager;
use OCP\Migration\IOutput;
use OCP\Migration\IRepairStep;
use OCP\IUser;
use OC\Avatar;
use OCP\IConfig;
use OCP\IAvatarManager;
use OCP\Files\NotFoundException;

/**
* Move avatars outside of their homes to the new location
*
* @package OC\Repair
*/
class MoveAvatarOutsideHome implements IRepairStep {
/** @var \OCP\IConfig */
protected $config;

/** @var IDBConnection */
private $connection;

/** @var IUserManager */
private $userManager;

/** @var IAvatarManager */
private $avatarManager;

/** @var IRootFolder */
private $rootFolder;

/** @var \OCP\ILogger */
private $logger;

/** @var \OCP\IL10N */
private $l;

/**
* @param IConfig $config config
* @param IDBConnection $connection database connection
* @param IUserManager $userManager user manager
* @param IAvatarManager $avatarManager
* @param IRootFolder $rootFolder
* @param IL10N $l10n
* @param ILogger $logger
*/
public function __construct(
IConfig $config,
IDBConnection $connection,
IUserManager $userManager,
IAvatarManager $avatarManager,
IRootFolder $rootFolder,
IL10N $l10n,
ILogger $logger
) {
$this->config = $config;
$this->connection = $connection;
$this->userManager = $userManager;
$this->avatarManager = $avatarManager;
$this->rootFolder = $rootFolder;
$this->l = $l10n;
$this->logger = $logger;
}

/**
* @return string
*/
public function getName() {
return 'Move user avatars outside the homes to the new location';
}

/**
* Move avatars outside of their homes
*
* @param IOutput $out
* @param IUser $user
*/
private function moveAvatars(IOutput $out, IUser $user) {
$userId = $user->getUID();

\OC\Files\Filesystem::initMountPoints($userId);

// call get instead of getUserFolder to avoid needless skeleton copy
try {
$oldAvatarUserFolder = $this->rootFolder->get('/' . $userId);
$oldAvatar = new Avatar($oldAvatarUserFolder, $this->l, $user, $this->logger);
if ($oldAvatar->exists()) {
$newAvatarsUserFolder = $this->avatarManager->getAvatarFolder($userId);

// get original file
$oldAvatarFile = $oldAvatar->getFile(-1);
$oldAvatarFile->move($newAvatarsUserFolder->getPath() . '/' . $oldAvatarFile->getName());
$oldAvatar->remove();
}
} catch (NotFoundException $e) {
// not all users have a home, ignore
}

\OC_Util::tearDownFS();
}

/**
* Count all the users
*
* @return int
*/
private function countUsers() {
$allCount = $this->userManager->countUsers();

$totalCount = 0;
foreach ($allCount as $backend => $count) {
$totalCount += $count;
}

return $totalCount;
}

/**
* @param IOutput $output
*/
public function run(IOutput $output) {
$ocVersionFromBeforeUpdate = $this->config->getSystemValue('version', '0.0.0');
if (version_compare($ocVersionFromBeforeUpdate, '9.2.0.2', '<')) {
$function = function(IUser $user) use ($output) {
$this->moveAvatars($output, $user);
$output->advance();
};

$userCount = $this->countUsers();
$output->startProgress($userCount);

$this->userManager->callForAllUsers($function);

$output->finishProgress();
}
}
}

60 changes: 46 additions & 14 deletions tests/lib/AvatarManagerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,32 +22,43 @@
namespace Test;

use OC\AvatarManager;
use Test\Traits\UserTrait;
use Test\Traits\MountProviderTrait;
use OCP\Files\Folder;
use OCP\Files\IRootFolder;
use OCP\IL10N;
use OCP\ILogger;
use OCP\IUser;
use OCP\IUserManager;

/**
* Class AvatarManagerTest
* @group DB
*/
class AvatarManagerTest extends \Test\TestCase {
use UserTrait;
use MountProviderTrait;
class AvatarManagerTest extends TestCase {

/** @var AvatarManager */
/** @var AvatarManager | \PHPUnit_Framework_MockObject_MockObject */
private $avatarManager;

/** @var \OC\Files\Storage\Temporary */
private $storage;
/** @var IUserManager | \PHPUnit_Framework_MockObject_MockObject */
private $userManager;

/** @var IRootFolder | \PHPUnit_Framework_MockObject_MockObject */
private $rootFolder;

public function setUp() {
parent::setUp();

$this->createUser('valid-user', 'valid-user');
$this->userManager = $this->createMock(IUserManager::class);
$this->rootFolder = $this->createMock(IRootFolder::class);
$l = $this->createMock(IL10N::class);
$logger = $this->createMock(ILogger::class);

$this->storage = new \OC\Files\Storage\Temporary();
$this->registerMount('valid-user', $this->storage, '/valid-user/');

$this->avatarManager = \OC::$server->getAvatarManager();
$this->avatarManager = $this->getMockBuilder(AvatarManager::class)
->setMethods(['getAvatarFolder'])
->setConstructorArgs([$this->userManager,
$this->rootFolder,
$l,
$logger])
->getMock();
}

/**
Expand All @@ -59,10 +70,31 @@ public function testGetAvatarInvalidUser() {
}

public function testGetAvatarValidUser() {
$user = $this->createMock(IUser::class);
$this->userManager->expects($this->once())->method('get')->willReturn($user);

$folder = $this->createMock(Folder::class);
$this->avatarManager->expects($this->once())->method('getAvatarFolder')->willReturn($folder);

$avatar = $this->avatarManager->getAvatar('valid-user');

$this->assertInstanceOf('\OCP\IAvatar', $avatar);
$this->assertFalse($this->storage->file_exists('files'));
}

/**
* @dataProvider providesUserIds
*/
public function testPathBuilding($expectedPath, $userId) {
$path = $this->invokePrivate($this->avatarManager, 'buildAvatarPath', [$userId]);
$this->assertEquals($expectedPath, implode('/', $path));
}

public function providesUserIds() {
return [
['21/23/2f297a57a5a743894a0e4a801fc3', 'admin'],
['c4/ca/4238a0b923820dcc509a6f75849b', '1'],
['f9/5b/70fdc3088560732a5ac135644506', '{'],
['d4/1d/8cd98f00b204e9800998ecf8427e', ''],
];
}
}
2 changes: 1 addition & 1 deletion version.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
// We only can count up. The 4. digit is only for the internal patchlevel to trigger DB upgrades
// between betas, final and RCs. This is _not_ the public version number. Reset minor/patchlevel
// when updating major/minor version number.
$OC_Version = array(9, 2, 0, 1);
$OC_Version = array(9, 2, 0, 2);

// The human readable string
$OC_VersionString = '9.2.0 pre alpha';
Expand Down