Passed
Push — visibilities ( e779e2...5173e6 )
by Matias
11:47
created

PersonMapper   A

Complexity

Total Complexity 39

Size/Duplication

Total Lines 548
Duplicated Lines 0 %

Test Coverage

Coverage 53.67%

Importance

Changes 17
Bugs 0 Features 0
Metric Value
eloc 284
c 17
b 0
f 0
dl 0
loc 548
ccs 161
cts 300
cp 0.5367
rs 9.28
wmc 39

19 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 2 1
A find() 0 7 1
A findByName() 0 19 1
A deleteUserPersons() 0 5 1
A deleteOrphaned() 0 22 2
A findAll() 0 17 1
A countPersons() 0 29 2
C mergeClusterToDatabase() 0 106 14
A findUnassigned() 0 22 1
A removeIfEmpty() 0 12 1
A deleteUserModel() 0 9 2
A invalidatePersons() 0 16 1
A findPersonsLike() 0 18 1
A findDistinctNames() 0 12 1
A setVisibility() 0 13 2
A detachFace() 0 50 2
A isFaceInClusters() 0 7 3
A updateFace() 0 6 1
A countClusterFaces() 0 12 1
1
<?php
2
/**
3
 * @copyright Copyright (c) 2018-2021, Matias De lellis <[email protected]>
4
 * @copyright Copyright (c) 2018-2019, Branko Kokanovic <[email protected]>
5
 *
6
 * @author Branko Kokanovic <[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
namespace OCA\FaceRecognition\Db;
25
26
use OC\DB\QueryBuilder\Literal;
0 ignored issues
show
Bug introduced by
The type OC\DB\QueryBuilder\Literal was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
27
28
use OCP\IDBConnection;
29
use OCP\IUser;
30
31
use OCP\AppFramework\Db\QBMapper;
32
use OCP\AppFramework\Db\DoesNotExistException;
33
use OCP\DB\QueryBuilder\IQueryBuilder;
34
35
class PersonMapper extends QBMapper {
36
37 1
	public function __construct(IDBConnection $db) {
38 1
		parent::__construct($db, 'facerecog_persons', '\OCA\FaceRecognition\Db\Person');
39 1
	}
40
41
	/**
42
	 * @param string $userId ID of the user
43
	 * @param int $personId ID of the person
44
	 *
45
	 * @return Person
46
	 */
47 8
	public function find(string $userId, int $personId): Person {
48 8
		$qb = $this->db->getQueryBuilder();
49 8
		$qb->select('id', 'name', 'is_visible')
50 8
			->from($this->getTableName(), 'p')
51 8
			->where($qb->expr()->eq('id', $qb->createNamedParameter($personId)))
52 8
			->andWhere($qb->expr()->eq('user', $qb->createNamedParameter($userId)));
53 8
		return $this->findEntity($qb);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->findEntity($qb) returns the type OCP\AppFramework\Db\Entity which includes types incompatible with the type-hinted return OCA\FaceRecognition\Db\Person.
Loading history...
54
	}
55
56
	/**
57
	 * @param string $userId ID of the user
58
	 * @param int $modelId ID of the model
59
	 * @param string $personName name of the person to find
60
	 * @return Person[]
61
	 */
62 2
	public function findByName(string $userId, int $modelId, string $personName): array {
63 2
		$sub = $this->db->getQueryBuilder();
64 2
		$sub->select(new Literal('1'))
65 2
			->from('facerecog_faces', 'f')
66 2
			->innerJoin('f', 'facerecog_images' ,'i', $sub->expr()->eq('f.image', 'i.id'))
67 2
			->where($sub->expr()->eq('p.id', 'f.person'))
68 2
			->andWhere($sub->expr()->eq('i.user', $sub->createParameter('user_id')))
69 2
			->andWhere($sub->expr()->eq('i.model', $sub->createParameter('model_id')))
70 2
			->andWhere($sub->expr()->eq('p.name', $sub->createParameter('person_name')));
71
72 2
		$qb = $this->db->getQueryBuilder();
73 2
		$qb->select('id', 'name', 'is_valid')
74 2
			->from($this->getTableName(), 'p')
75 2
			->where('EXISTS (' . $sub->getSQL() . ')')
76 2
			->setParameter('user_id', $userId)
77 2
			->setParameter('model_id', $modelId)
78 2
			->setParameter('person_name', $personName);
79
80 2
		return $this->findEntities($qb);
81
	}
