Passed
Push — master ( 3eb748...99ee00 )
by Roeland
12:40 queued 12s
created

StatusService   A

Complexity

Total Complexity 41

Size/Duplication

Total Lines 342
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 133
dl 0
loc 342
rs 9.1199
c 0
b 0
f 0
wmc 41

15 Methods

Rating   Name   Duplication   Size   Complexity  
A setPredefinedMessage() 0 32 6
A findAllRecentStatusChanges() 0 4 1
A cleanStatus() 0 6 1
A cleanStatusMessage() 0 7 1
A __construct() 0 8 1
B setCustomMessage() 0 37 8
B processStatus() 0 15 7
A setStatus() 0 28 5
A findByUserIds() 0 4 1
A clearMessage() 0 15 2
A removeUserStatus() 0 10 2
A findByUserId() 0 2 1
A findAll() 0 4 1
A clearStatus() 0 14 2
A addDefaultMessage() 0 6 2

How to fix   Complexity   

Complex Class

Complex classes like StatusService often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use StatusService, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
declare(strict_types=1);
4
5
/**
6
 * @copyright Copyright (c) 2020, Georg Ehrke
7
 *
8
 * @author Georg Ehrke <[email protected]>
9
 *
10
 * @license AGPL-3.0
11
 *
12
 * This code is free software: you can redistribute it and/or modify
13
 * it under the terms of the GNU Affero General Public License, version 3,
14
 * as published by the Free Software Foundation.
15
 *
16
 * This program is distributed in the hope that it will be useful,
17
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19
 * GNU Affero General Public License for more details.
20
 *
21
 * You should have received a copy of the GNU Affero General Public License, version 3,
22
 * along with this program. If not, see <http://www.gnu.org/licenses/>
23
 *
24
 */
25
26
namespace OCA\UserStatus\Service;
27
28
use OCA\UserStatus\Db\UserStatus;
29
use OCA\UserStatus\Db\UserStatusMapper;
30
use OCA\UserStatus\Exception\InvalidClearAtException;
31
use OCA\UserStatus\Exception\InvalidMessageIdException;
32
use OCA\UserStatus\Exception\InvalidStatusIconException;
33
use OCA\UserStatus\Exception\InvalidStatusTypeException;
34
use OCA\UserStatus\Exception\StatusMessageTooLongException;
35
use OCP\AppFramework\Db\DoesNotExistException;
36
use OCP\AppFramework\Utility\ITimeFactory;
37
use OCP\UserStatus\IUserStatus;
38
39
/**
40
 * Class StatusService
41
 *
42
 * @package OCA\UserStatus\Service
43
 */
