Completed
Push — master ( 3de7d3...149977 )
by Maxence
22s 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
	 * @return GSWrapper
131
	 * @throws Exception
132
	 */
133
	public function newEvent(GSEvent $event): GSWrapper {
134
		$event->setSource($this->configService->getLocalCloudId());
135
136
		try {
137
			$gs = $this->globalScaleService->getGlobalScaleEvent($event);
138
139
			if ($this->isLocalEvent($event)) {
140
				$gs->verify($event, true);
141
				if (!$event->isAsync()) {
142
					$gs->manage($event);
143
				}
144
145
				return $this->globalScaleService->asyncBroadcast($event);
146
			} else {
147
//				$gs->verify($event); // needed ? as we check event on the 'master' of the circle
148
				$this->confirmEvent($event);
149
				$this->miscService->log('confirmed: ' . json_encode($event));
150
//				$gs->manage($event); // needed ? as we manage it throw the confirmEvent
151
			}
152
		} catch (Exception $e) {
153
			$this->miscService->log(
154
				get_class($e) . ' on new event: ' . $e->getMessage() . ' - ' . json_encode($event), 1
155
			);
156
			throw $e;
157
		}
158
	}
159
160
161
	/**
162
	 * @param GSWrapper $wrapper
163
	 * @param string $protocol
164
	 */
165
	public function broadcastWrapper(GSWrapper $wrapper, string $protocol): void {
166
		$status = GSWrapper::STATUS_FAILED;
167
168
		try {
169
			$this->broadcastEvent($wrapper->getEvent(), $wrapper->getInstance(), $protocol);
170
			$status = GSWrapper::STATUS_DONE;
171
		} 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...
172
		}
173
174
		if ($wrapper->getSeverity() === GSEvent::SEVERITY_HIGH) {
175
			$wrapper->setStatus($status);
176
		} else {
177
			$wrapper->setStatus(GSWrapper::STATUS_OVER);
178
		}
179
180
		$this->gsEventsRequest->update($wrapper);
181
	}
182
183
184
	/**
185
	 * @param GSEvent $event
186
	 * @param string $instance
187
	 * @param string $protocol
188
	 *
189
	 * @throws RequestContentException
190
	 * @throws RequestNetworkException
191
	 * @throws RequestResultNotJsonException
192
	 * @throws RequestResultSizeException
193
	 * @throws RequestServerException
194
	 */
195
	public function broadcastEvent(GSEvent $event, string $instance, string $protocol = ''): void {
196
		$this->signEvent($event);
197
198
		$path = $this->urlGenerator->linkToRoute('circles.GlobalScale.broadcast');
199
		$request = new Request($path, Request::TYPE_POST);
200
		if ($this->configService->getAppValue(ConfigService::CIRCLES_SELF_SIGNED) === '1') {
201
			$request->setVerifyPeer(false);
202
		}
203
204
		$protocols = ['https', 'http'];
205
		if ($protocol !== '') {
206
			$protocols = [$protocol];
207
		}
208
209
		$request->setProtocols($protocols);
210
		$request->setDataSerialize($event);
211
212
		$request->setAddress($instance);
213
214
		$data = $this->retrieveJson($request);
215
		$event->setResult(new SimpleDataStore($this->getArray('result', $data, [])));
216
	}
217
218
219
	/**
220
	 * @param GSEvent $event
221
	 *
222
	 * @throws RequestContentException
223
	 * @throws RequestNetworkException
224
	 * @throws RequestResultSizeException
225
	 * @throws RequestServerException
226
	 * @throws RequestResultNotJsonException
227
	 * @throws GlobalScaleEventException
228
	 */
229
	public function confirmEvent(GSEvent &$event): void {
230
		$this->signEvent($event);
231
232
		$circle = $event->getCircle();
233
		$owner = $circle->getOwner();
234
		$path = $this->urlGenerator->linkToRoute('circles.GlobalScale.event');
235
236
		$request = new Request($path, Request::TYPE_POST);
237
		if ($this->configService->getAppValue(ConfigService::CIRCLES_SELF_SIGNED) === '1') {
238
			$request->setVerifyPeer(false);
239
		}
240
		if ($this->get('REQUEST_SCHEME', $_SERVER) !== '') {
241
			$request->setProtocols([$_SERVER['REQUEST_SCHEME']]);
242
		} else {
243
			$request->setProtocols(['https', 'http']);
244
		}
245
		$request->setAddressFromUrl($owner->getInstance());
246
		$request->setDataSerialize($event);
247
248
		$result = $this->retrieveJson($request);
249
		$this->miscService->log('result ' . json_encode($result));
250
		if ($this->getInt('status', $result) === 0) {
251
			throw new GlobalScaleEventException($this->get('error', $result));
252
		}
253
254
		$updatedData = $this->getArray('event', $result);
255
		$this->miscService->log('updatedEvent: ' . json_encode($updatedData));
256
		if (!empty($updatedData)) {
257
			$updated = new GSEvent();
258
			try {
259
				$updated->import($updatedData);
260
				$event = $updated;
261
			} catch (Exception $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
262
			}
263
		}
264
	}
