Passed
Push — nc24 ( 99f5cb...5810ff )
by Matias
07:38 queued 05:30
created

PersonMapper::countClusters()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 29
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 19
CRAP Score 2.0034

Importance

Changes 0
Metric Value
cc 2
eloc 22
c 0
b 0
f 0
nc 2
nop 3
dl 0
loc 29
ccs 19
cts 21
cp 0.9048
crap 2.0034
rs 9.568
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
	}
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 7
	public function findDistinctNames(string $userId, int $modelId): array {
142 7
		$qb = $this->db->getQueryBuilder();
143 7
		$qb->selectDistinct('name')
144 7
			->from($this->getTableName(), 'p')
145 7
			->innerJoin('p', 'facerecog_faces' , 'f', $qb->expr()->eq('f.person', 'p.id'))
146 7
			->innerJoin('f', 'facerecog_images' ,'i', $qb->expr()->eq('f.image', 'i.id'))
147 7
			->where($qb->expr()->eq('i.user', $qb->createParameter('user_id')))
148 7
			->andWhere($qb->expr()->eq('i.model', $qb->createParameter('model_id')))
149 7
			->andwhere($qb->expr()->isNotNull('p.name'))
150 7
			->setParameter('user_id', $userId)
151 7
			->setParameter('model_id', $modelId);
152 7
		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 found for a given user.
183
	 *
184
	 * @param string $userId ID of the user
185
	 * @param int $modelId ID of the model
186
	 * @return int Count of persons
187
	 */
188 7
	public function countPersons(string $userId, int $modelId, bool $onlyInvalid=false): int {
0 ignored issues
show
Unused Code introduced by
The parameter $onlyInvalid is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

188
	public function countPersons(string $userId, int $modelId, /** @scrutinizer ignore-unused */ bool $onlyInvalid=false): int {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
189 7
		return count($this->findDistinctNames($userId, $modelId));
190
	}
191
192
	/**
193
	 * Returns count of clusters found for a given user.
194
	 *
195
	 * @param string $userId ID of the user
196
	 * @param int $modelId ID of the model
197
	 * @param bool $onlyInvalid True if client wants count of invalid clusters only,
198
	 *  false if client want count of all clusters
199
	 * @return int Count of clusters
200
	 */
201 11
	public function countClusters(string $userId, int $modelId, bool $onlyInvalid=false): int {
202 11
		$sub = $this->db->getQueryBuilder();
203 11
		$sub->select(new Literal('1'))
204 11
			->from('facerecog_faces', 'f')
205 11
			->innerJoin('f', 'facerecog_images' ,'i', $sub->expr()->eq('f.image', 'i.id'))
206 11
			->where($sub->expr()->eq('p.id', 'f.person'))
207 11
			->andWhere($sub->expr()->eq('i.user', $sub->createParameter('user_id')))
208 11
			->andWhere($sub->expr()->eq('i.model', $sub->createParameter('model_id')));
209
210 11
		$qb = $this->db->getQueryBuilder();
211 11
		$qb->select($qb->createFunction('COUNT(' . $qb->getColumnName('id') . ')'))
212 11
			->from($this->getTableName(), 'p')
213 11
			->where('EXISTS (' . $sub->getSQL() . ')');
214
215 11
		if ($onlyInvalid) {
216
			$qb = $qb
217
				->andWhere($qb->expr()->eq('is_valid', $qb->createParameter('is_valid')))
218
				->setParameter('is_valid', false, IQueryBuilder::PARAM_BOOL);
219
		}
220
221
		$qb = $qb
222 11
			->setParameter('user_id', $userId)
223 11
			->setParameter('model_id', $modelId);
224
225 11
		$resultStatement = $qb->execute();
0 ignored issues
show
Deprecated Code introduced by
The function OCP\DB\QueryBuilder\IQueryBuilder::execute() has been deprecated: 22.0.0 Use executeQuery or executeStatement ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

225
		$resultStatement = /** @scrutinizer ignore-deprecated */ $qb->execute();

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
226 11
		$data = $resultStatement->fetch(\PDO::FETCH_NUM);
227 11
		$resultStatement->closeCursor();
228
229 11
		return (int)$data[0];
230
	}
231
232
	/**
233
	 * Based on a given image, takes all faces that belong to that image
234
	 * and invalidates all person that those faces belongs to.
235
	 *
236
	 * @param int $imageId ID of image for which to invalidate persons for
237
	 *
238
	 * @return void
239
	 */
240 12
	public function invalidatePersons(int $imageId): void {
241 12
		$sub = $this->db->getQueryBuilder();
242 12
		$tableNameWithPrefixWithoutQuotes = trim($sub->getTableName($this->getTableName()), '`');
243 12
		$sub->select(new Literal('1'));
244 12
		$sub->from('facerecog_images', 'i')
245 12
			->innerJoin('i', 'facerecog_faces' ,'f', $sub->expr()->eq('i.id', 'f.image'))
246 12
			->where($sub->expr()->eq($tableNameWithPrefixWithoutQuotes . '.id', 'f.person'))
247 12
			->andWhere($sub->expr()->eq('i.id', $sub->createParameter('image_id')));
248
249 12
		$qb = $this->db->getQueryBuilder();
250 12
		$qb->update($this->getTableName())
0 ignored issues
show
Deprecated Code introduced by
The function OCP\DB\QueryBuilder\IQueryBuilder::execute() has been deprecated: 22.0.0 Use executeQuery or executeStatement ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

250
		/** @scrutinizer ignore-deprecated */ $qb->update($this->getTableName())

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
251 12
			->set("is_valid", $qb->createParameter('is_valid'))
252 12
			->where('EXISTS (' . $sub->getSQL() . ')')
253 12
			->setParameter('image_id', $imageId)
254 12
			->setParameter('is_valid', false, IQueryBuilder::PARAM_BOOL)
255 12
			->execute();
256
	}
257
258
	/**
259
	 * Based on current clusters and new clusters, do database reconciliation.
260
	 * It tries to do that in minimal number of SQL queries. Operation is atomic.
261
	 *
262
	 * Clusters are array, where keys are ID of persons, and values are indexed arrays
263
	 * with values that are ID of the faces for those persons.
264
	 *
265
	 * @param string $userId ID of the user that clusters belong to
266
	 * @param array $currentClusters Current clusters
267
	 * @param array $newClusters New clusters
268
	 *
269
	 * @return void
270
	 */
271 14
	public function mergeClusterToDatabase(string $userId, $currentClusters, $newClusters): void {
272 14
		$this->db->beginTransaction();
273 14
		$currentDateTime = new \DateTime();
274
275
		try {
276
			// Delete clusters that do not exist anymore
277 14
			foreach($currentClusters as $oldPerson => $oldFaces) {
278 11
				if (array_key_exists($oldPerson, $newClusters)) {
279 6
					continue;
280
				}
281
282
				// OK, we bumped into cluster that existed and now it does not exist.
283
				// We need to remove all references to it and to delete it.
284 7
				foreach ($oldFaces as $oldFace) {
285 7
					$this->updateFace($oldFace, null);
286
				}
287
288
				// todo: this is not very cool. What if user had associated linked user to this. And all lost?
289 7
				$qb = $this->db->getQueryBuilder();
290
				// todo: for extra safety, we should probably add here additional condition, where (user=$userId)
291
				$qb
0 ignored issues
show
Deprecated Code introduced by
The function OCP\DB\QueryBuilder\IQueryBuilder::execute() has been deprecated: 22.0.0 Use executeQuery or executeStatement ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

291
				/** @scrutinizer ignore-deprecated */ $qb

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
292 7
					->delete($this->getTableName())
293 7
					->where($qb->expr()->eq('id', $qb->createNamedParameter($oldPerson)))
294 7
					->execute();
295
			}
296
297
			// Modify existing clusters
298 14
			foreach($newClusters as $newPerson=>$newFaces) {
299 12
				if (!array_key_exists($newPerson, $currentClusters)) {
300
					// This cluster didn't exist, there is nothing to modify
301
					// It will be processed during cluster adding operation
302 9
					continue;
303
				}
304
305 6
				$oldFaces = $currentClusters[$newPerson];
306 6
				if ($newFaces === $oldFaces) {
307
					// Set cluster as valid now
308 2
					$qb = $this->db->getQueryBuilder();
309
					$qb
0 ignored issues
show
Deprecated Code introduced by
The function OCP\DB\QueryBuilder\IQueryBuilder::execute() has been deprecated: 22.0.0 Use executeQuery or executeStatement ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

309
					/** @scrutinizer ignore-deprecated */ $qb

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
310 2
						->update($this->getTableName())
311 2
						->set("is_valid", $qb->createParameter('is_valid'))
312 2
						->where($qb->expr()->eq('id', $qb->createNamedParameter($newPerson)))
313 2
						->setParameter('is_valid', true, IQueryBuilder::PARAM_BOOL)
314 2
						->execute();
315 2
					continue;
316
				}
317
318
				// OK, set of faces do differ. Now, we could potentially go into finer grain details
319
				// and add/remove each individual face, but this seems too detailed. Enough is to
320
				// reset all existing faces to null and to add new faces to new person. That should
321
				// take care of both faces that are removed from cluster, as well as for newly added
322
				// faces to this cluster.
323
324
				// First remove all old faces from any cluster (reset them to null)
325 5
				foreach ($oldFaces as $oldFace) {
326
					// Reset face to null only if it wasn't moved to other cluster!
327
					// (if face is just moved to other cluster, do not reset to null, as some other
328
					// pass for some other cluster will eventually update it to proper cluster)
329 5
					if ($this->isFaceInClusters($oldFace, $newClusters) === false) {
330 1
						$this->updateFace($oldFace, null);
331
					}
332
				}
333
334
				// Then set all new faces to belong to this cluster
335 5
				foreach ($newFaces as $newFace) {
336 5
					$this->updateFace($newFace, $newPerson);
337
				}
338
339
				// Set cluster as valid now
340 5
				$qb = $this->db->getQueryBuilder();
341
				$qb
0 ignored issues
show
Deprecated Code introduced by
The function OCP\DB\QueryBuilder\IQueryBuilder::execute() has been deprecated: 22.0.0 Use executeQuery or executeStatement ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

341
				/** @scrutinizer ignore-deprecated */ $qb

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
342 5
					->update($this->getTableName())
343 5
					->set("is_valid", $qb->createParameter('is_valid'))
344 5
					->where($qb->expr()->eq('id', $qb->createNamedParameter($newPerson)))
345 5
					->setParameter('is_valid', true, IQueryBuilder::PARAM_BOOL)
346 5
					->execute();
347
			}
348
349
			// Add new clusters
350 14
			foreach($newClusters as $newPerson=>$newFaces) {
351 12
				if (array_key_exists($newPerson, $currentClusters)) {
352
					// This cluster already existed, nothing to add
353
					// It was already processed during modify cluster operation
354 6
					continue;
355
				}
356
357
				// Create new cluster and add all faces to it
358 9
				$qb = $this->db->getQueryBuilder();
359
				$qb
0 ignored issues
show
Deprecated Code introduced by
The function OCP\DB\QueryBuilder\IQueryBuilder::execute() has been deprecated: 22.0.0 Use executeQuery or executeStatement ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

359
				/** @scrutinizer ignore-deprecated */ $qb

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
360 9
					->insert($this->getTableName())
361 9
					->values([
362 9
						'user' => $qb->createNamedParameter($userId),
363 9
						'is_valid' => $qb->createNamedParameter(true),
364 9
						'last_generation_time' => $qb->createNamedParameter($currentDateTime, IQueryBuilder::PARAM_DATE),
365 9
						'linked_user' => $qb->createNamedParameter(null)])
366 9
					->execute();
367 9
				$insertedPersonId = $qb->getLastInsertId();
368 9
				foreach ($newFaces as $newFace) {
369 9
					$this->updateFace($newFace, $insertedPersonId);
370
				}
371
			}
372
373 14
			$this->db->commit();
374
		} catch (\Exception $e) {
375
			$this->db->rollBack();
376
			throw $e;
377
		}
378
	}
379
380
	/**
381
	 * Deletes all persons from that user.
382
	 *
383
	 * @param string $userId User to drop persons from a table.
384
	 *
385
	 * @return void
386
	 */
387 28
	public function deleteUserPersons(string $userId): void {
388 28
		$qb = $this->db->getQueryBuilder();
389 28
		$qb->delete($this->getTableName())
0 ignored issues
show
Deprecated Code introduced by
The function OCP\DB\QueryBuilder\IQueryBuilder::execute() has been deprecated: 22.0.0 Use executeQuery or executeStatement ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

389
		/** @scrutinizer ignore-deprecated */ $qb->delete($this->getTableName())

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
390 28
			->where($qb->expr()->eq('user', $qb->createNamedParameter($userId)))
391 28
			->execute();
392
	}
393
394
	/**
395
	 * Deletes all persons from that user and model
396
	 *
397
	 * @param string $userId ID of user for drop from table
398
	 * @param int $modelId
399
	 *
400
	 * @return void
401
	 */
402
	public function deleteUserModel(string $userId, int $modelId): void {
403
		//TODO: Make it atomic
404
		$qb = $this->db->getQueryBuilder();
405
		$qb->delete($this->getTableName())
406
			->where($qb->expr()->eq('id', $qb->createParameter('person')));
407
408
		$persons = $this->findAll($userId, $modelId);
409
		foreach ($persons as $person) {
410
			$qb->setParameter('person', $person->getId())->execute();
0 ignored issues
show
Deprecated Code introduced by
The function OCP\DB\QueryBuilder\IQueryBuilder::execute() has been deprecated: 22.0.0 Use executeQuery or executeStatement ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

410
			/** @scrutinizer ignore-deprecated */ $qb->setParameter('person', $person->getId())->execute();

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
411
		}
412
	}
413
414
	/**
415
	 * Deletes person if it is empty (have no faces associated to it)
416
	 *
417
	 * @param int $personId Person to check if it should be deleted
418
	 *
419
	 * @return void
420
	 */
421
	public function removeIfEmpty(int $personId): void {
422
		$sub = $this->db->getQueryBuilder();
423
		$sub->select(new Literal('1'));
424
		$sub->from('facerecog_faces', 'f')
425
			->where($sub->expr()->eq('f.person', $sub->createParameter('person')));
426
427
		$qb = $this->db->getQueryBuilder();
428
		$qb->delete($this->getTableName())
0 ignored issues
show
Deprecated Code introduced by
The function OCP\DB\QueryBuilder\IQueryBuilder::execute() has been deprecated: 22.0.0 Use executeQuery or executeStatement ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

428
		/** @scrutinizer ignore-deprecated */ $qb->delete($this->getTableName())

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
429
			->where($qb->expr()->eq('id', $qb->createParameter('person')))
430
			->andWhere('NOT EXISTS (' . $sub->getSQL() . ')')
431
			->setParameter('person', $personId)
432
			->execute();
433
	}
434
435
	/**
436
	 * Deletes all persons that have no faces associated to them
437
	 *
438
	 * @param string $userId ID of user for which we are deleting orphaned persons
439
	 */
440 1
	public function deleteOrphaned(string $userId): int {
441 1
		$sub = $this->db->getQueryBuilder();
442 1
		$sub->select(new Literal('1'));
443 1
		$sub->from('facerecog_faces', 'f')
444 1
			->where($sub->expr()->eq('f.person', 'p.id'));
445
446 1
		$qb = $this->db->getQueryBuilder();
447 1
		$qb->select('p.id')
448 1
			->from($this->getTableName(), 'p')
449 1
			->where($qb->expr()->eq('p.user', $qb->createParameter('user')))
450 1
			->andWhere('NOT EXISTS (' . $sub->getSQL() . ')')
451 1
			->setParameter('user', $userId);
452 1
		$orphanedPersons = $this->findEntities($qb);
453
454 1
		$orphaned = 0;
455 1
		foreach ($orphanedPersons as $person) {
456
			$qb = $this->db->getQueryBuilder();
457
			$orphaned += $qb->delete($this->getTableName())
0 ignored issues
show
Deprecated Code introduced by
The function OCP\DB\QueryBuilder\IQueryBuilder::execute() has been deprecated: 22.0.0 Use executeQuery or executeStatement ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

457
			$orphaned += /** @scrutinizer ignore-deprecated */ $qb->delete($this->getTableName())

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
458
				->where($qb->expr()->eq('id', $qb->createNamedParameter($person->id)))
459
				->execute();
460
		}
461 1
		return $orphaned;
462
	}
463
464
	/*
465
	 * Mark the cluster as hidden or visible to user.
466
	 *
467
	 * @param int $personId ID of the person
468
	 * @param bool $visible visibility of the person
469
	 *
470
	 * @return void
471
	 */
472
	public function setVisibility (int $personId, bool $visible): void {
473
		$qb = $this->db->getQueryBuilder();
474
		if ($visible) {
475
			$qb->update($this->getTableName())
0 ignored issues
show
Deprecated Code introduced by
The function OCP\DB\QueryBuilder\IQueryBuilder::execute() has been deprecated: 22.0.0 Use executeQuery or executeStatement ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

475
			/** @scrutinizer ignore-deprecated */ $qb->update($this->getTableName())

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
476
				->set('is_visible', $qb->createNamedParameter(1))
477
				->where($qb->expr()->eq('id', $qb->createNamedParameter($personId)))
478
				->execute();
479
		} else {
480
			$qb->update($this->getTableName())
0 ignored issues
show
Deprecated Code introduced by
The function OCP\DB\QueryBuilder\IQueryBuilder::execute() has been deprecated: 22.0.0 Use executeQuery or executeStatement ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

480
			/** @scrutinizer ignore-deprecated */ $qb->update($this->getTableName())

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
481
				->set('is_visible', $qb->createNamedParameter(0))
482
				->set('name', $qb->createNamedParameter(null))
483
				->where($qb->expr()->eq('id', $qb->createNamedParameter($personId)))
484
				->execute();
485
		}
486
	}
487
488
	/*
489
	 * Mark the cluster as hidden or visible to user.
490
	 *
491
	 * @param int $personId ID of the person
492
	 * @param int $faceId visibility of the person
493
	 * @param string|null $name optional name to rename them.
494
	 *
495
	 * @return Person
496
	 */
497
	public function detachFace(int $personId, int $faceId, $name = null): Person {
498
		// Mark the face as non groupable.
499
		$qb = $this->db->getQueryBuilder();
500
		$qb->update('facerecog_faces')
0 ignored issues
show
Deprecated Code introduced by
The function OCP\DB\QueryBuilder\IQueryBuilder::execute() has been deprecated: 22.0.0 Use executeQuery or executeStatement ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

500
		/** @scrutinizer ignore-deprecated */ $qb->update('facerecog_faces')

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
501
			->set('is_groupable', $qb->createParameter('is_groupable'))
502
			->where($qb->expr()->eq('id', $qb->createNamedParameter($faceId)))
503
			->setParameter('is_groupable', false, IQueryBuilder::PARAM_BOOL)
504
			->execute();
505
506
		if ($this->countClusterFaces($personId) === 1) {
507
			// If cluster is an single face just rename it.
508
			$qb = $this->db->getQueryBuilder();
509
			$qb->update($this->getTableName())
0 ignored issues
show
Deprecated Code introduced by
The function OCP\DB\QueryBuilder\IQueryBuilder::execute() has been deprecated: 22.0.0 Use executeQuery or executeStatement ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

509
			/** @scrutinizer ignore-deprecated */ $qb->update($this->getTableName())

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
510
				->set('name', $qb->createNamedParameter($name))
511
				->set('is_visible', $qb->createNamedParameter(true))
512
				->where($qb->expr()->eq('id', $qb->createNamedParameter($personId)))
513
				->execute();
514
		} else {
515
			// If there are other faces, must create a new person for that face.
516
			$qb = $this->db->getQueryBuilder();
517
			$qb->select('user')
518
				->from($this->getTableName())
519
				->where($qb->expr()->eq('id', $qb->createNamedParameter($personId)));
520
			$oldPerson = $this->findEntity($qb);
521
522
			$qb = $this->db->getQueryBuilder();
523
			$qb->insert($this->getTableName())->values([
0 ignored issues
show
Deprecated Code introduced by
The function OCP\DB\QueryBuilder\IQueryBuilder::execute() has been deprecated: 22.0.0 Use executeQuery or executeStatement ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

523
			/** @scrutinizer ignore-deprecated */ $qb->insert($this->getTableName())->values([

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
524
				'user' => $qb->createNamedParameter($oldPerson->getUser()),
525
				'name' => $qb->createNamedParameter($name),
526
				'is_valid' => $qb->createNamedParameter(true),
527
				'last_generation_time' => $qb->createNamedParameter(new \DateTime(), IQueryBuilder::PARAM_DATE),
528
				'linked_user' => $qb->createNamedParameter(null),
529
				'is_visible' => $qb->createNamedParameter(true)
530
			])->execute();
531
532
			$personId = $qb->getLastInsertId();
533
534
			$qb = $this->db->getQueryBuilder();
535
			$qb->update('facerecog_faces')
0 ignored issues
show
Deprecated Code introduced by
The function OCP\DB\QueryBuilder\IQueryBuilder::execute() has been deprecated: 22.0.0 Use executeQuery or executeStatement ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

535
			/** @scrutinizer ignore-deprecated */ $qb->update('facerecog_faces')

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
536
				->set('person', $qb->createParameter('person'))
537
				->where($qb->expr()->eq('id', $qb->createNamedParameter($faceId)))
538
				->setParameter('person', $personId)
539
				->execute();
540
		}
541
542
		$qb = $this->db->getQueryBuilder();
543
		$qb->select('id', 'name', 'is_valid', 'is_visible')
544
		   ->from($this->getTableName())
545
		   ->where($qb->expr()->eq('id', $qb->createNamedParameter($personId)));
546
		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...
547
	}
548
549
	public function countClusterFaces(int $personId): int {
550
		$qb = $this->db->getQueryBuilder();
551
		$query = $qb
552
			->select($qb->createFunction('COUNT(' . $qb->getColumnName('id') . ')'))
553
			->from('facerecog_faces')
554
			->where($qb->expr()->eq('person', $qb->createParameter('person')))
555
			->setParameter('person', $personId);
556
		$resultStatement = $query->execute();
0 ignored issues
show
Deprecated Code introduced by
The function OCP\DB\QueryBuilder\IQueryBuilder::execute() has been deprecated: 22.0.0 Use executeQuery or executeStatement ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

556
		$resultStatement = /** @scrutinizer ignore-deprecated */ $query->execute();

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
557
		$data = $resultStatement->fetch(\PDO::FETCH_NUM);
558
		$resultStatement->closeCursor();
559
560
		return (int)$data[0];
561
	}
562
563
	/**
564
	 * Updates one face with $faceId to database to person ID $personId.
565
	 *
566
	 * @param int $faceId ID of the face
567
	 * @param int|null $personId ID of the person
568
	 *
569
	 * @return void
570
	 */
571 12
	private function updateFace(int $faceId, $personId): void {
572 12
		$qb = $this->db->getQueryBuilder();
573 12
		$qb->update('facerecog_faces')
0 ignored issues
show
Deprecated Code introduced by
The function OCP\DB\QueryBuilder\IQueryBuilder::execute() has been deprecated: 22.0.0 Use executeQuery or executeStatement ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

573
		/** @scrutinizer ignore-deprecated */ $qb->update('facerecog_faces')

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
574 12
			->set("person", $qb->createNamedParameter($personId))
575 12
			->where($qb->expr()->eq('id', $qb->createNamedParameter($faceId)))
576 12
			->execute();
577
	}
578
579
	/**
580
	 * Checks if face with a given ID is in any cluster.
581
	 *
582
	 * @param int $faceId ID of the face to check
583
	 * @param array $cluster All clusters to check into
584
	 *
585
	 * @return bool True if face is found in any cluster, false otherwise.
586
	 */
587 5
	private function isFaceInClusters(int $faceId, array $clusters): bool {
588 5
		foreach ($clusters as $_=>$faces) {
589 5
			if (in_array($faceId, $faces)) {
590 5
				return true;
591
			}
592
		}
593 1
		return false;
594
	}
595
}
596