From c780b172d13d0d247fa852e97c4cc05b742f081b Mon Sep 17 00:00:00 2001 From: Maxence Lange Date: Fri, 12 Nov 2021 11:39:07 -0100 Subject: [PATCH] fix instances Signed-off-by: Maxence Lange --- appinfo/info.xml | 1 + lib/Command/FixInstance.php | 258 +++++++++++++++++++++++++++++++ lib/Db/MembersRequestBuilder.php | 8 +- 3 files changed, 263 insertions(+), 4 deletions(-) create mode 100644 lib/Command/FixInstance.php diff --git a/appinfo/info.xml b/appinfo/info.xml index 9eac960d7..1d8e4ba0d 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -75,6 +75,7 @@ Users won't be able to find this Circle using Nextcloud search engine. OCA\Circles\Command\SyncContact OCA\Circles\Command\FixUniqueId + OCA\Circles\Command\FixInstance diff --git a/lib/Command/FixInstance.php b/lib/Command/FixInstance.php new file mode 100644 index 000000000..a251e7f7f --- /dev/null +++ b/lib/Command/FixInstance.php @@ -0,0 +1,258 @@ + + * @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 . + * + */ + +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( + '> ' . $faulty->getUserId() . ' in ' . $faulty->getCircleId() + . ' with instance=' . $faulty->getInstance() . '' + ); + + $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 ' . $fixed . ''); + + $question = new ConfirmationQuestion( + 'Do you really want to ? (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(' - 2 many dupes, please fix manually'); + + 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( + ' - could not identify instance ' . $dupe->getInstance() . ', please fix manually' + ); + } + + + private function confirmDeleteDupe(Member $faulty, Member $dupe, $fixed = '') { + if ($fixed === '') { + $msg = 'dupe is local'; + } else { + $msg = 'dupe instance is ' . $dupe->getInstance() . ''; + } + + $this->output->writeln( + ' - ' . $msg . '. removing faulty with instance=' . $faulty->getInstance() . '' + ); + + $question = new ConfirmationQuestion( + 'Do you really want to ? (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(); + } + } +} + + + diff --git a/lib/Db/MembersRequestBuilder.php b/lib/Db/MembersRequestBuilder.php index 3bef13237..faee4b195 100644 --- a/lib/Db/MembersRequestBuilder.php +++ b/lib/Db/MembersRequestBuilder.php @@ -76,7 +76,7 @@ protected function getMembersInsertSql() { /** * @return IQueryBuilder */ - protected function getMembersSelectSql() { + public function getMembersSelectSql() { $qb = $this->dbConnection->getQueryBuilder(); /** @noinspection PhpMethodParametersCountMismatchInspection */ @@ -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(); @@ -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); @@ -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']);