82
83
	/**
84
	 * @param string $userId ID of the user
85
	 * @param int $modelId ID of the model
86
	 * @return Person[]
87
	 */
88
	public function findUnassigned(string $userId, int $modelId): array {
89
		$sub = $this->db->getQueryBuilder();
90
		$sub->select(new Literal('1'))
91
			->from('facerecog_faces', 'f')
92
			->innerJoin('f', 'facerecog_images' ,'i', $sub->expr()->eq('f.image', 'i.id'))
93
			->where($sub->expr()->eq('p.id', 'f.person'))
94
			->andWhere($sub->expr()->eq('i.user', $sub->createParameter('user_id')))
95
			->andWhere($sub->expr()->eq('i.model', $sub->createParameter('model_id')));
96
97
		$qb = $this->db->getQueryBuilder();
98
		$qb->select('id', 'is_valid')
99
			->from($this->getTableName(), 'p')
100
			->where('EXISTS (' . $sub->getSQL() . ')')
101
			->andWhere($qb->expr()->eq('is_valid', $qb->createParameter('is_valid')))
102
			->andWhere($qb->expr()->eq('is_visible', $qb->createParameter('is_visible')))
103
			->andWhere($qb->expr()->isNull('name'))
104
			->setParameter('user_id', $userId)
105
			->setParameter('model_id', $modelId)
106
			->setParameter('is_valid', true, IQueryBuilder::PARAM_BOOL)
107
			->setParameter('is_visible', true, IQueryBuilder::PARAM_BOOL);
108
109
		return $this->findEntities($qb);
110
	}
111
112
	/**
113
	 * @param string $userId ID of the user
114
	 * @param int $modelId ID of the model
115
	 * @return Person[]
116
	 */
117 13
	public function findAll(string $userId, int $modelId): array {
118 13
		$sub = $this->db->getQueryBuilder();
119 13
		$sub->select(new Literal('1'))
120 13
			->from('facerecog_faces', 'f')
121 13
			->innerJoin('f', 'facerecog_images' ,'i', $sub->expr()->eq('f.image', 'i.id'))
122 13
			->where($sub->expr()->eq('p.id', 'f.person'))
123 13
			->andWhere($sub->expr()->eq('i.user', $sub->createParameter('user_id')))
124 13
			->andWhere($sub->expr()->eq('i.model', $sub->createParameter('model_id')));
125
126 13
		$qb = $this->db->getQueryBuilder();
127 13
		$qb->select('id', 'name', 'is_valid')
128 13
			->from($this->getTableName(), 'p')
129 13
			->where('EXISTS (' . $sub->getSQL() . ')')
130 13
			->setParameter('user_id', $userId)
131 13
			->setParameter('model_id', $modelId);
132
133 13
		return $this->findEntities($qb);
134
	}
135
136
	/**
137
	 * @param string $userId ID of the user
138
	 *
139
	 * @return Person[]
140
	 */
141
	public function findDistinctNames(string $userId, int $modelId): array {
142
		$qb = $this->db->getQueryBuilder();
143
		$qb->selectDistinct('name')
144
			->from($this->getTableName(), 'p')
145
			->innerJoin('p', 'facerecog_faces' , 'f', $qb->expr()->eq('f.person', 'p.id'))
146
			->innerJoin('f', 'facerecog_images' ,'i', $qb->expr()->eq('f.image', 'i.id'))
147
			->where($qb->expr()->eq('i.user', $qb->createParameter('user_id')))
148
			->andWhere($qb->expr()->eq('i.model', $qb->createParameter('model_id')))
149
			->andwhere($qb->expr()->isNotNull('p.name'))
150
			->setParameter('user_id', $userId)
151
			->setParameter('model_id', $modelId);
152
		return $this->findEntities($qb);
153
	}
