Skip to content

Commit

Permalink
Merge pull request #843 from nextcloud/fix-instances
Browse files Browse the repository at this point in the history
fix instances
  • Loading branch information
ArtificialOwl authored Nov 19, 2021
2 parents e35c0f5 + c780b17 commit c7decea
Show file tree
Hide file tree
Showing 3 changed files with 263 additions and 4 deletions.
1 change: 1 addition & 0 deletions appinfo/info.xml
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ Users won't be able to find this Circle using Nextcloud search engine.
<command>OCA\Circles\Command\SyncContact</command>
<!--<command>OCA\Circles\Command\Groups</command>-->
<command>OCA\Circles\Command\FixUniqueId</command>
<command>OCA\Circles\Command\FixInstance</command>
</commands>

<settings>
Expand Down
258 changes: 258 additions & 0 deletions lib/Command/FixInstance.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,258 @@
<?php
/**
* Circles - Bring cloud-users closer together.
*
* This file is licensed under the Affero General Public License version 3 or
* later. See the COPYING file.
*
* @author Maxence Lange <[email protected]>
* @copyright 2017
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* 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
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/

namespace OCA\Circles\Command;

use OC\Core\Command\Base;
use OCA\Circles\Db\MembersRequest;
use OCA\Circles\Model\Member;
use OCP\IDBConnection;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\ConfirmationQuestion;


/**
*
*/
class FixInstance extends Base {

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

/** @var MembersRequest */
private $membersRequest;


/** @var InputInterface */
private $input;

/** @var OutputInterface */
private $output;


/**
* @param MembersRequest $membersRequest
* @param IDBConnection $connection
*/
public function __construct(
MembersRequest $membersRequest,
IDBConnection $connection
) {
parent::__construct();

$this->membersRequest = $membersRequest;
$this->connection = $connection;
}

protected function configure() {
parent::configure();
$this->setName('circles:fix:instance-alias')
->setDescription('fix Instance aliases issue.')
->addOption('fix', '', InputOption::VALUE_NONE, 'fix for real');
}

protected function execute(InputInterface $input, OutputInterface $output) {
$this->input = $input;
$this->output = $output;
$faulties = $this->getFaultyMembers();

$output->writeln('Found ' . sizeof($faulties) . ' faulty entries');

foreach ($faulties as $faulty) {
$output->writeln('');
$output->writeln(
'> <info>' . $faulty->getUserId() . '</info> in <info>' . $faulty->getCircleId()
. '</info> with instance=<info>' . $faulty->getInstance() . '</info>'
);

$dupes = $this->getDuplicates($faulty);
if (sizeof($dupes) === 0) {
$this->fixInstance($faulty);
} else {
$this->deleteDupe($faulty, $dupes);
}
}

return 0;
}


/**
* @return Member[]
*/
private function getFaultyMembers() {
$qb = $this->membersRequest->getMembersSelectSql();

$expr = $qb->expr();
$qb->andWhere(
$expr->neq('instance', $qb->createNamedParameter('')),
$expr->neq($qb->createFunction('POSITION(\'@\' IN instance)'), $qb->createNamedParameter(0))
);

$members = [];
$cursor = $qb->execute();
while ($data = $cursor->fetch()) {
$members[] = $this->membersRequest->parseMembersSelectSql($data);
}
$cursor->closeCursor();

return $members;
}


/**
* @param Member $faulty
*
* @return array
* @throws \OCP\DB\Exception
*/
private function getDuplicates(Member $faulty) {
$qb = $this->membersRequest->getMembersSelectSql();

$expr = $qb->expr();
$qb->andWhere(
$expr->neq('instance', $qb->createNamedParameter($faulty->getInstance())),
$expr->eq('circle_id', $qb->createNamedParameter($faulty->getCircleId())),
$expr->eq('user_type', $qb->createNamedParameter($faulty->getType())),
$expr->eq('user_id', $qb->createNamedParameter($faulty->getUserId()))
);

$dupes = [];
$cursor = $qb->execute();
while ($data = $cursor->fetch()) {
$dupes[] = $this->membersRequest->parseMembersSelectSql($data);
}
$cursor->closeCursor();

return $dupes;
}


private function fixInstance(Member $faulty) {
[, $fixed] = explode('@', $faulty->getInstance(), 2);
$this->output->writeln(' - found no dupe, fixing instance to <info>' . $fixed . '</info>');

$question = new ConfirmationQuestion(
'<comment>Do you really want to ?</comment> (y/N) ', false,
'/^(y|Y)/i'
);

$helper = $this->getHelper('question');
if (!$helper->ask($this->input, $this->output, $question)) {
$this->output->writeln('aborted.');

return;
}

$qb = $this->membersRequest->getMembersUpdateSql(
$faulty->getCircleId(),
$faulty->getUserId(),
$faulty->getInstance(),
$faulty->getType()
);
$qb->set('instance', $qb->createNamedParameter($fixed));

if ($this->input->getOption('fix')) {
$qb->execute();
}
}


/**
* @param Member $faulty
* @param Member[] $dupes
*/
private function deleteDupe(Member $faulty, $dupes) {
if (sizeof($dupes) > 1) {
$this->output->writeln(' - <error>2 many dupes, please fix manually</error>');

return;
}

$dupe = array_shift($dupes);

$removeFaulty = false;
if ($dupe->getInstance() === '') {
$this->confirmDeleteDupe($faulty, $dupe);

return;
}

[, $fixed] = explode('@', $faulty->getInstance(), 2);
if ($dupe->getInstance() === $fixed) {
$this->confirmDeleteDupe($faulty, $dupe, $fixed);

return;
}

$this->output->writeln(
' - <error>could not identify instance ' . $dupe->getInstance() . ', please fix manually</error>'
);
}


private function confirmDeleteDupe(Member $faulty, Member $dupe, $fixed = '') {
if ($fixed === '') {
$msg = 'dupe is local';
} else {
$msg = 'dupe instance is <info>' . $dupe->getInstance() . '</info>';
}

$this->output->writeln(
' - ' . $msg . '. removing faulty with instance=<info>' . $faulty->getInstance() . '</info>'
);

$question = new ConfirmationQuestion(
'<comment>Do you really want to ?</comment> (y/N) ', false,
'/^(y|Y)/i'
);

$helper = $this->getHelper('question');
if (!$helper->ask($this->input, $this->output, $question)) {
$this->output->writeln('aborted.');

return;
}

$qb = $this->membersRequest->getMembersDeleteSql();
$expr = $qb->expr();
$qb->andWhere(
$expr->eq('instance', $qb->createNamedParameter($faulty->getInstance())),
$expr->eq('circle_id', $qb->createNamedParameter($faulty->getCircleId())),
$expr->eq('user_type', $qb->createNamedParameter($faulty->getType())),
$expr->eq('user_id', $qb->createNamedParameter($faulty->getUserId()))
);

if ($this->input->getOption('fix')) {
$qb->execute();
}
}
}



