ApiController   B
last analyzed

Complexity

Total Complexity 48

Size/Duplication

Total Lines 379
Duplicated Lines 0 %

Test Coverage

Coverage 0%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 170
dl 0
loc 379
ccs 0
cts 173
cp 0
rs 8.5599
c 1
b 0
f 0
wmc 48

10 Methods

Rating   Name   Duplication   Size   Complexity  
A getPersonsV2() 0 20 3
A updateCluster() 0 20 5
A getPerson() 0 32 5
A getPersons() 0 32 5
A __construct() 0 18 1
A getFacesByPerson() 0 25 4
C updatePerson() 0 32 12
A detachFace() 0 7 2
B discoverPerson() 0 45 7
A autocomplete() 0 23 4

How to fix   Complexity   

Complex Class

Complex classes like ApiController 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 ApiController, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * @copyright Copyright (c) 2021 Ming Tsang <[email protected]>
4
 * @copyright Copyright (c) 2022 Matias De lellis <[email protected]>
5
 *
6
 * @author Ming Tsang <[email protected]>
7
 *
8
 * @license GNU AGPL version 3 or any later version
9
 *
10
 * This program is free software: you can redistribute it and/or modify
11
 * it under the terms of the GNU Affero General Public License as
12
 * published by the Free Software Foundation, either version 3 of the
13
 * License, or (at your option) any later version.
14
 *
15
 * This program is distributed in the hope that it will be useful,
16
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18
 * GNU Affero General Public License for more details.
19
 *
20
 * You should have received a copy of the GNU Affero General Public License
21
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
22
 *
23
 */
