introduce callForSeenUsers and countSeenUsers (#26361)

* introduce callForSeenUsers and countSeenUsers

* add tests

* oracle should support not null on clob

* since 9.2.0
This commit is contained in:
Jörn Friedrich Dreyer 2016-10-19 10:03:29 +02:00 committed by Roeland Jago Douma
parent ad597d498d
commit f8352fcb8d
No known key found for this signature in database
GPG Key ID: 1E152838F164D13B
10 changed files with 209 additions and 66 deletions

View File

@ -77,9 +77,9 @@ class ExpireTrash extends \OC\BackgroundJob\TimedJob {
return;
}
$this->userManager->callForAllUsers(function(IUser $user) {
$this->userManager->callForSeenUsers(function(IUser $user) {
$uid = $user->getUID();
if ($user->getLastLogin() === 0 || !$this->setupFS($uid)) {
if (!$this->setupFS($uid)) {
return;
}
$dirContent = Helper::getTrashFiles('/', $uid, 'mtime');

View File

@ -89,7 +89,7 @@ class ExpireTrash extends Command {
} else {
$p = new ProgressBar($output);
$p->start();
$this->userManager->callForAllUsers(function(IUser $user) use ($p) {
$this->userManager->callForSeenUsers(function(IUser $user) use ($p) {
$p->advance();
$this->expireTrashForUser($user);
});
@ -100,7 +100,7 @@ class ExpireTrash extends Command {
function expireTrashForUser(IUser $user) {
$uid = $user->getUID();
if ($user->getLastLogin() === 0 || !$this->setupFS($uid)) {
if (!$this->setupFS($uid)) {
return;
}
$dirContent = Helper::getTrashFiles('/', $uid, 'mtime');

View File

@ -67,9 +67,9 @@ class ExpireVersions extends \OC\BackgroundJob\TimedJob {
return;
}
$this->userManager->callForAllUsers(function(IUser $user) {
$this->userManager->callForSeenUsers(function(IUser $user) {
$uid = $user->getUID();
if ($user->getLastLogin() === 0 || !$this->setupFS($uid)) {
if (!$this->setupFS($uid)) {
return;
}
Storage::expireOlderThanMaxForUser($uid);

View File

@ -88,7 +88,7 @@ class ExpireVersions extends Command {
} else {
$p = new ProgressBar($output);
$p->start();
$this->userManager->callForAllUsers(function(IUser $user) use ($p) {
$this->userManager->callForSeenUsers(function(IUser $user) use ($p) {
$p->advance();
$this->expireVersionsForUser($user);
});
@ -99,7 +99,7 @@ class ExpireVersions extends Command {
function expireVersionsForUser(IUser $user) {
$uid = $user->getUID();
if ($user->getLastLogin() === 0 || !$this->setupFS($uid)) {
if (!$this->setupFS($uid)) {
return;
}
Storage::expireOlderThanMaxForUser($uid);

View File

@ -96,30 +96,13 @@ class RemoveRootShares implements IRepairStep {
$output->advance();
};
$userCount = $this->countUsers();
$output->startProgress($userCount);
$output->startProgress($this->userManager->countSeenUsers());
$this->userManager->callForAllUsers($function);
$this->userManager->callForSeenUsers($function);
$output->finishProgress();
}
/**
* Count all the users
*
* @return int
*/
private function countUsers() {
$allCount = $this->userManager->countUsers();
$totalCount = 0;
foreach ($allCount as $backend => $count) {
$totalCount += $count;
}
return $totalCount;
}
/**
* Verify if this repair steps is required
* It *should* not be necessary in most cases and it can be very

View File

@ -335,22 +335,6 @@ class RepairUnmergedShares implements IRepairStep {
}
}
/**
* Count all the users
*
* @return int
*/
private function countUsers() {
$allCount = $this->userManager->countUsers();
$totalCount = 0;
foreach ($allCount as $backend => $count) {
$totalCount += $count;
}
return $totalCount;
}
public function run(IOutput $output) {
$ocVersionFromBeforeUpdate = $this->config->getSystemValue('version', '0.0.0');
if (version_compare($ocVersionFromBeforeUpdate, '9.1.0.16', '<')) {
@ -363,8 +347,7 @@ class RepairUnmergedShares implements IRepairStep {
$this->buildPreparedQueries();
$userCount = $this->countUsers();
$output->startProgress($userCount);
$output->startProgress($this->userManager->countUsers());
$this->userManager->callForAllUsers($function);

View File

@ -314,10 +314,16 @@ class Manager extends PublicEmitter implements IUserManager {
/**
* returns how many users per backend exist (if supported by backend)
*
* @return array an array of backend class as key and count number as value
* @param boolean $hasLoggedIn when true only users that have a lastLogin
* entry in the preferences table will be affected
* @return array|int an array of backend class as key and count number as value
* if $hasLoggedIn is true only an int is returned
*/
public function countUsers() {
$userCountStatistics = array();
public function countUsers($hasLoggedIn = false) {
if ($hasLoggedIn) {
return $this->countSeenUsers();
}
$userCountStatistics = [];
foreach ($this->backends as $backend) {
if ($backend->implementsActions(Backend::COUNT_USERS)) {
$backendUsers = $backend->countUsers();
@ -344,29 +350,120 @@ class Manager extends PublicEmitter implements IUserManager {
*
* @param \Closure $callback
* @param string $search
* @param boolean $onlySeen when true only users that have a lastLogin entry
* in the preferences table will be affected
* @since 9.0.0
*/
public function callForAllUsers(\Closure $callback, $search = '') {
foreach($this->getBackends() as $backend) {
$limit = 500;
$offset = 0;
do {
$users = $backend->getUsers($search, $limit, $offset);
foreach ($users as $uid) {
if (!$backend->userExists($uid)) {
continue;
public function callForAllUsers(\Closure $callback, $search = '', $onlySeen = false) {
if ($onlySeen) {
$this->callForSeenUsers($callback);
} else {
foreach ($this->getBackends() as $backend) {
$limit = 500;
$offset = 0;
do {
$users = $backend->getUsers($search, $limit, $offset);
foreach ($users as $uid) {
if (!$backend->userExists($uid)) {
continue;
}
$user = $this->getUserObject($uid, $backend, false);
$return = $callback($user);
if ($return === false) {
break;
}
}
$user = $this->getUserObject($uid, $backend, false);
$return = $callback($user);
if ($return === false) {
break;
}
}
$offset += $limit;
} while (count($users) >= $limit);
$offset += $limit;
} while (count($users) >= $limit);
}
}
}
/**
* returns how many users have logged in once
*
* @return int
* @since 9.2.0
*/
public function countSeenUsers() {
$queryBuilder = \OC::$server->getDatabaseConnection()->getQueryBuilder();
$queryBuilder->select($queryBuilder->createFunction('COUNT(*)'))
->from('preferences')
->where($queryBuilder->expr()->eq(
'appid', $queryBuilder->createNamedParameter('login'))
)
->andWhere($queryBuilder->expr()->eq(
'configkey', $queryBuilder->createNamedParameter('lastLogin'))
)
->andWhere($queryBuilder->expr()->isNotNull('configvalue')
);
$query = $queryBuilder->execute();
return (int)$query->fetchColumn();
}
/**
* @param \Closure $callback
* @param string $search
* @since 9.2.0
*/
public function callForSeenUsers (\Closure $callback) {
$limit = 1000;
$offset = 0;
do {
$userIds = $this->getSeenUserIds($limit, $offset);
$offset += $limit;
foreach ($userIds as $userId) {
foreach ($this->backends as $backend) {
if ($backend->userExists($userId)) {
$user = $this->getUserObject($userId, $backend, false);
$return = $callback($user);
if ($return === false) {
return;
}
}
}
}
} while (count($userIds) >= $limit);
}
/**
* Getting all userIds that have a listLogin value requires checking the
* value in php because on oracle you cannot use a clob in a where clause,
* preventing us from doing a not null or length(value) > 0 check.
*
* @param int $limit
* @param int $offset
* @return string[] with user ids
*/
private function getSeenUserIds($limit = null, $offset = null) {
$queryBuilder = \OC::$server->getDatabaseConnection()->getQueryBuilder();
$queryBuilder->select(['userid'])
->from('preferences')
->where($queryBuilder->expr()->eq(
'appid', $queryBuilder->createNamedParameter('login'))
)
->andWhere($queryBuilder->expr()->eq(
'configkey', $queryBuilder->createNamedParameter('lastLogin'))
)
->andWhere($queryBuilder->expr()->isNotNull('configvalue')
);
if ($limit !== null) {
$queryBuilder->setMaxResults($limit);
}
if ($offset !== null) {
$queryBuilder->setFirstResult($offset);
}
$query = $queryBuilder->execute();
$result = [];
while ($row = $query->fetch()) {
$result[] = $row['userid'];
}
return $result;
}
/**
* @param string $email
* @return IUser[]

View File

@ -144,6 +144,21 @@ interface IUserManager {
*/
public function callForAllUsers (\Closure $callback, $search = '');
/**
* returns how many users have logged in once
*
* @return int
* @since 9.2.0
*/
public function countSeenUsers();
/**
* @param \Closure $callback
* @param string $search
* @since 9.2.0
*/
public function callForSeenUsers (\Closure $callback);
/**
* @param string $email
* @return IUser[]

View File

@ -106,6 +106,7 @@ class RemoveRootSharesTest extends \Test\TestCase {
$user = $this->userManager->createUser('test', 'test');
$userFolder = $this->rootFolder->getUserFolder('test');
$fileId = $userFolder->getId();
$user->updateLastLoginTimestamp();
//Now insert cyclic share
$qb = $this->connection->getQueryBuilder();
@ -134,6 +135,7 @@ class RemoveRootSharesTest extends \Test\TestCase {
$user1 = $this->userManager->createUser('test1', 'test1');
$userFolder = $this->rootFolder->getUserFolder('test1');
$fileId = $userFolder->getId();
$user1->updateLastLoginTimestamp();
//Now insert cyclic share
$qb = $this->connection->getQueryBuilder();
@ -156,6 +158,7 @@ class RemoveRootSharesTest extends \Test\TestCase {
$userFolder = $this->rootFolder->getUserFolder('test2');
$folder = $userFolder->newFolder('foo');
$fileId = $folder->getId();
$user2->updateLastLoginTimestamp();
//Now insert cyclic share
$qb = $this->connection->getQueryBuilder();

View File

@ -9,6 +9,8 @@
namespace Test\User;
use OC\User\Database;
use OCP\IUser;
use Test\TestCase;
/**
* Class ManagerTest
@ -17,7 +19,7 @@ use OC\User\Database;
*
* @package Test\User
*/
class ManagerTest extends \Test\TestCase {
class ManagerTest extends TestCase {
public function testGetBackends() {
$userDummyBackend = $this->createMock(\Test\Util\User\Dummy::class);
$manager = new \OC\User\Manager();
@ -448,6 +450,66 @@ class ManagerTest extends \Test\TestCase {
$this->assertEquals(7 + 16, $users);
}
public function testCountUsersOnlySeen() {
$manager = \OC::$server->getUserManager();
// count other users in the db before adding our own
$countBefore = $manager->countUsers(true);
//Add test users
$user1 = $manager->createUser('testseencount1', 'testseencount1');
$user1->updateLastLoginTimestamp();
$user2 = $manager->createUser('testseencount2', 'testseencount2');
$user2->updateLastLoginTimestamp();
$user3 = $manager->createUser('testseencount3', 'testseencount3');
$user4 = $manager->createUser('testseencount4', 'testseencount4');
$user4->updateLastLoginTimestamp();
$this->assertEquals($countBefore + 3, $manager->countUsers(true));
//cleanup
$user1->delete();
$user2->delete();
$user3->delete();
$user4->delete();
}
public function testCallForSeenUsers() {
$manager = \OC::$server->getUserManager();
// count other users in the db before adding our own
$count = 0;
$function = function (IUser $user) use (&$count) {
$count++;
};
$manager->callForAllUsers($function, '', true);
$countBefore = $count;
//Add test users
$user1 = $manager->createUser('testseen1', 'testseen1');
$user1->updateLastLoginTimestamp();
$user2 = $manager->createUser('testseen2', 'testseen2');
$user2->updateLastLoginTimestamp();
$user3 = $manager->createUser('testseen3', 'testseen3');
$user4 = $manager->createUser('testseen4', 'testseen4');
$user4->updateLastLoginTimestamp();
$count = 0;
$manager->callForAllUsers($function, '', true);
$this->assertEquals($countBefore + 3, $count);
//cleanup
$user1->delete();
$user2->delete();
$user3->delete();
$user4->delete();
}
public function testDeleteUser() {
$config = $this->getMockBuilder('OCP\IConfig')
->disableOriginalConstructor()