154
155
	/**
156
	 * Search Person by name
157
	 *
158
	 * @param int|null $offset
159
	 * @param int|null $limit
160
	 */
161
	public function findPersonsLike(string $userId, int $modelId, string $name, ?int $offset = null, ?int $limit = null): array {
162
		$qb = $this->db->getQueryBuilder();
163
		$qb->selectDistinct('p.name')
164
			->from($this->getTableName(), 'p')
165
			->innerJoin('p', 'facerecog_faces', 'f', $qb->expr()->eq('f.person', 'p.id'))
166
			->innerJoin('p', 'facerecog_images', 'i', $qb->expr()->eq('f.image', 'i.id'))
167
			->where($qb->expr()->eq('p.user', $qb->createNamedParameter($userId)))
168
			->andWhere($qb->expr()->eq('model', $qb->createNamedParameter($modelId)))
169
			->andWhere($qb->expr()->eq('is_processed', $qb->createNamedParameter(True)))
170
			->andWhere($qb->expr()->like($qb->func()->lower('p.name'), $qb->createParameter('query')));
171
172
		$query = '%' . $this->db->escapeLikeParameter(strtolower($name)) . '%';
173
		$qb->setParameter('query', $query);
174
175
		$qb->setFirstResult($offset);
176
		$qb->setMaxResults($limit);
177
178
		return $this->findEntities($qb);
179
	}
180
181
	/**
182
	 * Returns count of persons (clusters) found for a given user.
183
	 *
184
	 * @param string $userId ID of the user
185
	 * @param int $modelId ID of the model
186
	 * @param bool $onlyInvalid True if client wants count of invalid persons only,
187
	 *  false if client want count of all persons
188
	 * @return int Count of persons
189
	 */
190 15
	public function countPersons(string $userId, int $modelId, bool $onlyInvalid=false): int {
191 15
		$sub = $this->db->getQueryBuilder();
192 15
		$sub->select(new Literal('1'))
193 15
			->from('facerecog_faces', 'f')
194 15
			->innerJoin('f', 'facerecog_images' ,'i', $sub->expr()->eq('f.image', 'i.id'))
195 15
			->where($sub->expr()->eq('p.id', 'f.person'))
196 15
			->andWhere($sub->expr()->eq('i.user', $sub->createParameter('user_id')))
197 15
			->andWhere($sub->expr()->eq('i.model', $sub->createParameter('model_id')));
198
199 15
		$qb = $this->db->getQueryBuilder();
200 15
		$qb->select($qb->createFunction('COUNT(' . $qb->getColumnName('id') . ')'))
201 15
			->from($this->getTableName(), 'p')
202 15
			->where('EXISTS (' . $sub->getSQL() . ')');
203
204 15
		if ($onlyInvalid) {
205
			$qb = $qb
206
				->andWhere($qb->expr()->eq('is_valid', $qb->createParameter('is_valid')))
207
				->setParameter('is_valid', false, IQueryBuilder::PARAM_BOOL);
208
		}
209
210
		$qb = $qb
211 15
			->setParameter('user_id', $userId)
212 15
			->setParameter('model_id', $modelId);
213
214 15
		$resultStatement = $qb->execute();
215 15
		$data = $resultStatement->fetch(\PDO::FETCH_NUM);
216 15
		$resultStatement->closeCursor();
217
218 15
		return (int)$data[0];
219
	}
220
221
	/**
222
	 * Based on a given image, takes all faces that belong to that image
223
	 * and invalidates all person that those faces belongs to.
224
	 *
225
	 * @param int $imageId ID of image for which to invalidate persons for
226
	 *
227
	 * @return void
228
	 */