24
25
namespace OCA\FaceRecognition\Controller;
26
27
use OCP\IRequest;
28
use OCP\Files\File;
29
30
use OCP\AppFramework\Http;
31
use OCP\AppFramework\Http\JSONResponse;
32
use OCP\AppFramework\ApiController as NCApiController;
33
34
use OCA\FaceRecognition\Db\Face;
35
use OCA\FaceRecognition\Db\FaceMapper;
36
37
use OCA\FaceRecognition\Db\Image;
38
use OCA\FaceRecognition\Db\ImageMapper;
39
40
use OCA\FaceRecognition\Db\Person;
41
use OCA\FaceRecognition\Db\PersonMapper;
42
43
use OCA\FaceRecognition\Service\SettingsService;
44
use OCA\FaceRecognition\Service\UrlService;
45
46
class ApiController extends NcApiController {
47
48
	/** @var FaceMapper */
49
	private $faceMapper;
50
51
	/** @var ImageMapper */
52
	private $imageMapper;
53
54
	/** @var PersonMapper */
55
	private $personMapper;
56
57
	/** @var SettingsService */
58
	private $settingsService;
59
60
	/** @var UrlService */
61
	private $urlService;
62
63
	/** @var string */
64
	private $userId;
65
66
	public function __construct(
67
		$AppName,
68
		IRequest        $request,
69
		FaceMapper      $faceMapper,
70
		ImageMapper     $imageMapper,
71
		PersonMapper    $personmapper,
72
		SettingsService $settingsService,
73
		UrlService      $urlService,
74
		$UserId)
75
	{
76
		parent::__construct($AppName, $request);
77
78
		$this->faceMapper      = $faceMapper;
79
		$this->imageMapper     = $imageMapper;
80
		$this->personMapper    = $personmapper;
81
		$this->settingsService = $settingsService;
82
		$this->urlService      = $urlService;
83
		$this->userId          = $UserId;
84
	}
85
86
	/**
87
	 * API V1
88
	 */
89
90
	/**
91
	 * Get all named persons
92
	 *
93
	 * - Endpoint: /persons
94
	 * - Method: GET
95
	 * - Response: Array of persons
96
	 * 		- Person:
97
	 * 			- name: Name of the person
98
	 * 			- thumbFaceId: Face representing this person
99
	 * 			- count: Number of images associated to this person
100
	 *
101
	 * @NoAdminRequired
102
	 *
103
	 * @return JSONResponse
104
	 */
105
	public function getPersons(): JSONResponse {
106
		$userEnabled = $this->settingsService->getUserEnabled($this->userId);
107
108
		$resp = array();
109
110
		if (!$userEnabled)
111
			return new JSONResponse($resp);
112
113
		$modelId = $this->settingsService->getCurrentFaceModel();
114
115
		$personsNames = $this->personMapper->findDistinctNames($this->userId, $modelId);
116
		foreach ($personsNames as $personNamed) {
117
			$facesCount = 0;
118
			$thumbFaceId = null;
119
			$persons = $this->personMapper->findByName($this->userId, $modelId, $personNamed->getName());
120
			foreach ($persons as $person) {
121
				$personFaces = $this->faceMapper->findFromCluster($this->userId, $person->getId(), $modelId);
122
				if (is_null($thumbFaceId)) {
123
					$thumbFaceId = $personFaces[0]->getId();
124
				}
125
				$facesCount += count($personFaces);
126
			}
127
128
			$respPerson = [];
129
			$respPerson['name'] = $personNamed->getName();
130
			$respPerson['thumbFaceId'] = $thumbFaceId;
131
			$respPerson['count'] = $facesCount;
132
133
			$resp[] = $respPerson;
134
		}
135
136
		return new JSONResponse($resp);
137
	}
138
139
	/**
140
	 * Get all faces associated to a person
141
	 *
142
	 * - Endpoint: /person/<name>/faces
143
	 * - Method: GET
144
	 * - URL Arguments: name - (string) name of the person
145
	 * - Response: Array of faces
146
	 * 		- Face:
147
	 * 			- id: Face ID
148
	 * 			- fileId: The file where this face was found
149
	 *
150
	 * @NoAdminRequired
151
	 *
152
	 * @return JSONResponse
153
	 */
154
	public function getFacesByPerson(string $name): JSONResponse {
155
		$userEnabled = $this->settingsService->getUserEnabled($this->userId);
156
157
		$resp = array();
158
159
		if (!$userEnabled)
160
			return new JSONResponse($resp);
161
162
		$modelId = $this->settingsService->getCurrentFaceModel();
163
164
		$clusters = $this->personMapper->findByName($this->userId, $modelId, $name);
165
		foreach ($clusters as $cluster) {
166
			$faces = $this->faceMapper->findFromCluster($this->userId, $cluster->getId(), $modelId);
167
			foreach ($faces as $face) {
168
				$image = $this->imageMapper->find($this->userId, $face->getImage());
169
170
				$respFace = [];
171
				$respFace['id'] = $face->getId();
172
				$respFace['fileId'] = $image->getFile();
173
174
				$resp[] = $respFace;
175
			}
176
		}
177
178
		return new JSONResponse($resp);
179
	}
180
181
	/**
182
	 * API V2
183
	 */
184
185
	/**
186
	 * @NoAdminRequired
187
	 * @CORS
188
	 * @NoCSRFRequired
189
	 *
190
	 * @return JSONResponse
191
	 */
192
	public function getPersonsV2($thumb_size = 128): JSONResponse {
193
		if (!$this->settingsService->getUserEnabled($this->userId))
194
			return new JSONResponse([], Http::STATUS_PRECONDITION_FAILED);
195
196
		$list = [];
197
		$modelId = $this->settingsService->getCurrentFaceModel();
198
		$personsNames = $this->personMapper->findDistinctNames($this->userId, $modelId);
199
		foreach ($personsNames as $personNamed) {
200
			$name = $personNamed->getName();
201
			$personFace = current($this->faceMapper->findFromPerson($this->userId, $name, $modelId, 1));
202
203
			$person = [];
204
			$person['name'] = $name;
205
			$person['thumbUrl'] = $this->urlService->getThumbUrl($personFace->getId(), $thumb_size);
206
			$person['count'] = $this->imageMapper->countFromPerson($this->userId, $modelId, $name);
207
208
			$list[] = $person;
209
		}
210
211
		return new JSONResponse($list, Http::STATUS_OK);
212
	}
213
214
	/**
215
	 * @NoAdminRequired
216
	 * @CORS
217
	 * @NoCSRFRequired
218
	 *
219
	 * @return JSONResponse
220
	 */
221
	public function getPerson(string $personName, $thumb_size = 128): JSONResponse {
222
		if (!$this->settingsService->getUserEnabled($this->userId))
223
			return new JSONResponse([], Http::STATUS_PRECONDITION_FAILED);
224
		if (empty($personName))
225
			return new JSONResponse([], Http::STATUS_PRECONDITION_FAILED);
226
227
		$resp = [];
228
		$resp['name'] = $personName;
229
		$resp['thumbUrl'] = null;
230
		$resp['images'] = array();
231
232
		$modelId = $this->settingsService->getCurrentFaceModel();
233
234
		$personFace = current($this->faceMapper->findFromPerson($this->userId, $personName, $modelId, 1));
235
		$resp['thumbUrl'] = $this->urlService->getThumbUrl($personFace->getId(), $thumb_size);
236
237
		$images = $this->imageMapper->findFromPerson($this->userId, $modelId, $personName);
238
		foreach ($images as $image) {
239
			$node = $this->urlService->getFileNode($image->getFile());
240
			if ($node === null) continue;
241
242
			$photo = [];
243
			$photo['basename'] = $this->urlService->getBasename($node);
244
			$photo['filename'] = $this->urlService->getFilename($node);
245
			$photo['mimetype'] = $this->urlService->getMimetype($node);
246
			$photo['fileUrl']  = $this->urlService->getRedirectToFileUrl($node);
247
			$photo['thumbUrl'] = $this->urlService->getPreviewUrl($node, 256);
248
249
			$resp['images'][] = $photo;
250
		}
251
252
		return new JSONResponse($resp, Http::STATUS_OK);
253
	}
254
255
	/**
256
	 * @NoAdminRequired
257
	 * @CORS
258
	 * @NoCSRFRequired
259
	 *
260
	 * @return JSONResponse
261
	 */
262
	public function updatePerson(string $personName, $name = null, $visible = null): JSONResponse {
263
		if (!$this->settingsService->getUserEnabled($this->userId))
264
			return new JSONResponse([], Http::STATUS_PRECONDITION_FAILED);
265
		if (empty($personName))
266
			return new JSONResponse([], Http::STATUS_PRECONDITION_FAILED);
267
268
		$modelId = $this->settingsService->getCurrentFaceModel();
269
270
		$clusters = $this->personMapper->findByName($this->userId, $modelId, $personName);
271
		if (empty($clusters))
272
			return new JSONResponse([], Http::STATUS_NOT_FOUND);
273
274
		if (!is_null($name)) {
275
			foreach ($clusters as $person) {
276
				$person->setName($name);
277
				$this->personMapper->update($person);
278
			}
279
		}
280
		// When change visibility it has a special treatment
281
		if (!is_null($visible)) {
282
			foreach ($clusters as $person) {
283
				$person->setIsVisible($visible);
284
				$person->setName($visible ? $name : null);
285
				$this->personMapper->update($person);
286
			}
287
		}
288
289
		// FIXME: What should response?
290
		if (is_null($name) || (!is_null($visible) && !$visible))
291
			return new JSONResponse([], Http::STATUS_OK);
292
		else
293
			return $this->getPerson($name);
294
	}
295
296
	/**
297
	 * @NoAdminRequired
298
	 * @CORS
299
	 * @NoCSRFRequired
300
	 *
301
	 * @return JSONResponse
302
	 */
303
	public function updateCluster(int $clusterId, $name = null, $visible = null): JSONResponse {
304
		if (!$this->settingsService->getUserEnabled($this->userId))
305
			return new JSONResponse([], Http::STATUS_PRECONDITION_FAILED);
306
307
		$cluster = [];
308
		if (!is_null($name)) {
309
			$cluster = $this->personMapper->find($this->userId, $clusterId);
310
			$cluster->setName($name);
311
			$cluster = $this->personMapper->update($cluster);
312
		}
313
314
		if (!is_null($visible)) {
315
			$cluster = $this->personMapper->find($this->userId, $clusterId);
316
			$cluster->setIsVisible($visible);
317
			$cluster->setName($visible ? $name : null);
318
			$cluster = $this->personMapper->update($cluster);
319
		}
320
321
		// FIXME: What should response?
322
		return new JSONResponse($cluster, Http::STATUS_OK);
323
	}
324
325
	/**
326
	 * @NoAdminRequired
327
	 * @CORS
328
	 * @NoCSRFRequired
329
	 *
330
	 * @return JSONResponse
331
	 */
332
	public function discoverPerson($minimum_count = NULL, $max_previews = 40, $thumb_size = 128): JSONResponse {
333
		if (!$this->settingsService->getUserEnabled($this->userId))
334
			return new JSONResponse([], Http::STATUS_PRECONDITION_FAILED);
335
336
		$discoveries = [];
337
338
		$modelId = $this->settingsService->getCurrentFaceModel();
339
		if (is_null($minimum_count))
340
			$minimum_count = $this->settingsService->getMinimumFacesInCluster();
341
342
		$clusters = $this->personMapper->findUnassigned($this->userId, $modelId);
343
		foreach ($clusters as $cluster) {
344
			$clusterSize = $this->personMapper->countClusterFaces($cluster->getId());
345
			if ($clusterSize < $minimum_count)
346
				continue;
347
348
			$personFaces = $this->faceMapper->findFromCluster($this->userId, $cluster->getId(), $modelId, $max_previews);
349
350
			$faces = [];
351
			foreach ($personFaces as $personFace) {
352
				$image = $this->imageMapper->find($this->userId, $personFace->getImage());
353
354
				$file = $this->urlService->getFileNode($image->getFile());
355
				if ($file === null) continue;
356
357
				$face = [];
358
				$face['thumbUrl'] = $this->urlService->getThumbUrl($personFace->getId(), $thumb_size);
359
				$face['fileUrl'] = $this->urlService->getRedirectToFileUrl($file);
360
361
				$faces[] = $face;
362
			}
363
364
			$discovery = [];
365
			$discovery['id'] = $cluster->getId();
366
			$discovery['count'] = $clusterSize;
367
			$discovery['faces'] = $faces;
368
369
			$discoveries[] = $discovery;
370
		}
371
372
		usort($discoveries, function ($a, $b) {
373
			return $b['count'] <=> $a['count'];
374
		});
375
376
		return new JSONResponse($discoveries, Http::STATUS_OK);
377
	}
378
379
	/**
380
	 * @NoAdminRequired
381
	 * @CORS
382
	 * @NoCSRFRequired
383
	 *
384
	 * @return JSONResponse
385
	 */
386
	public function autocomplete(string $query, $thumb_size = 128): JSONResponse {
387
		if (!$this->settingsService->getUserEnabled($this->userId))
388
			return new JSONResponse([], Http::STATUS_PRECONDITION_FAILED);
389
390
		if (strlen($query) < 3)
391
			return new JSONResponse([], Http::STATUS_OK);
392
393
		$resp = [];
394
395
		$modelId = $this->settingsService->getCurrentFaceModel();
396
		$persons = $this->personMapper->findPersonsLike($this->userId, $modelId, $query);
397
		foreach ($persons as $person) {
398
			$name = [];
399
			$name['name'] = $person->getName();
400
			$name['value'] = $person->getName();
401
402
			$personFace = current($this->faceMapper->findFromPerson($this->userId, $person->getName(), $modelId, 1));
403
			$name['thumbUrl'] = $this->urlService->getThumbUrl($personFace->getId(), $thumb_size);
404
405
			$resp[] = $name;
406
		}
407
408
		return new JSONResponse($resp, Http::STATUS_OK);
409
	}
410
411
	/**
412
	 * @NoAdminRequired
413
	 * @CORS
414
	 * @NoCSRFRequired
415
	 *
416
	 * @return JSONResponse
417
	 */
418
	public function detachFace(int $faceId, $name = null): JSONResponse {
419
		if (!$this->settingsService->getUserEnabled($this->userId))
420
			return new JSONResponse([], Http::STATUS_PRECONDITION_FAILED);
421
422
		$face = $this->faceMapper->find($faceId);
423
		$person = $this->personMapper->detachFace($face->getPerson(), $faceId, $name);
424
		return new JSONResponse($person, Http::STATUS_OK);
425
	}
426
427
}
428