8 changes: 4 additions & 4 deletions lib/Db/MembersRequestBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ protected function getMembersInsertSql() {
/**
* @return IQueryBuilder
*/
protected function getMembersSelectSql() {
public function getMembersSelectSql() {
$qb = $this->dbConnection->getQueryBuilder();

/** @noinspection PhpMethodParametersCountMismatchInspection */
Expand All @@ -103,7 +103,7 @@ protected function getMembersSelectSql() {
*
* @return IQueryBuilder
*/
protected function getMembersUpdateSql(string $circleId, string $userId, string $instance, int $type) {
public function getMembersUpdateSql(string $circleId, string $userId, string $instance, int $type) {
$qb = $this->dbConnection->getQueryBuilder();
$expr = $qb->expr();

Expand All @@ -127,7 +127,7 @@ protected function getMembersUpdateSql(string $circleId, string $userId, string
*
* @return IQueryBuilder
*/
protected function getMembersDeleteSql() {
public function getMembersDeleteSql() {
$qb = $this->dbConnection->getQueryBuilder();
$qb->delete(CoreRequestBuilder::TABLE_MEMBERS);

Expand All @@ -140,7 +140,7 @@ protected function getMembersDeleteSql() {
*
* @return Member
*/
protected function parseMembersSelectSql(array $data) {
public function parseMembersSelectSql(array $data) {
$member = new Member($data['user_id'], $data['user_type'], $data['circle_id']);
$member->setNote($data['note']);
$member->setContactId($data['contact_id']);
Expand Down

0 comments on commit c7decea

Please sign in to comment.