229 12
	public function invalidatePersons(int $imageId): void {
230 12
		$sub = $this->db->getQueryBuilder();
231 12
		$tableNameWithPrefixWithoutQuotes = trim($sub->getTableName($this->getTableName()), '`');
232 12
		$sub->select(new Literal('1'));
233 12
		$sub->from('facerecog_images', 'i')
234 12
			->innerJoin('i', 'facerecog_faces' ,'f', $sub->expr()->eq('i.id', 'f.image'))
235 12
			->where($sub->expr()->eq($tableNameWithPrefixWithoutQuotes . '.id', 'f.person'))
236 12
			->andWhere($sub->expr()->eq('i.id', $sub->createParameter('image_id')));
237
238 12
		$qb = $this->db->getQueryBuilder();
239 12
		$qb->update($this->getTableName())
240 12
			->set("is_valid", $qb->createParameter('is_valid'))
241 12
			->where('EXISTS (' . $sub->getSQL() . ')')
242 12
			->setParameter('image_id', $imageId)
243 12
			->setParameter('is_valid', false, IQueryBuilder::PARAM_BOOL)
244 12
			->execute();
245 12
	}
246
247
	/**
248
	 * Based on current clusters and new clusters, do database reconciliation.
249
	 * It tries to do that in minimal number of SQL queries. Operation is atomic.
250
	 *
251
	 * Clusters are array, where keys are ID of persons, and values are indexed arrays
252
	 * with values that are ID of the faces for those persons.
253
	 *
254
	 * @param string $userId ID of the user that clusters belong to
255
	 * @param array $currentClusters Current clusters
256
	 * @param array $newClusters New clusters
257
	 *
258
	 * @return void
259
	 */