265
266
267
	/**
268
	 * @param GSEvent $event
269
	 */
270
	private function signEvent(GSEvent $event) {
271
		$event->setKey($this->globalScaleService->getKey());
272
	}
273
274
275
	/**
276
	 * We check that the event can be managed/checked locally or if the owner of the circle belongs to
277
	 * an other instance of Nextcloud
278
	 *
279
	 * @param GSEvent $event
280
	 *asyncBroadcast
281
	 *
282
	 * @return bool
283
	 */
284
	private function isLocalEvent(GSEvent $event): bool {
285
		if ($event->isLocal()) {
286
			return true;
287
		}
288
289
		$circle = $event->getCircle();
290
		$owner = $circle->getOwner();
291
		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...
292
			|| in_array(
293
				$owner->getInstance(), $this->configService->getTrustedDomains()
294
			)) {
295
			return true;
296
		}
297
298
		return false;
299
	}
300
301
302
	/**
303
	 * @param string $token
304
	 *
305
	 * @return GSWrapper[]
306
	 * @throws JsonException
307
	 * @throws ModelException
308
	 */
309
	public function getEventsByToken(string $token): array {
310
		return $this->gsEventsRequest->getByToken($token);
311
	}
312
313
314
	/**
315
	 * Deprecated ?
316
	 * should be used to manage results from events, like sending mails on user creation
317
	 *
318
	 * @param string $token
319
	 */
320
	public function manageResults(string $token): void {
321
		try {
322
			$wrappers = $this->gsEventsRequest->getByToken($token);
323
		} catch (JsonException | ModelException $e) {
324
			return;
325
		}
326
327
		$event = null;
328
		$events = [];
329
		foreach ($wrappers as $wrapper) {
330
			if ($wrapper->getStatus() !== GSWrapper::STATUS_DONE) {
331
				return;
332
			}
333
334
			$events[$wrapper->getInstance()] = $event = $wrapper->getEvent();
335
		}
336
337
		if ($event === null) {
338
			return;
339
		}
340
341
		try {
342
			$gs = $this->globalScaleService->getGlobalScaleEvent($event);
343
			$gs->result($events);
344
		} catch (GlobalScaleEventException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
345
		}
346
	}
347
348
349
	/**
350
	 * @throws GSStatusException
351
	 */
352
	public function synchronize() {
353
		$this->configService->getGSStatus();
354
355
		$sync = $this->getCirclesToSync();
356
		$this->synchronizeCircles($sync);
357
		$this->removeDeprecatedCircles();
358
359
		$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...
360
	}
361
362
363
	/**
364
	 * @param array $circles
365
	 */
366
	public function synchronizeCircles(array $circles): void {
367
		$event = new GSEvent(GSEvent::GLOBAL_SYNC, true);
368
		$event->setSource($this->configService->getLocalCloudId());
369
		$event->setData(new SimpleDataStore($circles));
370
371
		foreach ($this->globalScaleService->getInstances() as $instance) {
372
			try {
373
				$this->broadcastEvent($event, $instance);
374
			} 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...
375
			}
376
		}
377
	}
378
379
380
	/**
381
	 * @return Circle[]
382
	 */
383
	private function getCirclesToSync(): array {
384
		$circles = $this->circlesRequest->forceGetCircles();
385
386
		$sync = [];
387
		foreach ($circles as $circle) {
388
			if ($circle->getOwner()
389
					   ->getInstance() !== ''
390
				|| $circle->getType() === Circle::CIRCLES_PERSONAL) {
391
				continue;
392
			}
393
394
			$members = $this->membersRequest->forceGetMembers($circle->getUniqueId());
395
			$circle->setMembers($members);
396
397
			$sync[] = $circle;
398
		}
399
400
		return $sync;
401
	}
