Completed
Pull Request — master (#551)
by Maxence
01:54
created

RemoteUpstreamService::checkCircle()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 8
rs 10
c 0
b 0
f 0
cc 2
nc 2
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 Exception;
47
use OCA\Circles\Db\DeprecatedCirclesRequest;
48
use OCA\Circles\Db\DeprecatedMembersRequest;
49
use OCA\Circles\Db\RemoteWrapperRequest;
50
use OCA\Circles\Exceptions\GSStatusException;
51
use OCA\Circles\Exceptions\RemoteNotFoundException;
52
use OCA\Circles\Exceptions\RemoteResourceNotFoundException;
53
use OCA\Circles\Exceptions\UnknownRemoteException;
54
use OCA\Circles\GlobalScale\CircleStatus;
55
use OCA\Circles\Model\DeprecatedCircle;
56
use OCA\Circles\Model\GlobalScale\GSEvent;
57
use OCA\Circles\Model\GlobalScale\GSWrapper;
58
use OCA\Circles\Model\Remote\RemoteEvent;
59
use OCA\Circles\Model\Remote\RemoteInstance;
60
use OCA\Circles\Model\Remote\RemoteWrapper;
61
use OCP\IURLGenerator;
62
63
64
/**
65
 * Class RemoteUpstreamService
66
 *
67
 * @package OCA\Circles\Service
68
 */
69
class RemoteUpstreamService {
70
71
72
	use TNC21Request;
73
74
75
	/** @var IURLGenerator */
76
	private $urlGenerator;
77
78
	/** @var RemoteWrapperRequest */
79
	private $remoteWrapperRequest;
80
81
	/** @var DeprecatedCirclesRequest */
82
	private $circlesRequest;
83
84
	/** @var DeprecatedMembersRequest */
85
	private $membersRequest;
86
87
	/** @var RemoteService */
88
	private $remoteService;
89
90
	/** @var RemoteEventService */
91
	private $remoteEventService;
92
93
	/** @var ConfigService */
94
	private $configService;
95
96
97
	public function __construct(
98
		IURLGenerator $urlGenerator,
99
		RemoteWrapperRequest $remoteWrapperRequest,
100
		DeprecatedCirclesRequest $circlesRequest,
101
		DeprecatedMembersRequest $membersRequest,
102
		RemoteService $remoteService,
103
		RemoteEventService $remoteEventService,
104
		ConfigService $configService
105
	) {
106
		$this->urlGenerator = $urlGenerator;
107
		$this->remoteWrapperRequest = $remoteWrapperRequest;
108
		$this->circlesRequest = $circlesRequest;
109
		$this->membersRequest = $membersRequest;
110
		$this->remoteService = $remoteService;
111
		$this->remoteEventService = $remoteEventService;
112
		$this->configService = $configService;
113
	}
114
115
116
	/**
117
	 * @param RemoteWrapper $wrapper
118
	 */
119
	public function broadcastWrapper(RemoteWrapper $wrapper): void {
120
		$status = RemoteWrapper::STATUS_FAILED;
121
122
		try {
123
			$this->broadcastEvent($wrapper->getEvent(), $wrapper->getInstance());
124
			$status = GSWrapper::STATUS_DONE;
125
		} catch (Exception $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
126
		}
127
128
		if ($wrapper->getSeverity() === GSEvent::SEVERITY_HIGH) {
129
			$wrapper->setStatus($status);
130
		} else {
131
			$wrapper->setStatus(GSWrapper::STATUS_OVER);
132
		}
133
134
		$this->remoteWrapperRequest->update($wrapper);
135
	}
136
137
138
	/**
139
	 * @param RemoteEvent $event
140
	 * @param string $instance
141
	 *
142
	 * @throws RequestNetworkException
143
	 * @throws SignatoryException
144
	 * @throws SignatureException
145
	 * @throws RemoteNotFoundException
146
	 * @throws RemoteResourceNotFoundException
147
	 * @throws UnknownRemoteException
148
	 */
149
	public function broadcastEvent(RemoteEvent $event, string $instance): void {
150
//		if ($this->configService->isLocalInstance($instance)) {
151
//			$request = new NC21Request('', Request::TYPE_POST);
152
//			$this->configService->configureRequest($request, 'circles.RemoteWrapper.broadcast');
153
//		} else {
154
//			$path = $this->urlGenerator->linkToRoute('circles.RemoteWrapper.broadcast');
155
//			$request = new NC21Request($path, Request::TYPE_POST);
156
//			$this->configService->configureRequest($request);
157
//			$request->setInstance($instance);
158
//		}
159
160
//		$request->setDataSerialize($event);
161
162
		$data = $this->remoteService->requestRemoteInstance(
163
			$instance,
164
			RemoteInstance::INCOMING,
165
			Request::TYPE_POST,
166
			$event
167
		);
168
169
		$event->setResult(new SimpleDataStore($this->getArray('incoming', $data, [])));
170
	}
171
172
173
	/**
174
	 * @param string $token
175
	 *
176
	 * @return RemoteWrapper[]
177
	 */
178
	public function getEventsByToken(string $token): array {
179
		return $this->remoteWrapperRequest->getByToken($token);
180
	}
181
182
183
184
	//
185
	//
186
	//
187
	//
188
	//
189
190
	/**
191
	 * @param array $sync
192
	 *
193
	 * @throws GSStatusException
194
	 */
195
	public function synchronize(array $sync) {
196
		$this->configService->getGSStatus();
197
198
		$this->synchronizeCircles($sync);
199
		$this->removeDeprecatedCircles();
200
		$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...
201
	}
202
203
204
	/**
205
	 * @param array $circles
206
	 */
207
	public function synchronizeCircles(array $circles): void {
208
		$event = new GSEvent(GSEvent::GLOBAL_SYNC, true);
209
		$event->setSource($this->configService->getLocalInstance());
210
		$event->setData(new SimpleDataStore($circles));
211
212
		foreach ($this->remoteEventService->getInstances() as $instance) {
213
			try {
214
				$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...
215
			} 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...
216
			}
217
		}
218
	}
219
220
221
	/**
222
	 *
223
	 */
224
	private function removeDeprecatedCircles() {
225
		$knownCircles = $this->circlesRequest->forceGetCircles();
226
		foreach ($knownCircles as $knownItem) {
227
			if ($knownItem->getOwner()
228
						  ->getInstance() === '') {
229
				continue;
230
			}
231
232
			try {
233
				$this->checkCircle($knownItem);
234
			} catch (GSStatusException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
235
			}
236
		}
237
	}
238
239
240
	/**
241
	 * @param DeprecatedCircle $circle
242
	 *
243
	 * @throws GSStatusException
244
	 */
245
	private function checkCircle(DeprecatedCircle $circle): void {
246
		$status = $this->confirmCircleStatus($circle);
247
248
		if (!$status) {
249
			$this->circlesRequest->destroyCircle($circle->getUniqueId());
250
			$this->membersRequest->removeAllFromCircle($circle->getUniqueId());
251
		}
252
	}
253
254
255
	/**
256
	 * @param DeprecatedCircle $circle
257
	 *
258
	 * @return bool
259
	 * @throws GSStatusException
260
	 */
261
	public function confirmCircleStatus(DeprecatedCircle $circle): bool {
262
		$event = new GSEvent(GSEvent::CIRCLE_STATUS, true);
263
		$event->setSource($this->configService->getLocalInstance());
264
		$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...
265
266
		$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...
267
268
		$path = $this->urlGenerator->linkToRoute('circles.RemoteWrapper.status');
269
		$request = new NC21Request($path, Request::TYPE_POST);
270
		$this->configService->configureRequest($request);
271
		$request->setDataSerialize($event);
272
273
		$requestIssue = false;
274
		$notFound = false;
275
		$foundWithNoOwner = false;
276
		foreach ($this->remoteEventService->getInstances() as $instance) {
277
			$request->setHost($instance);
278
279
			try {
280
				$result = $this->retrieveJson($request);
281
				if ($this->getInt('status', $result, 0) !== 1) {
282
					throw new RequestContentException('result status is not good');
283
				}
284
285
				$status = $this->getInt('success.data.status', $result);
286
287
				// if error, we assume the circle might still exist.
288
				if ($status === CircleStatus::STATUS_ERROR) {
289
					return true;
290
				}
291
292
				if ($status === CircleStatus::STATUS_OK) {
293
					return true;
294
				}
295
296
				// TODO: check the data.supposedOwner entry.
297
				if ($status === CircleStatus::STATUS_NOT_OWNER) {
298
					$foundWithNoOwner = true;
299
				}
300
301
				if ($status === CircleStatus::STATUS_NOT_FOUND) {
302
					$notFound = true;
303
				}
304
305
			} catch (RequestContentException
306
			| RequestNetworkException
307
			| RequestResultNotJsonException
308
			| RequestResultSizeException
309
			| RequestServerException $e) {
310
				$requestIssue = true;
311
				// TODO: log instances that have network issue, after too many tries (7d), remove this circle.
312
				continue;
313
			}
314
		}
315
316
		// if no request issue, we can imagine that the instance that owns the circle is down.
317
		// We'll wait for more information (cf request exceptions management);
318
		if ($requestIssue) {
319
			return true;
320
		}
321
322
		// circle were not found in any other instances, we can easily says that the circle does not exists anymore
323
		if ($notFound && !$foundWithNoOwner) {
324
			return false;
325
		}
326
327
		// circle were found everywhere but with no owner on every instance. we need to assign a new owner.
328
		// This should be done by checking admin rights. if no admin rights, let's assume that circle should be removed.
329
		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...
330
			// TODO: assign a new owner and check that when changing owner, we do check that the destination instance is updated FOR SURE!
331
			return true;
332
		}
333
334
		// some instances returned notFound, some returned circle with no owner. let's assume the circle is deprecated.
335
		return false;
336
	}
337
338
	/**
339
	 * @throws GSStatusException
340
	 */
341
	public function syncEvents() {
342
343
	}
344
345
	/**
346
	 *
347
	 */
348
	private function removeDeprecatedEvents() {
349
//		$this->deprecatedEvents();
350
351
	}
352
353
354
}
355
356