260 14
	public function mergeClusterToDatabase(string $userId, $currentClusters, $newClusters): void {
261 14
		$this->db->beginTransaction();
262 14
		$currentDateTime = new \DateTime();
263
264
		try {
265
			// Delete clusters that do not exist anymore
266 14
			foreach($currentClusters as $oldPerson => $oldFaces) {
267 11
				if (array_key_exists($oldPerson, $newClusters)) {
268 6
					continue;
269
				}
270
271
				// OK, we bumped into cluster that existed and now it does not exist.
272
				// We need to remove all references to it and to delete it.
273 7
				foreach ($oldFaces as $oldFace) {
274 7
					$this->updateFace($oldFace, null);
275
				}
276
277
				// todo: this is not very cool. What if user had associated linked user to this. And all lost?
278 7
				$qb = $this->db->getQueryBuilder();
279
				// todo: for extra safety, we should probably add here additional condition, where (user=$userId)
280
				$qb
281 7
					->delete($this->getTableName())
282 7
					->where($qb->expr()->eq('id', $qb->createNamedParameter($oldPerson)))
283 7
					->execute();
284
			}
285
286
			// Modify existing clusters
287 14
			foreach($newClusters as $newPerson=>$newFaces) {
288 12
				if (!array_key_exists($newPerson, $currentClusters)) {
289
					// This cluster didn't exist, there is nothing to modify
290
					// It will be processed during cluster adding operation
291 9
					continue;
292
				}
293
294 6
				$oldFaces = $currentClusters[$newPerson];
295 6
				if ($newFaces === $oldFaces) {
296
					// Set cluster as valid now
297 2
					$qb = $this->db->getQueryBuilder();
298
					$qb
299 2
						->update($this->getTableName())
300 2
						->set("is_valid", $qb->createParameter('is_valid'))
301 2
						->where($qb->expr()->eq('id', $qb->createNamedParameter($newPerson)))
302 2
						->setParameter('is_valid', true, IQueryBuilder::PARAM_BOOL)
303 2
						->execute();
304 2
					continue;
305
				}
306
307
				// OK, set of faces do differ. Now, we could potentially go into finer grain details
308
				// and add/remove each individual face, but this seems too detailed. Enough is to
309
				// reset all existing faces to null and to add new faces to new person. That should
310
				// take care of both faces that are removed from cluster, as well as for newly added
311
				// faces to this cluster.
312
313
				// First remove all old faces from any cluster (reset them to null)
314 5
				foreach ($oldFaces as $oldFace) {
315
					// Reset face to null only if it wasn't moved to other cluster!
316
					// (if face is just moved to other cluster, do not reset to null, as some other
317
					// pass for some other cluster will eventually update it to proper cluster)
318 5
					if ($this->isFaceInClusters($oldFace, $newClusters) === false) {
319 1
						$this->updateFace($oldFace, null);
320
					}
321
				}
322
323
				// Then set all new faces to belong to this cluster
324 5
				foreach ($newFaces as $newFace) {
325 5
					$this->updateFace($newFace, $newPerson);
326
				}
327
328
				// Set cluster as valid now
329 5
				$qb = $this->db->getQueryBuilder();
330
				$qb
331 5
					->update($this->getTableName())
332 5
					->set("is_valid", $qb->createParameter('is_valid'))
333 5
					->where($qb->expr()->eq('id', $qb->createNamedParameter($newPerson)))
334 5
					->setParameter('is_valid', true, IQueryBuilder::PARAM_BOOL)
335 5
					->execute();
336
			}
337
338
			// Add new clusters
339 14
			foreach($newClusters as $newPerson=>$newFaces) {
340 12
				if (array_key_exists($newPerson, $currentClusters)) {
341
					// This cluster already existed, nothing to add
342
					// It was already processed during modify cluster operation
343 6
					continue;
344
				}
345
346
				// Create new cluster and add all faces to it
347 9
				$qb = $this->db->getQueryBuilder();
348
				$qb
349 9
					->insert($this->getTableName())
350 9
					->values([
351 9
						'user' => $qb->createNamedParameter($userId),
352 9
						'is_valid' => $qb->createNamedParameter(true),
353 9
						'last_generation_time' => $qb->createNamedParameter($currentDateTime, IQueryBuilder::PARAM_DATE),
354 9
						'linked_user' => $qb->createNamedParameter(null)])
355 9
					->execute();
356 9
				$insertedPersonId = $qb->getLastInsertId();
357 9
				foreach ($newFaces as $newFace) {
358 9
					$this->updateFace($newFace, $insertedPersonId);
359
				}
360
			}
361
362 14
			$this->db->commit();
363
		} catch (\Exception $e) {
364
			$this->db->rollBack();
365
			throw $e;
366
		}
367 14
	}
368
369
	/**
370
	 * Deletes all persons from that user.
371
	 *
372
	 * @param string $userId User to drop persons from a table.
373
	 *
374
	 * @return void
375
	 */
376 28
	public function deleteUserPersons(string $userId): void {
377 28
		$qb = $this->db->getQueryBuilder();
378 28
		$qb->delete($this->getTableName())
379 28
			->where($qb->expr()->eq('user', $qb->createNamedParameter($userId)))
380 28
			->execute();
381 28
	}
382
383
	/**
384
	 * Deletes all persons from that user and model
385
	 *
386
	 * @param string $userId ID of user for drop from table
387
	 * @param int $modelId
388
	 *
389
	 * @return void
390
	 */
391
	public function deleteUserModel(string $userId, int $modelId): void {
392
		//TODO: Make it atomic
393
		$qb = $this->db->getQueryBuilder();
394
		$qb->delete($this->getTableName())
395
			->where($qb->expr()->eq('id', $qb->createParameter('person')));
396
397
		$persons = $this->findAll($userId, $modelId);
398
		foreach ($persons as $person) {
399
			$qb->setParameter('person', $person->getId())->execute();
400
		}
401
	}
402
403
	/**
404
	 * Deletes person if it is empty (have no faces associated to it)
405
	 *
406
	 * @param int $personId Person to check if it should be deleted
407
	 *
408
	 * @return void
409
	 */
