Completed
Pull Request — master (#634)
by Maxence
02:50
created

MigrationService::migrationTo22()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 14
rs 9.7998
c 0
b 0
f 0
cc 2
nc 2
nop 0
1
<?php
2
3
declare(strict_types=1);
4
5
6
/**
7
 * Circles - Bring cloud-users closer together.
8
 *
9
 * This file is licensed under the Affero General Public License version 3 or
10
 * later. See the COPYING file.
11
 *
12
 * @author Maxence Lange <[email protected]>
13
 * @copyright 2021
14
 * @license GNU AGPL version 3 or any later version
15
 *
16
 * This program is free software: you can redistribute it and/or modify
17
 * it under the terms of the GNU Affero General Public License as
18
 * published by the Free Software Foundation, either version 3 of the
19
 * License, or (at your option) any later version.
20
 *
21
 * This program is distributed in the hope that it will be useful,
22
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
23
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
24
 * GNU Affero General Public License for more details.
25
 *
26
 * You should have received a copy of the GNU Affero General Public License
27
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
28
 *
29
 */
30
31
32
namespace OCA\Circles\Service;
33
34
35
use ArtificialOwl\MySmallPhpTools\Model\SimpleDataStore;
36
use ArtificialOwl\MySmallPhpTools\Traits\Nextcloud\nc22\TNC22Logger;
37
use ArtificialOwl\MySmallPhpTools\Traits\TStringTools;
38
use Exception;
39
use OC;
40
use OCA\Circles\AppInfo\Application;
41
use OCA\Circles\Db\CircleRequest;
42
use OCA\Circles\Db\MemberRequest;
43
use OCA\Circles\Exceptions\CircleNotFoundException;
44
use OCA\Circles\Exceptions\ContactAddressBookNotFoundException;
45
use OCA\Circles\Exceptions\ContactFormatException;
46
use OCA\Circles\Exceptions\ContactNotFoundException;
47
use OCA\Circles\Exceptions\FederatedItemException;
48
use OCA\Circles\Exceptions\FederatedUserException;
49
use OCA\Circles\Exceptions\FederatedUserNotFoundException;
50
use OCA\Circles\Exceptions\InvalidIdException;
51
use OCA\Circles\Exceptions\MemberNotFoundException;
52
use OCA\Circles\Exceptions\MigrationException;
53
use OCA\Circles\Exceptions\OwnerNotFoundException;
54
use OCA\Circles\Exceptions\RemoteInstanceException;
55
use OCA\Circles\Exceptions\RemoteNotFoundException;
56
use OCA\Circles\Exceptions\RemoteResourceNotFoundException;
57
use OCA\Circles\Exceptions\RequestBuilderException;
58
use OCA\Circles\Exceptions\SingleCircleNotFoundException;
59
use OCA\Circles\Exceptions\UnknownRemoteException;
60
use OCA\Circles\Exceptions\UserTypeNotFoundException;
61
use OCA\Circles\Model\Circle;
62
use OCA\Circles\Model\FederatedUser;
63
use OCA\Circles\Model\Member;
64
use OCA\DAV\CardDAV\ContactsManager;
65
use OCP\Contacts\IManager;
66
use OCP\IDBConnection;
67
use OCP\IURLGenerator;
68
69
70
/**
71
 * Class MigrationService
72
 *
73
 * @package OCA\Circles\Service
74
 */
75
class MigrationService {
76
77
78
	use TStringTools;
79
	use TNC22Logger;
80
81
82
	/** @var IDBConnection */
83
	private $dbConnection;
84
85
	/** @var IURLGenerator */
86
	private $urlGenerator;
87
88
	/** @var CircleRequest */
89
	private $circleRequest;
90
91
	/** @var MemberRequest */
92
	private $memberRequest;
93
94
	/** @var SyncService */
95
	private $syncService;
96
97
	/** @var MembershipService */
98
	private $membershipService;
99
100
	/** @var FederatedUserService */
101
	private $federatedUserService;
102
103
	/** @var CircleService */
104
	private $circleService;
105
106
	/** @var ContactService */
107
	private $contactService;
108
109
	/** @var TimezoneService */
110
	private $timezoneService;
111
112
	/** @var OutputService */
113
	private $outputService;
114
115
	/** @var ConfigService */
116
	private $configService;
117
118
119
	/** @var FederatedUser */
120
	private $appCircle = null;
121
122
123
	/**
124
	 * MigrationService constructor.
125
	 *
126
	 * @param IDBConnection $dbConnection
127
	 * @param IURLGenerator $urlGenerator
128
	 * @param CircleRequest $circleRequest
129
	 * @param MemberRequest $memberRequest
130
	 * @param FederatedUserService $federatedUserService
131
	 * @param CircleService $circleService
132
	 * @param ContactService $contactService
133
	 * @param TimezoneService $timezoneService
134
	 * @param OutputService $outputService
135
	 * @param ConfigService $configService
136
	 */
137
	public function __construct(
138
		IDBConnection $dbConnection,
139
		IURLGenerator $urlGenerator,
140
		CircleRequest $circleRequest,
141
		MemberRequest $memberRequest,
142
		SyncService $syncService,
143
		MembershipService $membershipService,
144
		FederatedUserService $federatedUserService,
145
		CircleService $circleService,
146
		ContactService $contactService,
147
		TimezoneService $timezoneService,
148
		OutputService $outputService,
149
		ConfigService $configService
150
	) {
151
		$this->dbConnection = $dbConnection;
152
		$this->urlGenerator = $urlGenerator;
153
		$this->circleRequest = $circleRequest;
154
		$this->memberRequest = $memberRequest;
155
		$this->syncService = $syncService;
156
		$this->membershipService = $membershipService;
157
		$this->federatedUserService = $federatedUserService;
158
		$this->circleService = $circleService;
159
		$this->contactService = $contactService;
160
		$this->timezoneService = $timezoneService;
161
		$this->outputService = $outputService;
162
		$this->configService = $configService;
163
164
		$this->setup('app', Application::APP_ID);
165
	}
166
167
168
	/**
169
	 * @param bool $force
170
	 *
171
	 * @throws ContactAddressBookNotFoundException
172
	 * @throws ContactFormatException
173
	 * @throws ContactNotFoundException
174
	 * @throws FederatedUserException
175
	 * @throws InvalidIdException
176
	 * @throws MigrationException
177
	 * @throws RequestBuilderException
178
	 * @throws SingleCircleNotFoundException
179
	 */
180
	public function migration(bool $force = false): void {
181
		if ($this->configService->getAppValueBool(ConfigService::MIGRATION_RUN)) {
182
			throw new MigrationException('A migration process is already running');
183
		}
184
		$this->configService->setAppValue(ConfigService::MIGRATION_RUN, '1');
185
186
		if ($force) {
187
			$this->configService->setAppValue(ConfigService::MIGRATION_22, '0');
188
//			$this->configService->setAppValue(ConfigService::MIGRATION_23, '0');
189
		}
190
191
		$this->appCircle = $this->federatedUserService->getAppInitiator(
192
			Application::APP_ID,
193
			Member::APP_CIRCLES
194
		);
195
196
		$this->migrationTo22();
197
		//	$this->migrationTo23();
198
199
		$this->configService->setAppValue(ConfigService::MIGRATION_RUN, '0');
200
	}
201
202
203
	/**
204
	 *
205
	 */
206
	private function migrationTo22(): void {
207
		if ($this->configService->getAppValueBool(ConfigService::MIGRATION_22)) {
208
			return;
209
		}
210
211
		$this->outputService->output('Migrating to 22');
212
213
		$this->syncService->sync();
214
		$this->migrationTo22_Circles();
215
		$this->migrationTo22_Members();
216
		$this->membershipService->manageAll();
217
218
		$this->configService->setAppValue(ConfigService::MIGRATION_22, '1');
219
	}
220
221
222 View Code Duplication
	private function migrationTo22_Circles(): void {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
223
		$qb = $this->dbConnection->getQueryBuilder();
224
		$qb->select('*')->from('circle_circles');
225
226
		try {
227
			$cursor = $qb->executeQuery();
228
			$this->outputService->startMigrationProgress($cursor->rowCount());
229
230
			while ($row = $cursor->fetch()) {
231
				try {
232
					$data = new SimpleDataStore($row);
233
					$this->outputService->output(
234
						'Migrating Circle \'' . $data->g('name') . '\'',
235
						true
236
					);
237
238
					$circle = $this->generateCircleFrom21($data);
239
					$this->saveGeneratedCircle($circle);
240
				} catch (Exception $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
241
				}
242
			}
243
244
			$cursor->closeCursor();
245
		} catch (\OCP\DB\Exception $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
Bug introduced by
The class OCP\DB\Exception does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
246
		}
247
248
		$this->outputService->finishMigrationProgress();
249
	}
250
251
252
	/**
253
	 * @param SimpleDataStore $data
254
	 *
255
	 * @return Circle
256
	 * @throws RequestBuilderException
257
	 */
258
	private function generateCircleFrom21(SimpleDataStore $data): Circle {
259
		$circle = new Circle();
260
		$circle->setSingleId($data->g('unique_id'))
261
			   ->setName($data->g('name'))
262
			   ->setDisplayName($data->g('display_name'))
263
			   ->setSettings($data->gArray('settings'))
264
			   ->setDescription($data->g('description'))
265
			   ->setContactAddressBook($data->gInt('contact_addressbook'))
266
			   ->setContactGroupName($data->g('contact_groupname'))
267
			   ->setSource(Member::TYPE_CIRCLE);
268
269
		$dTime = $this->timezoneService->getDateTime($data->g('creation'));
270
		$circle->setCreation($dTime->getTimestamp());
271
272
		if ($circle->getDisplayName() === '') {
273
			$circle->setDisplayName($circle->getName());
274
		}
275
276
		$this->circleService->generateSanitizedName($circle);
277
		$this->convertCircleTypeFrom21($circle, $data->gInt('type'));
278
279
		return $circle;
280
	}
281
282
283
	/**
284
	 * @param Circle $circle
285
	 * @param int $type
286
	 */
287
	private function convertCircleTypeFrom21(Circle $circle, int $type): void {
288
		switch ($type) {
289
			case 1: // personal
290
				$circle->setConfig(Circle::CFG_PERSONAL);
291
				break;
292
293
			case 2: // secret
294
				$circle->setConfig(Circle::CFG_CIRCLE);
295
				break;
296
297
			case 4: // closed
298
				$circle->setConfig(Circle::CFG_OPEN + Circle::CFG_REQUEST);
299
				break;
300
301
			case 8: // public
302
				$circle->setConfig(Circle::CFG_OPEN);
303
				break;
304
		}
305
	}
306
307
308
	/**
309
	 * @param Circle $circle
310
	 */
311
	private function saveGeneratedCircle(Circle $circle): void {
312
		try {
313
			$this->circleRequest->getCircle($circle->getSingleId());
314
		} catch (CircleNotFoundException $e) {
315
			try {
316
				$this->circleRequest->save($circle);
317
			} catch (InvalidIdException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
318
			}
319
			usleep(50);
320
		} catch (RequestBuilderException $e) {
321
		}
322
	}
323
324
325
	/**
326
	 */
327 View Code Duplication
	private function migrationTo22_Members(): void {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
328
		$qb = $this->dbConnection->getQueryBuilder();
329
		$qb->select('*')->from('circle_members');
330
331
		try {
332
			$cursor = $qb->executeQuery();
333
			$this->outputService->startMigrationProgress($cursor->rowCount());
334
335
			while ($row = $cursor->fetch()) {
336
				try {
337
					$data = new SimpleDataStore($row);
338
					$this->outputService->output(
339
						'Migrating Member \'' . $data->g('user_id') . '\'',
340
						true
341
					);
342
343
					$member = $this->generateMemberFrom21($data);
344
					$this->saveGeneratedMember($member);
345
				} catch (Exception $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
346
				}
347
			}
348
349
			$cursor->closeCursor();
350
		} catch (\OCP\DB\Exception $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
Bug introduced by
The class OCP\DB\Exception does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
351
		}
352
353
		$this->outputService->finishMigrationProgress();
354
	}
355
356
357
	/**
358
	 * @throws CircleNotFoundException
359
	 * @throws RemoteInstanceException
360
	 * @throws UserTypeNotFoundException
361
	 * @throws FederatedUserNotFoundException
362
	 * @throws OwnerNotFoundException
363
	 * @throws RequestBuilderException
364
	 * @throws RemoteNotFoundException
365
	 * @throws UnknownRemoteException
366
	 * @throws FederatedUserException
367
	 * @throws ContactAddressBookNotFoundException
368
	 * @throws RemoteResourceNotFoundException
369
	 * @throws MemberNotFoundException
370
	 * @throws FederatedItemException
371
	 * @throws SingleCircleNotFoundException
372
	 * @throws InvalidIdException
373
	 */
374
	private function generateMemberFrom21(SimpleDataStore $data): Member {
375
		$member = new Member();
376
377
		$member->setCircleId($data->g('circle_id'))
378
			   ->setId($data->g('member_id'))
379
			   ->setUserId($data->g('user_id'))
380
			   ->setInstance($data->g('instance'))
381
			   ->setDisplayName($data->g('cached_name'))
382
			   ->setLevel($data->gInt('level'))
383
			   ->setStatus($data->g('status'))
384
			   ->setContactMeta($data->g('contact_meta'))
385
			   ->setContactId($data->g('contact_id'))
386
			   ->setInvitedBy($this->appCircle);
387
388
		$this->convertMemberUserTypeFrom21($member, $data->gInt('user_type'));
389
390
		$singleMember = $this->federatedUserService->getFederatedUser(
391
			$member->getUserId(),
392
			$member->getUserType()
393
		);
394
395
		$member->setSingleId($singleMember->getSingleId());
396
397
//					"cached_update":"2021-05-02 12:13:22",
398
//					"joined":"2021-05-02 12:13:22",
399
//					"contact_checked":null,"
400
//					single_id":"wt6WQYYCry3EOud",
401
//					"circle_source":null}
402
403
		return $member;
404
	}
405
406
407
	/**
408
	 * @param Member $member
409
	 * @param int $userType
410
	 *
411
	 * @throws ContactAddressBookNotFoundException
412
	 */
413
	private function convertMemberUserTypeFrom21(Member $member, int $userType): void {
414
		switch ($userType) {
415
			case 1:
416
				$member->setUserType(1);
417
418
				return;
419
			case 2:
420
				$member->setUserType(2);
421
422
				return;
423
			case 3:
424
				$member->setUserType(4);
425
426
				return;
427
			case 4:
428
				$member->setUserType(8);
429
				$this->fixContactId($member);
430
431
				return;
432
		}
433
	}
434
435
436
	/**
437
	 * @param Member $member
438
	 *
439
	 * @throws ContactAddressBookNotFoundException
440
	 */
441
	private function fixContactId(Member $member) {
442
		list($userId, $contactId) = explode(':', $member->getUserId());
443
444
		$contactsManager = OC::$server->get(ContactsManager::class);
445
446
		/** @var IManager $cm */
447
		$cm = OC::$server->get(IManager::class);
448
		$contactsManager->setupContactsProvider($cm, $userId, $this->urlGenerator);
449
450
		$contact = $cm->search($contactId, ['UID']);
451
		if (sizeof($contact) === 1) {
452
			$entry = array_shift($contact);
453
			$addressBook =
454
				$this->contactService->getAddressBoxById($cm, $this->get('addressbook-key', $entry));
455
456
			$member->setUserId($userId . '/' . $addressBook->getUri() . '/' . $contactId);
457
		}
458
	}
459
460
461
	/**
462
	 * @param Member $member
463
	 */
464
	private function saveGeneratedMember(Member $member): void {
465
		try {
466
			$this->memberRequest->getMemberById($member->getId());
467
		} catch (MemberNotFoundException $e) {
468
			try {
469
				$this->memberRequest->save($member);
470
			} catch (InvalidIdException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
471
			}
472
		} catch (RequestBuilderException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
473
		}
474
	}
475
476
}
477
478