Skip to content

Commit c780b17

Browse files
committed
fix instances
Signed-off-by: Maxence Lange <maxence@artificial-owl.com>
1 parent e35c0f5 commit c780b17

File tree

3 files changed

+263
-4
lines changed

3 files changed

+263
-4
lines changed

appinfo/info.xml

+1
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ Users won't be able to find this Circle using Nextcloud search engine.
7575
<command>OCA\Circles\Command\SyncContact</command>
7676
<!--<command>OCA\Circles\Command\Groups</command>-->
7777
<command>OCA\Circles\Command\FixUniqueId</command>
78+
<command>OCA\Circles\Command\FixInstance</command>
7879
</commands>
7980

8081
<settings>

lib/Command/FixInstance.php

+258
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,258 @@
1+
<?php
2+
/**
3+
* Circles - Bring cloud-users closer together.
4+
*
5+
* This file is licensed under the Affero General Public License version 3 or
6+
* later. See the COPYING file.
7+
*
8+
* @author Maxence Lange <maxence@artificial-owl.com>
9+
* @copyright 2017
10+
* @license GNU AGPL version 3 or any later version
11+
*
12+
* This program is free software: you can redistribute it and/or modify
13+
* it under the terms of the GNU Affero General Public License as
14+
* published by the Free Software Foundation, either version 3 of the
15+
* License, or (at your option) any later version.
16+
*
17+
* This program is distributed in the hope that it will be useful,
18+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
19+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20+
* GNU Affero General Public License for more details.
21+
*
22+
* You should have received a copy of the GNU Affero General Public License
23+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
24+
*
25+
*/
26+
27+
namespace OCA\Circles\Command;
28+
29+
use OC\Core\Command\Base;
30+
use OCA\Circles\Db\MembersRequest;
31+
use OCA\Circles\Model\Member;
32+
use OCP\IDBConnection;
33+
use Symfony\Component\Console\Input\InputInterface;
34+
use Symfony\Component\Console\Input\InputOption;
35+
use Symfony\Component\Console\Output\OutputInterface;
36+
use Symfony\Component\Console\Question\ConfirmationQuestion;
37+
38+
39+
/**
40+
*
41+
*/
42+
class FixInstance extends Base {
43+
44+
/** @var IDBConnection */
45+
protected $connection;
46+
47+
/** @var MembersRequest */
48+
private $membersRequest;
49+
50+
51+
/** @var InputInterface */
52+
private $input;
53+
54+
/** @var OutputInterface */
55+
private $output;
56+
57+
58+
/**
59+
* @param MembersRequest $membersRequest
60+
* @param IDBConnection $connection
61+
*/
62+
public function __construct(
63+
MembersRequest $membersRequest,
64+
IDBConnection $connection
65+
) {
66+
parent::__construct();
67+
68+
$this->membersRequest = $membersRequest;
69+
$this->connection = $connection;
70+
}
71+
72+
protected function configure() {
73+
parent::configure();
74+
$this->setName('circles:fix:instance-alias')
75+
->setDescription('fix Instance aliases issue.')
76+
->addOption('fix', '', InputOption::VALUE_NONE, 'fix for real');
77+
}
78+
79+
protected function execute(InputInterface $input, OutputInterface $output) {
80+
$this->input = $input;
81+
$this->output = $output;
82+
$faulties = $this->getFaultyMembers();
83+
84+
$output->writeln('Found ' . sizeof($faulties) . ' faulty entries');
85+
86+
foreach ($faulties as $faulty) {
87+
$output->writeln('');
88+
$output->writeln(
89+
'> <info>' . $faulty->getUserId() . '</info> in <info>' . $faulty->getCircleId()
90+
. '</info> with instance=<info>' . $faulty->getInstance() . '</info>'
91+
);
92+
93+
$dupes = $this->getDuplicates($faulty);
94+
if (sizeof($dupes) === 0) {
95+
$this->fixInstance($faulty);
96+
} else {
97+
$this->deleteDupe($faulty, $dupes);
98+
}
99+
}
100+
101+
return 0;
102+
}
103+
104+
105+
/**
106+
* @return Member[]
107+
*/
108+
private function getFaultyMembers() {
109+
$qb = $this->membersRequest->getMembersSelectSql();
110+
111+
$expr = $qb->expr();
112+
$qb->andWhere(
113+
$expr->neq('instance', $qb->createNamedParameter('')),
114+
$expr->neq($qb->createFunction('POSITION(\'@\' IN instance)'), $qb->createNamedParameter(0))
115+
);
116+
117+
$members = [];
118+
$cursor = $qb->execute();
119+
while ($data = $cursor->fetch()) {
120+
$members[] = $this->membersRequest->parseMembersSelectSql($data);
121+
}
122+
$cursor->closeCursor();
123+
124+
return $members;
125+
}
126+
127+
128+
/**
129+
* @param Member $faulty
130+
*
131+
* @return array
132+
* @throws \OCP\DB\Exception
133+
*/
134+
private function getDuplicates(Member $faulty) {
135+
$qb = $this->membersRequest->getMembersSelectSql();
136+
137+
$expr = $qb->expr();
138+
$qb->andWhere(
139+
$expr->neq('instance', $qb->createNamedParameter($faulty->getInstance())),
140+
$expr->eq('circle_id', $qb->createNamedParameter($faulty->getCircleId())),
141+
$expr->eq('user_type', $qb->createNamedParameter($faulty->getType())),
142+
$expr->eq('user_id', $qb->createNamedParameter($faulty->getUserId()))
143+
);
144+
145+
$dupes = [];
146+
$cursor = $qb->execute();
147+
while ($data = $cursor->fetch()) {
148+
$dupes[] = $this->membersRequest->parseMembersSelectSql($data);
149+
}
150+
$cursor->closeCursor();
151+
152+
return $dupes;
153+
}
154+
155+
156+
private function fixInstance(Member $faulty) {
157+
[, $fixed] = explode('@', $faulty->getInstance(), 2);
158+
$this->output->writeln(' - found no dupe, fixing instance to <info>' . $fixed . '</info>');
159+
160+
$question = new ConfirmationQuestion(
161+
'<comment>Do you really want to ?</comment> (y/N) ', false,
162+
'/^(y|Y)/i'
163+
);
164+
165+
$helper = $this->getHelper('question');
166+
if (!$helper->ask($this->input, $this->output, $question)) {
167+
$this->output->writeln('aborted.');
168+
169+
return;
170+
}
171+
172+
$qb = $this->membersRequest->getMembersUpdateSql(
173+
$faulty->getCircleId(),
174+
$faulty->getUserId(),
175+
$faulty->getInstance(),
176+
$faulty->getType()
177+
);
178+
$qb->set('instance', $qb->createNamedParameter($fixed));
179+
180+
if ($this->input->getOption('fix')) {
181+
$qb->execute();
182+
}
183+
}
184+
185+
186+
/**
187+
* @param Member $faulty
188+
* @param Member[] $dupes
189+
*/
190+
private function deleteDupe(Member $faulty, $dupes) {
191+
if (sizeof($dupes) > 1) {
192+
$this->output->writeln(' - <error>2 many dupes, please fix manually</error>');
193+
194+
return;
195+
}
196+
197+
$dupe = array_shift($dupes);
198+
199+
$removeFaulty = false;
200+
if ($dupe->getInstance() === '') {
201+
$this->confirmDeleteDupe($faulty, $dupe);
202+
203+
return;
204+
}
205+
206+
[, $fixed] = explode('@', $faulty->getInstance(), 2);
207+
if ($dupe->getInstance() === $fixed) {
208+
$this->confirmDeleteDupe($faulty, $dupe, $fixed);
209+
210+
return;
211+
}
212+
213+
$this->output->writeln(
214+
' - <error>could not identify instance ' . $dupe->getInstance() . ', please fix manually</error>'
215+
);
216+
}
217+
218+
219+
private function confirmDeleteDupe(Member $faulty, Member $dupe, $fixed = '') {
220+
if ($fixed === '') {
221+
$msg = 'dupe is local';
222+
} else {
223+
$msg = 'dupe instance is <info>' . $dupe->getInstance() . '</info>';
224+
}
225+
226+
$this->output->writeln(
227+
' - ' . $msg . '. removing faulty with instance=<info>' . $faulty->getInstance() . '</info>'
228+
);
229+
230+
$question = new ConfirmationQuestion(
231+
'<comment>Do you really want to ?</comment> (y/N) ', false,
232+
'/^(y|Y)/i'
233+
);
234+
235+
$helper = $this->getHelper('question');
236+
if (!$helper->ask($this->input, $this->output, $question)) {
237+
$this->output->writeln('aborted.');
238+
239+
return;
240+
}
241+
242+
$qb = $this->membersRequest->getMembersDeleteSql();
243+
$expr = $qb->expr();
244+
$qb->andWhere(
245+
$expr->eq('instance', $qb->createNamedParameter($faulty->getInstance())),
246+
$expr->eq('circle_id', $qb->createNamedParameter($faulty->getCircleId())),
247+
$expr->eq('user_type', $qb->createNamedParameter($faulty->getType())),
248+
$expr->eq('user_id', $qb->createNamedParameter($faulty->getUserId()))
249+
);
250+
251+
if ($this->input->getOption('fix')) {
252+
$qb->execute();
253+
}
254+
}
255+
}
256+
257+
258+