410
	public function removeIfEmpty(int $personId): void {
411
		$sub = $this->db->getQueryBuilder();
412
		$sub->select(new Literal('1'));
413
		$sub->from('facerecog_faces', 'f')
414
			->where($sub->expr()->eq('f.person', $sub->createParameter('person')));
415
416
		$qb = $this->db->getQueryBuilder();
417
		$qb->delete($this->getTableName())
418
			->where($qb->expr()->eq('id', $qb->createParameter('person')))
419
			->andWhere('NOT EXISTS (' . $sub->getSQL() . ')')
420
			->setParameter('person', $personId)
421
			->execute();
422
	}
423
424
	/**
425
	 * Deletes all persons that have no faces associated to them
426
	 *
427
	 * @param string $userId ID of user for which we are deleting orphaned persons
428
	 */
429 1
	public function deleteOrphaned(string $userId): int {
430 1
		$sub = $this->db->getQueryBuilder();
431 1
		$sub->select(new Literal('1'));
432 1
		$sub->from('facerecog_faces', 'f')
433 1
			->where($sub->expr()->eq('f.person', 'p.id'));
434
435 1
		$qb = $this->db->getQueryBuilder();
436 1
		$qb->select('p.id')
437 1
			->from($this->getTableName(), 'p')
438 1
			->where($qb->expr()->eq('p.user', $qb->createParameter('user')))
439 1
			->andWhere('NOT EXISTS (' . $sub->getSQL() . ')')
440 1
			->setParameter('user', $userId);
441 1
		$orphanedPersons = $this->findEntities($qb);
442
443 1
		$orphaned = 0;
444 1
		foreach ($orphanedPersons as $person) {
445
			$qb = $this->db->getQueryBuilder();
446
			$orphaned += $qb->delete($this->getTableName())
447
				->where($qb->expr()->eq('id', $qb->createNamedParameter($person->id)))
448
				->execute();
449
		}
450 1
		return $orphaned;
451
	}
452
453
	/*
454
	 * Mark the cluster as hidden or visible to user.
455
	 *
456
	 * @param int $personId ID of the person
457
	 * @param bool $visible visibility of the person
458
	 *
459
	 * @return void
460
	 */
461
	public function setVisibility (int $personId, bool $visible): void {
462
		$qb = $this->db->getQueryBuilder();
463
		if ($visible) {
464
			$qb->update($this->getTableName())
465
				->set('is_visible', $qb->createNamedParameter(1))
466
				->where($qb->expr()->eq('id', $qb->createNamedParameter($personId)))
467
				->execute();
468
		} else {
469
			$qb->update($this->getTableName())
470
				->set('is_visible', $qb->createNamedParameter(0))
471
				->set('name', $qb->createNamedParameter(null))
472
				->where($qb->expr()->eq('id', $qb->createNamedParameter($personId)))
473
				->execute();
474
		}
475
	}
476
477
	/*
478
	 * Mark the cluster as hidden or visible to user.
479
	 *
480
	 * @param int $personId ID of the person
481
	 * @param int $faceId visibility of the person
482
	 * @param string|null optional name to rename them.
0 ignored issues
show
Bug introduced by
The type OCA\FaceRecognition\Db\optional was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
483
	 *
484
	 * @return Person
485
	 */