402
403
404
	/**
405
	 *
406
	 */
407
	private function removeDeprecatedCircles() {
408
		$knownCircles = $this->circlesRequest->forceGetCircles();
409
		foreach ($knownCircles as $knownItem) {
410
			if ($knownItem->getOwner()
411
						  ->getInstance() === '') {
412
				continue;
413
			}
414
415
			try {
416
				$this->checkCircle($knownItem);
417
			} catch (GSStatusException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
418
			}
419
		}
420
	}
421
422
423
	/**
424
	 * @param Circle $circle
425
	 *
426
	 * @throws GSStatusException
427
	 */
428
	private function checkCircle(Circle $circle): void {
429
		$status = $this->confirmCircleStatus($circle);
430
431
		if (!$status) {
432
			$this->circlesRequest->destroyCircle($circle->getUniqueId());
433
			$this->membersRequest->removeAllFromCircle($circle->getUniqueId());
434
		}
435
	}
436
437
438
	/**
439
	 * @param Circle $circle
440
	 *
441
	 * @return bool
442
	 * @throws GSStatusException
443
	 */
444
	public function confirmCircleStatus(Circle $circle): bool {
445
		$event = new GSEvent(GSEvent::CIRCLE_STATUS, true);
446
		$event->setSource($this->configService->getLocalCloudId());
447
		$event->setCircle($circle);
448
449
		$this->signEvent($event);
450
451
		$path = $this->urlGenerator->linkToRoute('circles.GlobalScale.status');
452
		$request = new Request($path, Request::TYPE_POST);
453
		if ($this->configService->getAppValue(ConfigService::CIRCLES_SELF_SIGNED) === '1') {
454
			$request->setVerifyPeer(false);
455
		}
456
457
		$request->setProtocols(['https', 'http']);
458
		$request->setDataSerialize($event);
459
460
		$requestIssue = false;
461
		$notFound = false;
462
		$foundWithNoOwner = false;
463
		foreach ($this->globalScaleService->getInstances() as $instance) {
464
			$request->setAddress($instance);
465
466
			try {
467
				$result = $this->retrieveJson($request);
468
//				$this->miscService->log('result: ' . json_encode($result));
469
				if ($this->getInt('status', $result, 0) !== 1) {
470
					throw new RequestContentException('result status is not good');
471
				}
472
473
				$status = $this->getInt('success.data.status', $result);
474
475
				// if error, we assume the circle might still exist.
476
				if ($status === CircleStatus::STATUS_ERROR) {
477
					return true;
478
				}
479
480
				if ($status === CircleStatus::STATUS_OK) {
481
					return true;
482
				}
483
484
				// TODO: check the data.supposedOwner entry.
485
				if ($status === CircleStatus::STATUS_NOT_OWNER) {
486
					$foundWithNoOwner = true;
487
				}
488
489
				if ($status === CircleStatus::STATUS_NOT_FOUND) {
490
					$notFound = true;
491
				}
492
493
			} catch (RequestContentException
494
			| RequestNetworkException
495
			| RequestResultNotJsonException
496
			| RequestResultSizeException
497
			| RequestServerException $e) {
498
				$requestIssue = true;
499
				// TODO: log instances that have network issue, after too many tries (7d), remove this circle.
500
				continue;
501
			}
502
		}
503
504
		// if no request issue, we can imagine that the instance that owns the circle is down.
505
		// We'll wait for more information (cf request exceptions management);
506
		if ($requestIssue) {
507
			return true;
508
		}
509
510
		// circle were not found in any other instances, we can easily says that the circle does not exists anymore
511
		if ($notFound && !$foundWithNoOwner) {
512
			return false;
513
		}
514
515
		// circle were found everywhere but with no owner on every instance. we need to assign a new owner.
516
		// This should be done by checking admin rights. if no admin rights, let's assume that circle should be removed.
517
		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...
518
			// TODO: assign a new owner and check that when changing owner, we do check that the destination instance is updated FOR SURE!
519
			return true;
520
		}
521
522
		// some instances returned notFound, some returned circle with no owner. let's assume the circle is deprecated.
523
		return false;
524
	}
525
526
	/**
527
	 * @throws GSStatusException
528
	 */
529
	public function syncEvents() {
530
531
	}
532
533
	/**
534
	 *
535
	 */
536
	private function removeDeprecatedEvents() {
537
//		$this->deprecatedEvents();
538
539
	}
540
541
542
}
543
544