Completed
Pull Request — master (#362)
by Maxence
01:55 queued 11s
created

GSUpstreamService::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 19
rs 9.6333
c 0
b 0
f 0
cc 1
nc 1
nop 8

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php declare(strict_types=1);
2
3
4
/**
5
 * Circles - Bring cloud-users closer together.
6
 *
7
 * This file is licensed under the Affero General Public License version 3 or
8
 * later. See the COPYING file.
9
 *
10
 * @author Maxence Lange <[email protected]>
11
 * @copyright 2017
12
 * @license GNU AGPL version 3 or any later version
13
 *
14
 * This program is free software: you can redistribute it and/or modify
15
 * it under the terms of the GNU Affero General Public License as
16
 * published by the Free Software Foundation, either version 3 of the
17
 * License, or (at your option) any later version.
18
 *
19
 * This program is distributed in the hope that it will be useful,
20
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22
 * GNU Affero General Public License for more details.
23
 *
24
 * You should have received a copy of the GNU Affero General Public License
25
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
26
 *
27
 */
28
29
30
namespace OCA\Circles\Service;
31
32
33
use daita\MySmallPhpTools\Exceptions\RequestContentException;
34
use daita\MySmallPhpTools\Exceptions\RequestNetworkException;
35
use daita\MySmallPhpTools\Exceptions\RequestResultNotJsonException;
36
use daita\MySmallPhpTools\Exceptions\RequestResultSizeException;
37
use daita\MySmallPhpTools\Exceptions\RequestServerException;
38
use daita\MySmallPhpTools\Model\Request;
39
use daita\MySmallPhpTools\Model\SimpleDataStore;
40
use daita\MySmallPhpTools\Traits\TArrayTools;
41
use daita\MySmallPhpTools\Traits\TRequest;
42
use Exception;
43
use OCA\Circles\Db\CirclesRequest;
44
use OCA\Circles\Db\GSEventsRequest;
45
use OCA\Circles\Db\MembersRequest;
46
use OCA\Circles\Exceptions\GlobalScaleEventException;
47
use OCA\Circles\Exceptions\GSStatusException;
48
use OCA\Circles\Exceptions\JsonException;
49
use OCA\Circles\Exceptions\ModelException;
50
use OCA\Circles\GlobalScale\CircleStatus;
51
use OCA\Circles\Model\Circle;
52
use OCA\Circles\Model\GlobalScale\GSEvent;
53
use OCA\Circles\Model\GlobalScale\GSWrapper;
54
use OCP\IURLGenerator;
55
56
57
/**
58
 * Class GSUpstreamService
59
 *
60
 * @package OCA\Circles\Service
61
 */
62
class GSUpstreamService {
63
64
65
	use TRequest;
66
	use TArrayTools;
67
68
69
	/** @var string */
70
	private $userId = '';
71
72
	/** @var IURLGenerator */
73
	private $urlGenerator;
74
75
	/** @var GSEventsRequest */
76
	private $gsEventsRequest;
77
78
	/** @var CirclesRequest */
79
	private $circlesRequest;
80
81
	/** @var MembersRequest */
82
	private $membersRequest;
83
84
	/** @var GlobalScaleService */
85
	private $globalScaleService;
86
87
	/** @var ConfigService */
88
	private $configService;
89
90
	/** @var MiscService */
91
	private $miscService;
92
93
94
	/**
95
	 * GSUpstreamService constructor.
96
	 *
97
	 * @param $userId
98
	 * @param IURLGenerator $urlGenerator
99
	 * @param GSEventsRequest $gsEventsRequest
100
	 * @param CirclesRequest $circlesRequest
101
	 * @param MembersRequest $membersRequest
102
	 * @param GlobalScaleService $globalScaleService
103
	 * @param ConfigService $configService
104
	 * @param MiscService $miscService
105
	 */
106
	public function __construct(
107
		$userId,
108
		IURLGenerator $urlGenerator,
109
		GSEventsRequest $gsEventsRequest,
110
		CirclesRequest $circlesRequest,
111
		MembersRequest $membersRequest,
112
		GlobalScaleService $globalScaleService,
113
		ConfigService $configService,
114
		MiscService $miscService
115
	) {
116
		$this->userId = $userId;
117
		$this->urlGenerator = $urlGenerator;
118
		$this->gsEventsRequest = $gsEventsRequest;
119
		$this->circlesRequest = $circlesRequest;
120
		$this->membersRequest = $membersRequest;
121
		$this->globalScaleService = $globalScaleService;
122
		$this->configService = $configService;
123
		$this->miscService = $miscService;
124
	}
125
126
127
	/**
128
	 * @param GSEvent $event
129
	 *
130
	 * @throws Exception
131
	 */
132
	public function newEvent(GSEvent $event) {
133
		try {
134
			$gs = $this->globalScaleService->getGlobalScaleEvent($event);
135
136
			$this->fillEvent($event);
137
			if ($this->isLocalEvent($event)) {
138
				$gs->verify($event, true);
139
				if (!$event->isAsync()) {
140
					$gs->manage($event);
141
				}
142
143
				$this->globalScaleService->asyncBroadcast($event);
144
			} else {
145
//				$gs->verify($event); // needed ? as we check event on the 'master' of the circle
146
				$this->confirmEvent($event);
147
				$this->miscService->log('confirmed: ' . json_encode($event));
148
				$gs->manage($event); // needed ? as we manage it throw the confirmEvent
149
			}
150
		} catch (Exception $e) {
151
			$this->miscService->log(
152
				get_class($e) . ' on new event: ' . $e->getMessage() . ' - ' . json_encode($event), 1
153
			);
154
			throw $e;
155
		}
156
	}
157
158
159
	/**
160
	 * @param GSWrapper $wrapper
161
	 * @param string $protocol
162
	 */
163
	public function broadcastWrapper(GSWrapper $wrapper, string $protocol): void {
164
		$status = GSWrapper::STATUS_FAILED;
165
166
		try {
167
			$this->broadcastEvent($wrapper->getEvent(), $wrapper->getInstance(), $protocol);
168
			$status = GSWrapper::STATUS_DONE;
169
		} catch (RequestContentException | RequestNetworkException | RequestResultSizeException | RequestServerException | RequestResultNotJsonException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
170
		}
171
172
		if ($wrapper->getSeverity() === GSEvent::SEVERITY_HIGH) {
173
			$wrapper->setStatus($status);
174
		} else {
175
			$wrapper->setStatus(GSWrapper::STATUS_OVER);
176
		}
177
178
		$this->gsEventsRequest->update($wrapper);
179
	}
180
181
182
	/**
183
	 * @param GSEvent $event
184
	 * @param string $instance
185
	 * @param string $protocol
186
	 *
187
	 * @throws RequestContentException
188
	 * @throws RequestNetworkException
189
	 * @throws RequestResultNotJsonException
190
	 * @throws RequestResultSizeException
191
	 * @throws RequestServerException
192
	 */
193
	public function broadcastEvent(GSEvent $event, string $instance, string $protocol = ''): void {
194
		$this->signEvent($event);
195
196
		$path = $this->urlGenerator->linkToRoute('circles.GlobalScale.broadcast');
197
		$request = new Request($path, Request::TYPE_POST);
198
199
		$protocols = ['https', 'http'];
200
		if ($protocol !== '') {
201
			$protocols = [$protocol];
202
		}
203
204
		$request->setProtocols($protocols);
0 ignored issues
show
Bug introduced by
The method setProtocols() does not exist on daita\MySmallPhpTools\Model\Request. Did you maybe mean setProtocol()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
205
		$request->setDataSerialize($event);
206
207
		$request->setAddress($instance);
208
209
		$data = $this->retrieveJson($request);
210
		$event->setResult(new SimpleDataStore($this->getArray('result', $data, [])));
211
	}
212
213
214
	/**
215
	 * @param GSEvent $event
216
	 *
217
	 * @throws RequestContentException
218
	 * @throws RequestNetworkException
219
	 * @throws RequestResultSizeException
220
	 * @throws RequestServerException
221
	 * @throws RequestResultNotJsonException
222
	 * @throws GlobalScaleEventException
223
	 */
224
	public function confirmEvent(GSEvent &$event): void {
225
		$this->signEvent($event);
226
227
		$circle = $event->getCircle();
228
		$owner = $circle->getOwner();
229
		$path = $this->urlGenerator->linkToRoute('circles.GlobalScale.event');
230
231
		$request = new Request($path, Request::TYPE_POST);
232
		if ($this->get('REQUEST_SCHEME', $_SERVER) !== '') {
233
			$request->setProtocols([$_SERVER['REQUEST_SCHEME']]);
0 ignored issues
show
Bug introduced by
The method setProtocols() does not exist on daita\MySmallPhpTools\Model\Request. Did you maybe mean setProtocol()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
234
		} else {
235
			$request->setProtocols(['https', 'http']);
0 ignored issues
show
Bug introduced by
The method setProtocols() does not exist on daita\MySmallPhpTools\Model\Request. Did you maybe mean setProtocol()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
236
		}
237
		$request->setAddressFromUrl($owner->getInstance());
238
		$request->setDataSerialize($event);
239
240
		$result = $this->retrieveJson($request);
241
		$this->miscService->log('result ' . json_encode($result));
242
		if ($this->getInt('status', $result) === 0) {
243
			throw new GlobalScaleEventException($this->get('error', $result));
244
		}
245
246
		$updatedData = $this->getArray('event', $result);
247
		$this->miscService->log('updatedEvent: ' . json_encode($updatedData));
248
		if (!empty($updatedData)) {
249
			$updated = new GSEvent();
250
			try {
251
				$updated->import($updatedData);
252
				$event = $updated;
253
			} catch (Exception $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
254
			}
255
		}
256
	}
257
258
259
	/**
260
	 * @param GSEvent $event
261
	 *
262
	 * @throws GSStatusException
263
	 */
264
	private function fillEvent(GSEvent $event): void {
265
		if (!$this->configService->getGSStatus(ConfigService::GS_ENABLED)) {
266
			return;
267
		}
268
269
		$event->setSource($this->configService->getLocalCloudId());
270
	}
271
272
273
	/**
274
	 * @param GSEvent $event
275
	 */
276
	private function signEvent(GSEvent $event) {
277
		$event->setKey($this->globalScaleService->getKey());
278
	}
279
280
281
	/**
282
	 * We check that the event can be managed/checked locally or if the owner of the circle belongs to
283
	 * an other instance of Nextcloud
284
	 *
285
	 * @param GSEvent $event
286
	 *asyncBroadcast
287
	 *
288
	 * @return bool
289
	 */
290
	private function isLocalEvent(GSEvent $event): bool {
291
		if ($event->isLocal()) {
292
			return true;
293
		}
294
295
		$circle = $event->getCircle();
296
		$owner = $circle->getOwner();
297
		if ($owner->getInstance() === ''
0 ignored issues
show
Unused Code introduced by
This if statement, and the following return statement can be replaced with return $owner->getInstan...->getTrustedDomains());.
Loading history...
298
			|| in_array(
299
				$owner->getInstance(), $this->configService->getTrustedDomains()
300
			)) {
301
			return true;
302
		}
303
304
		return false;
305
	}
306
307
308
	/**
309
	 * @param string $token
310
	 *
311
	 * @return GSWrapper[]
312
	 * @throws JsonException
313
	 * @throws ModelException
314
	 */
315
	public function getEventsByToken(string $token): array {
316
		return $this->gsEventsRequest->getByToken($token);
317
	}
318
319
320
	/**
321
	 * Deprecated ?
322
	 * should be used to manage results from events, like sending mails on user creation
323
	 *
324
	 * @param string $token
325
	 */
326
	public function manageResults(string $token): void {
327
		try {
328
			$wrappers = $this->gsEventsRequest->getByToken($token);
329
		} catch (JsonException | ModelException $e) {
330
			return;
331
		}
332
333
		$event = null;
334
		$events = [];
335
		foreach ($wrappers as $wrapper) {
336
			if ($wrapper->getStatus() !== GSWrapper::STATUS_DONE) {
337
				return;
338
			}
339
340
			$events[$wrapper->getInstance()] = $event = $wrapper->getEvent();
341
		}
342
343
		if ($event === null) {
344
			return;
345
		}
346
347
		try {
348
			$gs = $this->globalScaleService->getGlobalScaleEvent($event);
349
			$gs->result($events);
350
		} catch (GlobalScaleEventException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
351
		}
352
	}
353
354
355
	/**
356
	 * @throws GSStatusException
357
	 */
358
	public function synchronize() {
359
		$this->configService->getGSStatus();
360
361
		$sync = $this->getCirclesToSync();
362
		$this->synchronizeCircles($sync);
363
		$this->removeDeprecatedCircles();
364
365
		$this->removeDeprecatedEvents();
0 ignored issues
show
Unused Code introduced by
The call to the method OCA\Circles\Service\GSUp...emoveDeprecatedEvents() seems un-needed as the method has no side-effects.

PHP Analyzer performs a side-effects analysis of your code. A side-effect is basically anything that might be visible after the scope of the method is left.

Let’s take a look at an example:

class User
{
    private $email;

    public function getEmail()
    {
        return $this->email;
    }

    public function setEmail($email)
    {
        $this->email = $email;
    }
}

If we look at the getEmail() method, we can see that it has no side-effect. Whether you call this method or not, no future calls to other methods are affected by this. As such code as the following is useless:

$user = new User();
$user->getEmail(); // This line could safely be removed as it has no effect.

On the hand, if we look at the setEmail(), this method _has_ side-effects. In the following case, we could not remove the method call:

$user = new User();
$user->setEmail('email@domain'); // This line has a side-effect (it changes an
                                 // instance variable).
Loading history...
366
	}
367
368
369
	/**
370
	 * @param array $circles
371
	 *
372
	 * @throws GSStatusException
373
	 */
374
	public function synchronizeCircles(array $circles): void {
375
		$event = new GSEvent(GSEvent::GLOBAL_SYNC, true);
376
		$event->setSource($this->configService->getLocalCloudId());
377
		$event->setData(new SimpleDataStore($circles));
378
379
		foreach ($this->globalScaleService->getInstances() as $instance) {
380
			try {
381
				$this->broadcastEvent($event, $instance);
382
			} catch (RequestContentException | RequestNetworkException | RequestResultSizeException | RequestServerException | RequestResultNotJsonException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
383
			}
384
		}
385
	}
386
387
388
	/**
389
	 * @return Circle[]
390
	 */
391
	private function getCirclesToSync(): array {
392
		$circles = $this->circlesRequest->forceGetCircles();
393
394
		$sync = [];
395
		foreach ($circles as $circle) {
396
			if ($circle->getOwner()
397
					   ->getInstance() !== ''
398
				|| $circle->getType() === Circle::CIRCLES_PERSONAL) {
399
				continue;
400
			}
401
402
			$members = $this->membersRequest->forceGetMembers($circle->getUniqueId());
403
			$circle->setMembers($members);
404
405
			$sync[] = $circle;
406
		}
407
408
		return $sync;
409
	}
410
411
412
	/**
413
	 *
414
	 */
415
	private function removeDeprecatedCircles() {
416
		$knownCircles = $this->circlesRequest->forceGetCircles();
417
		foreach ($knownCircles as $knownItem) {
418
			if ($knownItem->getOwner()
419
						  ->getInstance() === '') {
420
				continue;
421
			}
422
423
			try {
424
				$this->checkCircle($knownItem);
425
			} catch (GSStatusException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
426
			}
427
		}
428
	}
429
430
431
	/**
432
	 * @param Circle $circle
433
	 *
434
	 * @throws GSStatusException
435
	 */
436
	private function checkCircle(Circle $circle): void {
437
		$status = $this->confirmCircleStatus($circle);
438
439
		if (!$status) {
440
			$this->circlesRequest->destroyCircle($circle->getUniqueId());
441
			$this->membersRequest->removeAllFromCircle($circle->getUniqueId());
442
		}
443
	}
444
445
446
	/**
447
	 * @param Circle $circle
448
	 *
449
	 * @return bool
450
	 * @throws GSStatusException
451
	 */
452
	public function confirmCircleStatus(Circle $circle): bool {
453
		$event = new GSEvent(GSEvent::CIRCLE_STATUS, true);
454
		$event->setSource($this->configService->getLocalCloudId());
455
		$event->setCircle($circle);
456
457
		$this->signEvent($event);
458
459
		$path = $this->urlGenerator->linkToRoute('circles.GlobalScale.status');
460
		$request = new Request($path, Request::TYPE_POST);
461
462
		$request->setProtocols(['https', 'http']);
0 ignored issues
show
Bug introduced by
The method setProtocols() does not exist on daita\MySmallPhpTools\Model\Request. Did you maybe mean setProtocol()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
463
		$request->setDataSerialize($event);
464
465
		$requestIssue = false;
466
		$notFound = false;
467
		$foundWithNoOwner = false;
468
		foreach ($this->globalScaleService->getInstances() as $instance) {
469
			$request->setAddress($instance);
470
471
			try {
472
				$result = $this->retrieveJson($request);
473
//				$this->miscService->log('result: ' . json_encode($result));
474
				if ($this->getInt('status', $result, 0) !== 1) {
475
					throw new RequestContentException('result status is not good');
476
				}
477
478
				$status = $this->getInt('success.data.status', $result);
479
480
				// if error, we assume the circle might still exist.
481
				if ($status === CircleStatus::STATUS_ERROR) {
482
					return true;
483
				}
484
485
				if ($status === CircleStatus::STATUS_OK) {
486
					return true;
487
				}
488
489
				// TODO: check the data.supposedOwner entry.
490
				if ($status === CircleStatus::STATUS_NOT_OWNER) {
491
					$foundWithNoOwner = true;
492
				}
493
494
				if ($status === CircleStatus::STATUS_NOT_FOUND) {
495
					$notFound = true;
496
				}
497
498
			} catch (RequestContentException
499
			| RequestNetworkException
500
			| RequestResultNotJsonException
501
			| RequestResultSizeException
502
			| RequestServerException $e) {
503
				$requestIssue = true;
504
				// TODO: log instances that have network issue, after too many tries (7d), remove this circle.
505
				continue;
506
			}
507
		}
508
509
		// if no request issue, we can imagine that the instance that owns the circle is down.
510
		// We'll wait for more information (cf request exceptions management);
511
		if ($requestIssue) {
512
			return true;
513
		}
514
515
		// circle were not found in any other instances, we can easily says that the circle does not exists anymore
516
		if ($notFound && !$foundWithNoOwner) {
517
			return false;
518
		}
519
520
		// circle were found everywhere but with no owner on every instance. we need to assign a new owner.
521
		// This should be done by checking admin rights. if no admin rights, let's assume that circle should be removed.
522
		if (!$notFound && $foundWithNoOwner) {
0 ignored issues
show
Unused Code introduced by
This if statement, and the following return statement can be replaced with return !$notFound && $foundWithNoOwner;.
Loading history...
523
			// TODO: assign a new owner and check that when changing owner, we do check that the destination instance is updated FOR SURE!
524
			return true;
525
		}
526
527
		// some instances returned notFound, some returned circle with no owner. let's assume the circle is deprecated.
528
		return false;
529
	}
530
531
	/**
532
	 * @throws GSStatusException
533
	 */
534
	public function syncEvents() {
535
536
	}
537
538
	/**
539
	 *
540
	 */
541
	private function removeDeprecatedEvents() {
542
//		$this->deprecatedEvents();
543
544
	}
545
546
547
}
548
549