486
	public function detachFace(int $personId, int $faceId, $name = null): Person {
487
		// Mark the face as non groupable.
488
		$qb = $this->db->getQueryBuilder();
489
		$qb->update('facerecog_faces')
490
			->set('is_groupable', $qb->createParameter('is_groupable'))
491
			->where($qb->expr()->eq('id', $qb->createNamedParameter($faceId)))
492
			->setParameter('is_groupable', false, IQueryBuilder::PARAM_BOOL)
493
			->execute();
494
495
		if ($this->countClusterFaces($personId) === 1) {
496
			// If cluster is an single face just rename it.
497
			$qb = $this->db->getQueryBuilder();
498
			$qb->update($this->getTableName())
499
				->set('name', $qb->createNamedParameter($name))
500
				->set('is_visible', $qb->createNamedParameter(true))
501
				->where($qb->expr()->eq('id', $qb->createNamedParameter($personId)))
502
				->execute();
503
		} else {
504
			// If there are other faces, must create a new person for that face.
505
			$qb = $this->db->getQueryBuilder();
506
			$qb->select('user')
507
				->from($this->getTableName())
508
				->where($qb->expr()->eq('id', $qb->createNamedParameter($personId)));
509
			$oldPerson = $this->findEntity($qb);
510
511
			$qb = $this->db->getQueryBuilder();
512
			$qb->insert($this->getTableName())->values([
513
				'user' => $qb->createNamedParameter($oldPerson->getUser()),
514
				'name' => $qb->createNamedParameter($name),
515
				'is_valid' => $qb->createNamedParameter(true),
516
				'last_generation_time' => $qb->createNamedParameter(new \DateTime(), IQueryBuilder::PARAM_DATE),
517
				'linked_user' => $qb->createNamedParameter(null),
518
				'is_visible' => $qb->createNamedParameter(true)
519
			])->execute();
520
521
			$personId = $qb->getLastInsertId();
522
523
			$qb = $this->db->getQueryBuilder();
524
			$qb->update('facerecog_faces')
525
				->set('person', $qb->createParameter('person'))
526
				->where($qb->expr()->eq('id', $qb->createNamedParameter($faceId)))
527
				->setParameter('person', $personId)
528
				->execute();
529
		}
530
531
		$qb = $this->db->getQueryBuilder();
532
		$qb->select('id', 'name', 'is_valid', 'is_visible')
533
		   ->from($this->getTableName())
534
		   ->where($qb->expr()->eq('id', $qb->createNamedParameter($personId)));
535
		return $this->findEntity($qb);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->findEntity($qb) returns the type OCP\AppFramework\Db\Entity which includes types incompatible with the type-hinted return OCA\FaceRecognition\Db\Person.
Loading history...
536
	}
537
538
	public function countClusterFaces(int $personId): int {
539
		$qb = $this->db->getQueryBuilder();
540
		$query = $qb
541
			->select($qb->createFunction('COUNT(' . $qb->getColumnName('id') . ')'))
542
			->from('facerecog_faces')
543
			->where($qb->expr()->eq('person', $qb->createParameter('person')))
544
			->setParameter('person', $personId);
545
		$resultStatement = $query->execute();
546
		$data = $resultStatement->fetch(\PDO::FETCH_NUM);
547
		$resultStatement->closeCursor();
548
549
		return (int)$data[0];
550
	}
551
552
	/**
553
	 * Updates one face with $faceId to database to person ID $personId.
554
	 *
555
	 * @param int $faceId ID of the face
556
	 * @param int|null $personId ID of the person
557
	 *
558
	 * @return void
559
	 */
560 12
	private function updateFace(int $faceId, $personId): void {
561 12
		$qb = $this->db->getQueryBuilder();
562 12
		$qb->update('facerecog_faces')
563 12
			->set("person", $qb->createNamedParameter($personId))
564 12
			->where($qb->expr()->eq('id', $qb->createNamedParameter($faceId)))
565 12
			->execute();
566 12
	}
567
568
	/**
569
	 * Checks if face with a given ID is in any cluster.
570
	 *
571
	 * @param int $faceId ID of the face to check
572
	 * @param array $cluster All clusters to check into
573
	 *
574
	 * @return bool True if face is found in any cluster, false otherwise.
575
	 */
576 5
	private function isFaceInClusters(int $faceId, array $clusters): bool {
577 5
		foreach ($clusters as $_=>$faces) {
578 5
			if (in_array($faceId, $faces)) {
579 5
				return true;
580
			}
581
		}
582 1
		return false;
583
	}
584
}
585