44
class StatusService {
45
46
	/** @var UserStatusMapper */
47
	private $mapper;
48
49
	/** @var ITimeFactory */
50
	private $timeFactory;
51
52
	/** @var PredefinedStatusService */
53
	private $predefinedStatusService;
54
55
	/** @var EmojiService */
56
	private $emojiService;
57
58
	/**
59
	 * List of priorities ordered by their priority
60
	 */
61
	public const PRIORITY_ORDERED_STATUSES = [
62
		IUserStatus::ONLINE,
63
		IUserStatus::AWAY,
64
		IUserStatus::DND,
65
		IUserStatus::INVISIBLE,
66
		IUserStatus::OFFLINE
67
	];
68
69
	/**
70
	 * List of statuses that persist the clear-up
71
	 * or UserLiveStatusEvents
72
	 */
73
	public const PERSISTENT_STATUSES = [
74
		IUserStatus::AWAY,
75
		IUserStatus::DND,
76
		IUserStatus::INVISIBLE,
77
	];
78
79
	/** @var int */
80
	public const INVALIDATE_STATUS_THRESHOLD = 5 /* minutes */ * 60 /* seconds */;
81
82
	/** @var int */
83
	public const MAXIMUM_MESSAGE_LENGTH = 80;
84
85
	/**
86
	 * StatusService constructor.
87
	 *
88
	 * @param UserStatusMapper $mapper
89
	 * @param ITimeFactory $timeFactory
90
	 * @param PredefinedStatusService $defaultStatusService,
91
	 * @param EmojiService $emojiService
92
	 */
93
	public function __construct(UserStatusMapper $mapper,
94
								ITimeFactory $timeFactory,
95
								PredefinedStatusService $defaultStatusService,
96
								EmojiService $emojiService) {
97
		$this->mapper = $mapper;
98
		$this->timeFactory = $timeFactory;
99
		$this->predefinedStatusService = $defaultStatusService;
100
		$this->emojiService = $emojiService;
101
	}
102
103
	/**
104
	 * @param int|null $limit
105
	 * @param int|null $offset
106
	 * @return UserStatus[]
107
	 */
108
	public function findAll(?int $limit = null, ?int $offset = null): array {
109
		return array_map(function ($status) {
110
			return $this->processStatus($status);
111
		}, $this->mapper->findAll($limit, $offset));
112
	}
113
114
	/**
115
	 * @param int|null $limit
116
	 * @param int|null $offset
117
	 * @return array
118
	 */
119
	public function findAllRecentStatusChanges(?int $limit = null, ?int $offset = null): array {
120
		return array_map(function ($status) {
121
			return $this->processStatus($status);
122
		}, $this->mapper->findAllRecent($limit, $offset));
123
	}
124
125
	/**
126
	 * @param string $userId
127
	 * @return UserStatus
128
	 * @throws DoesNotExistException
129
	 */
130
	public function findByUserId(string $userId):UserStatus {
131
		return $this->processStatus($this->mapper->findByUserId($userId));
132
	}
133
134
	/**
135
	 * @param array $userIds
136
	 * @return UserStatus[]
137
	 */
138
	public function findByUserIds(array $userIds):array {
139
		return array_map(function ($status) {
140
			return $this->processStatus($status);
141
		}, $this->mapper->findByUserIds($userIds));
142
	}
143
144
	/**
145
	 * @param string $userId
146
	 * @param string $status
147
	 * @param int|null $statusTimestamp
148
	 * @param bool $isUserDefined
149
	 * @return UserStatus
150
	 * @throws InvalidStatusTypeException
151
	 */
152
	public function setStatus(string $userId,
153
							  string $status,
154
							  ?int $statusTimestamp,
155
							  bool $isUserDefined): UserStatus {
156
		try {
157
			$userStatus = $this->mapper->findByUserId($userId);
158
		} catch (DoesNotExistException $ex) {
159
			$userStatus = new UserStatus();
160
			$userStatus->setUserId($userId);
161
		}
162
163
		// Check if status-type is valid
164
		if (!\in_array($status, self::PRIORITY_ORDERED_STATUSES, true)) {
165
			throw new InvalidStatusTypeException('Status-type "' . $status . '" is not supported');
166
		}
167
		if ($statusTimestamp === null) {
168
			$statusTimestamp = $this->timeFactory->getTime();
169
		}
170
171
		$userStatus->setStatus($status);
172
		$userStatus->setStatusTimestamp($statusTimestamp);
173
		$userStatus->setIsUserDefined($isUserDefined);
174
175
		if ($userStatus->getId() === null) {
0 ignored issues
show
introduced by
The condition $userStatus->getId() === null is always false.
Loading history...
176
			return $this->mapper->insert($userStatus);
177
		}
178
179
		return $this->mapper->update($userStatus);
180
	}
181
182
	/**
183
	 * @param string $userId
184
	 * @param string $messageId
185
	 * @param int|null $clearAt
186
	 * @return UserStatus
187
	 * @throws InvalidMessageIdException
188
	 * @throws InvalidClearAtException
189
	 */
190
	public function setPredefinedMessage(string $userId,
191
										 string $messageId,
192
										 ?int $clearAt): UserStatus {
193
		try {
194
			$userStatus = $this->mapper->findByUserId($userId);
195
		} catch (DoesNotExistException $ex) {
196
			$userStatus = new UserStatus();
197
			$userStatus->setUserId($userId);
198
			$userStatus->setStatus(IUserStatus::OFFLINE);
199
			$userStatus->setStatusTimestamp(0);
200
			$userStatus->setIsUserDefined(false);
201
		}
202
203
		if (!$this->predefinedStatusService->isValidId($messageId)) {
204
			throw new InvalidMessageIdException('Message-Id "' . $messageId . '" is not supported');
205
		}
206
207
		// Check that clearAt is in the future
208
		if ($clearAt !== null && $clearAt < $this->timeFactory->getTime()) {
209
			throw new InvalidClearAtException('ClearAt is in the past');
210
		}
211
212
		$userStatus->setMessageId($messageId);
213
		$userStatus->setCustomIcon(null);
214
		$userStatus->setCustomMessage(null);
215
		$userStatus->setClearAt($clearAt);
216
217
		if ($userStatus->getId() === null) {
0 ignored issues
show
introduced by
The condition $userStatus->getId() === null is always false.
Loading history...
218
			return $this->mapper->insert($userStatus);
219
		}
220
221
		return $this->mapper->update($userStatus);
222
	}
223
224
	/**
225
	 * @param string $userId
226
	 * @param string|null $statusIcon
227
	 * @param string|null $message
228
	 * @param int|null $clearAt
229
	 * @return UserStatus
230
	 * @throws InvalidClearAtException
231
	 * @throws InvalidStatusIconException
232
	 * @throws StatusMessageTooLongException
233
	 */
234
	public function setCustomMessage(string $userId,
235
									 ?string $statusIcon,
236
									 string $message,
237
									 ?int $clearAt): UserStatus {
238
		try {
239
			$userStatus = $this->mapper->findByUserId($userId);
240
		} catch (DoesNotExistException $ex) {
241
			$userStatus = new UserStatus();
242
			$userStatus->setUserId($userId);
243
			$userStatus->setStatus(IUserStatus::OFFLINE);
244
			$userStatus->setStatusTimestamp(0);
245
			$userStatus->setIsUserDefined(false);
246
		}
247
248
		// Check if statusIcon contains only one character
249
		if ($statusIcon !== null && !$this->emojiService->isValidEmoji($statusIcon)) {
250
			throw new InvalidStatusIconException('Status-Icon is longer than one character');
251
		}
252
		// Check for maximum length of custom message
253
		if (\mb_strlen($message) > self::MAXIMUM_MESSAGE_LENGTH) {
254
			throw new StatusMessageTooLongException('Message is longer than supported length of ' . self::MAXIMUM_MESSAGE_LENGTH . ' characters');
255
		}
256
		// Check that clearAt is in the future
257
		if ($clearAt !== null && $clearAt < $this->timeFactory->getTime()) {
258
			throw new InvalidClearAtException('ClearAt is in the past');
259
		}
260
261
		$userStatus->setMessageId(null);
262
		$userStatus->setCustomIcon($statusIcon);
263
		$userStatus->setCustomMessage($message);
264
		$userStatus->setClearAt($clearAt);
265
266
		if ($userStatus->getId() === null) {
0 ignored issues
show
introduced by
The condition $userStatus->getId() === null is always false.
Loading history...
267
			return $this->mapper->insert($userStatus);
268
		}
269
270
		return $this->mapper->update($userStatus);
271
	}
272
273
	/**
274
	 * @param string $userId
275
	 * @return bool
276
	 */
277
	public function clearStatus(string $userId): bool {
278
		try {
279
			$userStatus = $this->mapper->findByUserId($userId);
280
		} catch (DoesNotExistException $ex) {
281
			// if there is no status to remove, just return
282
			return false;
283
		}
284
285
		$userStatus->setStatus(IUserStatus::OFFLINE);
286
		$userStatus->setStatusTimestamp(0);
287
		$userStatus->setIsUserDefined(false);
288
289
		$this->mapper->update($userStatus);
290
		return true;
291
	}
292
293
	/**
294
	 * @param string $userId
295
	 * @return bool
296
	 */
297
	public function clearMessage(string $userId): bool {
298
		try {
299
			$userStatus = $this->mapper->findByUserId($userId);
300
		} catch (DoesNotExistException $ex) {
301
			// if there is no status to remove, just return
302
			return false;
303
		}
304
305
		$userStatus->setMessageId(null);
306
		$userStatus->setCustomMessage(null);
307
		$userStatus->setCustomIcon(null);
308
		$userStatus->setClearAt(null);
309
310
		$this->mapper->update($userStatus);
311
		return true;
312
	}
313
314
	/**
315
	 * @param string $userId
316
	 * @return bool
317
	 */
318
	public function removeUserStatus(string $userId): bool {
319
		try {
320
			$userStatus = $this->mapper->findByUserId($userId);
321
		} catch (DoesNotExistException $ex) {
322
			// if there is no status to remove, just return
323
			return false;
324
		}
325
326
		$this->mapper->delete($userStatus);
327
		return true;
328
	}
329
330
	/**
331
	 * Processes a status to check if custom message is still
332
	 * up to date and provides translated default status if needed
333
	 *
334
	 * @param UserStatus $status
335
	 * @returns UserStatus
336
	 */
337
	private function processStatus(UserStatus $status): UserStatus {
338
		$clearAt = $status->getClearAt();
339
340
		if ($status->getStatusTimestamp() < $this->timeFactory->getTime() - self::INVALIDATE_STATUS_THRESHOLD
341
			&& (!$status->getIsUserDefined() || $status->getStatus() === IUserStatus::ONLINE)) {
342
			$this->cleanStatus($status);
343
		}
344
		if ($clearAt !== null && $clearAt < $this->timeFactory->getTime()) {
345
			$this->cleanStatusMessage($status);
346
		}
347
		if ($status->getMessageId() !== null) {
0 ignored issues
show
introduced by
The condition $status->getMessageId() !== null is always true.
Loading history...
348
			$this->addDefaultMessage($status);
349
		}
350
351
		return $status;
352
	}
353
354
	/**
355
	 * @param UserStatus $status
356
	 */
357
	private function cleanStatus(UserStatus $status): void {
358
		$status->setStatus(IUserStatus::OFFLINE);
359
		$status->setStatusTimestamp($this->timeFactory->getTime());
360
		$status->setIsUserDefined(false);
361
362
		$this->mapper->update($status);
363
	}
364
365
	/**
366
	 * @param UserStatus $status
367
	 */
368
	private function cleanStatusMessage(UserStatus $status): void {
369
		$status->setMessageId(null);
370
		$status->setCustomIcon(null);
371
		$status->setCustomMessage(null);
372
		$status->setClearAt(null);
373
374
		$this->mapper->update($status);
375
	}
376
377
	/**
378
	 * @param UserStatus $status
379
	 */
380
	private function addDefaultMessage(UserStatus $status): void {
381
		// If the message is predefined, insert the translated message and icon
382
		$predefinedMessage = $this->predefinedStatusService->getDefaultStatusById($status->getMessageId());
383
		if ($predefinedMessage !== null) {
384
			$status->setCustomMessage($predefinedMessage['message']);
385
			$status->setCustomIcon($predefinedMessage['icon']);
386
		}
387
	}
388
}
389