lib/Db/MembersRequestBuilder.php

+4-4
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ protected function getMembersInsertSql() {
7676
/**
7777
* @return IQueryBuilder
7878
*/
79-
protected function getMembersSelectSql() {
79+
public function getMembersSelectSql() {
8080
$qb = $this->dbConnection->getQueryBuilder();
8181

8282
/** @noinspection PhpMethodParametersCountMismatchInspection */
@@ -103,7 +103,7 @@ protected function getMembersSelectSql() {
103103
*
104104
* @return IQueryBuilder
105105
*/
106-
protected function getMembersUpdateSql(string $circleId, string $userId, string $instance, int $type) {
106+
public function getMembersUpdateSql(string $circleId, string $userId, string $instance, int $type) {
107107
$qb = $this->dbConnection->getQueryBuilder();
108108
$expr = $qb->expr();
109109

@@ -127,7 +127,7 @@ protected function getMembersUpdateSql(string $circleId, string $userId, string
127127
*
128128
* @return IQueryBuilder
129129
*/
130-
protected function getMembersDeleteSql() {
130+
public function getMembersDeleteSql() {
131131
$qb = $this->dbConnection->getQueryBuilder();
132132
$qb->delete(CoreRequestBuilder::TABLE_MEMBERS);
133133

@@ -140,7 +140,7 @@ protected function getMembersDeleteSql() {
140140
*
141141
* @return Member
142142
*/
143-
protected function parseMembersSelectSql(array $data) {
143+
public function parseMembersSelectSql(array $data) {
144144
$member = new Member($data['user_id'], $data['user_type'], $data['circle_id']);
145145
$member->setNote($data['note']);
146146
$member->setContactId($data['contact_id']);

0 commit comments

Comments
 (0)