Completed
Pull Request — master (#551)
by Maxence
02:16
created

RemoteUpstreamService::broadcastWrapper()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 17
rs 9.7
c 0
b 0
f 0
cc 3
nc 4
nop 1
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 daita\MySmallPhpTools\Exceptions\RequestContentException;
36
use daita\MySmallPhpTools\Exceptions\RequestNetworkException;
37
use daita\MySmallPhpTools\Exceptions\RequestResultNotJsonException;
38
use daita\MySmallPhpTools\Exceptions\RequestResultSizeException;
39
use daita\MySmallPhpTools\Exceptions\RequestServerException;
40
use daita\MySmallPhpTools\Exceptions\SignatoryException;
41
use daita\MySmallPhpTools\Exceptions\SignatureException;
42
use daita\MySmallPhpTools\Model\Nextcloud\nc21\NC21Request;
43
use daita\MySmallPhpTools\Model\Request;
44
use daita\MySmallPhpTools\Model\SimpleDataStore;
45
use daita\MySmallPhpTools\Traits\Nextcloud\nc21\TNC21Request;
46
use OCA\Circles\Db\DeprecatedCirclesRequest;
47
use OCA\Circles\Db\DeprecatedMembersRequest;
48
use OCA\Circles\Db\RemoteWrapperRequest;
49
use OCA\Circles\Exceptions\GSStatusException;
50
use OCA\Circles\GlobalScale\CircleStatus;
51
use OCA\Circles\Model\DeprecatedCircle;
52
use OCA\Circles\Model\GlobalScale\GSEvent;
53
use OCA\Circles\Model\GlobalScale\GSWrapper;
54
use OCA\Circles\Model\Remote\RemoteEvent;
55
use OCA\Circles\Model\Remote\RemoteWrapper;
56
use OCP\IURLGenerator;
57
58
59
/**
60
 * Class RemoteUpstreamService
61
 *
62
 * @package OCA\Circles\Service
63
 */
64
class RemoteUpstreamService {
65
66
67
	use TNC21Request;
68
69
70
	/** @var IURLGenerator */
71
	private $urlGenerator;
72
73
	/** @var RemoteWrapperRequest */
74
	private $remoteWrapperRequest;
75
76
	/** @var DeprecatedCirclesRequest */
77
	private $circlesRequest;
78
79
	/** @var DeprecatedMembersRequest */
80
	private $membersRequest;
81
82
	/** @var RemoteService */
83
	private $remoteService;
84
85
	/** @var RemoteEventService */
86
	private $remoteEventService;
87
88
	/** @var ConfigService */
89
	private $configService;
90
91
92
	public function __construct(
93
		IURLGenerator $urlGenerator,
94
		RemoteWrapperRequest $remoteWrapperRequest,
95
		DeprecatedCirclesRequest $circlesRequest,
96
		DeprecatedMembersRequest $membersRequest,
97
		RemoteService $remoteService,
98
		RemoteEventService $remoteEventService,
99
		ConfigService $configService
100
	) {
101
		$this->urlGenerator = $urlGenerator;
102
		$this->remoteWrapperRequest = $remoteWrapperRequest;
103
		$this->circlesRequest = $circlesRequest;
104
		$this->membersRequest = $membersRequest;
105
		$this->remoteService = $remoteService;
106
		$this->remoteEventService = $remoteEventService;
107
		$this->configService = $configService;
108
	}
109
110
111
	/**
112
	 * @param RemoteWrapper $wrapper
113
	 */
114
	public function broadcastWrapper(RemoteWrapper $wrapper): void {
115
		$status = RemoteWrapper::STATUS_FAILED;
116
117
		try {
118
			$this->broadcastEvent($wrapper->getEvent(), $wrapper->getInstance());
119
			$status = GSWrapper::STATUS_DONE;
120
		} catch (RequestNetworkException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
121
		}
122
123
		if ($wrapper->getSeverity() === GSEvent::SEVERITY_HIGH) {
124
			$wrapper->setStatus($status);
125
		} else {
126
			$wrapper->setStatus(GSWrapper::STATUS_OVER);
127
		}
128
129
		$this->remoteWrapperRequest->update($wrapper);
130
	}
131
132
133
	/**
134
	 * @param RemoteEvent $event
135
	 * @param string $instance
136
	 *
137
	 * @throws RequestNetworkException
138
	 * @throws SignatoryException
139
	 * @throws SignatureException
140
	 */
141
	public function broadcastEvent(RemoteEvent $event, string $instance): void {
142
		if ($this->configService->isLocalInstance($instance)) {
143
			$request = new NC21Request('', Request::TYPE_POST);
144
			$this->configService->configureRequest($request, 'circles.RemoteWrapper.broadcast');
145
		} else {
146
			$path = $this->urlGenerator->linkToRoute('circles.RemoteWrapper.broadcast');
147
			$request = new NC21Request($path, Request::TYPE_POST);
148
			$this->configService->configureRequest($request);
149
			$request->setInstance($instance);
150
		}
151
152
		$request->setDataSerialize($event);
153
154
		$data = $this->remoteService->signAndRetrieveJson($request);
155
		$event->setResult(new SimpleDataStore($this->getArray('result', $data, [])));
156
	}
157
158
159
	/**
160
	 * @param string $token
161
	 *
162
	 * @return RemoteWrapper[]
163
	 */
164
	public function getEventsByToken(string $token): array {
165
		return $this->remoteWrapperRequest->getByToken($token);
166
	}
167
168
169
170
	//
171
	//
172
	//
173
	//
174
	//
175
176
	/**
177
	 * @param array $sync
178
	 *
179
	 * @throws GSStatusException
180
	 */
181
	public function synchronize(array $sync) {
182
		$this->configService->getGSStatus();
183
184
		$this->synchronizeCircles($sync);
185
		$this->removeDeprecatedCircles();
186
		$this->removeDeprecatedEvents();
0 ignored issues
show
Unused Code introduced by
The call to the method OCA\Circles\Service\Remo...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...
187
	}
188
189
190
	/**
191
	 * @param array $circles
192
	 */
193
	public function synchronizeCircles(array $circles): void {
194
		$event = new GSEvent(GSEvent::GLOBAL_SYNC, true);
195
		$event->setSource($this->configService->getLocalInstance());
196
		$event->setData(new SimpleDataStore($circles));
197
198
		foreach ($this->remoteEventService->getInstances() as $instance) {
199
			try {
200
				$this->broadcastEvent($event, $instance);
0 ignored issues
show
Documentation introduced by
$event is of type object<OCA\Circles\Model\GlobalScale\GSEvent>, but the function expects a object<OCA\Circles\Model\Remote\RemoteEvent>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
201
			} 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...
202
			}
203
		}
204
	}
205
206
207
	/**
208
	 *
209
	 */
210
	private function removeDeprecatedCircles() {
211
		$knownCircles = $this->circlesRequest->forceGetCircles();
212
		foreach ($knownCircles as $knownItem) {
213
			if ($knownItem->getOwner()
214
						  ->getInstance() === '') {
215
				continue;
216
			}
217
218
			try {
219
				$this->checkCircle($knownItem);
220
			} catch (GSStatusException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
221
			}
222
		}
223
	}
224
225
226
	/**
227
	 * @param DeprecatedCircle $circle
228
	 *
229
	 * @throws GSStatusException
230
	 */
231
	private function checkCircle(DeprecatedCircle $circle): void {
232
		$status = $this->confirmCircleStatus($circle);
233
234
		if (!$status) {
235
			$this->circlesRequest->destroyCircle($circle->getUniqueId());
236
			$this->membersRequest->removeAllFromCircle($circle->getUniqueId());
237
		}
238
	}
239
240
241
	/**
242
	 * @param DeprecatedCircle $circle
243
	 *
244
	 * @return bool
245
	 * @throws GSStatusException
246
	 */
247
	public function confirmCircleStatus(DeprecatedCircle $circle): bool {
248
		$event = new GSEvent(GSEvent::CIRCLE_STATUS, true);
249
		$event->setSource($this->configService->getLocalInstance());
250
		$event->setDeprecatedCircle($circle);
0 ignored issues
show
Deprecated Code introduced by
The method OCA\Circles\Model\Global...::setDeprecatedCircle() has been deprecated.

This method has been deprecated.

Loading history...
251
252
		$this->signEvent($event);
0 ignored issues
show
Bug introduced by
The method signEvent() does not seem to exist on object<OCA\Circles\Service\RemoteUpstreamService>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
253
254
		$path = $this->urlGenerator->linkToRoute('circles.RemoteWrapper.status');
255
		$request = new NC21Request($path, Request::TYPE_POST);
256
		$this->configService->configureRequest($request);
257
		$request->setDataSerialize($event);
258
259
		$requestIssue = false;
260
		$notFound = false;
261
		$foundWithNoOwner = false;
262
		foreach ($this->remoteEventService->getInstances() as $instance) {
263
			$request->setHost($instance);
264
265
			try {
266
				$result = $this->retrieveJson($request);
267
				if ($this->getInt('status', $result, 0) !== 1) {
268
					throw new RequestContentException('result status is not good');
269
				}
270
271
				$status = $this->getInt('success.data.status', $result);
272
273
				// if error, we assume the circle might still exist.
274
				if ($status === CircleStatus::STATUS_ERROR) {
275
					return true;
276
				}
277
278
				if ($status === CircleStatus::STATUS_OK) {
279
					return true;
280
				}
281
282
				// TODO: check the data.supposedOwner entry.
283
				if ($status === CircleStatus::STATUS_NOT_OWNER) {
284
					$foundWithNoOwner = true;
285
				}
286
287
				if ($status === CircleStatus::STATUS_NOT_FOUND) {
288
					$notFound = true;
289
				}
290
291
			} catch (RequestContentException
292
			| RequestNetworkException
293
			| RequestResultNotJsonException
294
			| RequestResultSizeException
295
			| RequestServerException $e) {
296
				$requestIssue = true;
297
				// TODO: log instances that have network issue, after too many tries (7d), remove this circle.
298
				continue;
299
			}
300
		}
301
302
		// if no request issue, we can imagine that the instance that owns the circle is down.
303
		// We'll wait for more information (cf request exceptions management);
304
		if ($requestIssue) {
305
			return true;
306
		}
307
308
		// circle were not found in any other instances, we can easily says that the circle does not exists anymore
309
		if ($notFound && !$foundWithNoOwner) {
310
			return false;
311
		}
312
313
		// circle were found everywhere but with no owner on every instance. we need to assign a new owner.
314
		// This should be done by checking admin rights. if no admin rights, let's assume that circle should be removed.
315
		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...
316
			// TODO: assign a new owner and check that when changing owner, we do check that the destination instance is updated FOR SURE!
317
			return true;
318
		}
319
320
		// some instances returned notFound, some returned circle with no owner. let's assume the circle is deprecated.
321
		return false;
322
	}
323
324
	/**
325
	 * @throws GSStatusException
326
	 */
327
	public function syncEvents() {
328
329
	}
330
331
	/**
332
	 *
333
	 */
334
	private function removeDeprecatedEvents() {
335
//		$this->deprecatedEvents();
336
337
	}
338
339
340
}
341
342