|
1
|
|
|
<?php |
|
2
|
|
|
/** |
|
3
|
|
|
* @copyright Copyright (c) 2017, Matias De lellis <[email protected]> |
|
4
|
|
|
* @copyright Copyright (c) 2018, 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 OCP\IDBConnection; |
|
27
|
|
|
use OCP\IUser; |
|
28
|
|
|
|
|
29
|
|
|
use OCP\AppFramework\Db\Mapper; |
|
30
|
|
|
use OCP\AppFramework\Db\DoesNotExistException; |
|
31
|
|
|
use OCP\DB\QueryBuilder\IQueryBuilder; |
|
32
|
|
|
|
|
33
|
|
|
class ImageMapper extends Mapper { |
|
34
|
|
|
|
|
35
|
|
|
public function __construct(IDBConnection $db) { |
|
36
|
|
|
parent::__construct($db, 'face_recognition_images', '\OCA\FaceRecognition\Db\Image'); |
|
37
|
|
|
} |
|
38
|
|
|
|
|
39
|
|
View Code Duplication |
public function find (string $userId, int $imageId): Image { |
|
|
|
|
|
|
40
|
|
|
$qb = $this->db->getQueryBuilder(); |
|
41
|
|
|
$qb->select('id', 'file') |
|
42
|
|
|
->from('face_recognition_images', 'i') |
|
43
|
|
|
->where($qb->expr()->eq('user', $qb->createParameter('user_id'))) |
|
44
|
|
|
->andWhere($qb->expr()->eq('id', $qb->createParameter('image_id'))); |
|
45
|
|
|
$params = array(); |
|
46
|
|
|
$params['user_id'] = $userId; |
|
47
|
|
|
$params['image_id'] = $imageId; |
|
48
|
|
|
$image = $this->findEntity($qb->getSQL(), $params); |
|
49
|
|
|
return $image; |
|
50
|
|
|
} |
|
51
|
|
|
|
|
52
|
|
|
public function imageExists(Image $image) { |
|
53
|
|
|
$qb = $this->db->getQueryBuilder(); |
|
54
|
|
|
$query = $qb |
|
55
|
|
|
->select(['id']) |
|
56
|
|
|
->from('face_recognition_images') |
|
57
|
|
|
->where($qb->expr()->eq('user', $qb->createParameter('user'))) |
|
58
|
|
|
->andWhere($qb->expr()->eq('file', $qb->createParameter('file'))) |
|
59
|
|
|
->andWhere($qb->expr()->eq('model', $qb->createParameter('model'))) |
|
60
|
|
|
->setParameter('user', $image->getUser()) |
|
61
|
|
|
->setParameter('file', $image->getFile()) |
|
62
|
|
|
->setParameter('model', $image->getModel()); |
|
63
|
|
|
$resultStatement = $query->execute(); |
|
64
|
|
|
$row = $resultStatement->fetch(); |
|
65
|
|
|
$resultStatement->closeCursor(); |
|
66
|
|
|
return $row ? (int)$row['id'] : null; |
|
67
|
|
|
} |
|
68
|
|
|
|
|
69
|
|
|
public function countUserImages(string $userId, $model): int { |
|
70
|
|
|
$qb = $this->db->getQueryBuilder(); |
|
71
|
|
|
$query = $qb |
|
72
|
|
|
->select($qb->createFunction('COUNT(' . $qb->getColumnName('id') . ')')) |
|
73
|
|
|
->from('face_recognition_images') |
|
74
|
|
|
->where($qb->expr()->eq('user', $qb->createParameter('user'))) |
|
75
|
|
|
->andWhere($qb->expr()->eq('model', $qb->createParameter('model'))) |
|
76
|
|
|
->setParameter('user', $userId) |
|
77
|
|
|
->setParameter('model', $model); |
|
78
|
|
|
$resultStatement = $query->execute(); |
|
79
|
|
|
$data = $resultStatement->fetch(\PDO::FETCH_NUM); |
|
80
|
|
|
$resultStatement->closeCursor(); |
|
81
|
|
|
|
|
82
|
|
|
return (int)$data[0]; |
|
83
|
|
|
} |
|
84
|
|
|
|
|
85
|
|
|
public function countUserProcessedImages(string $userId, $model): int { |
|
86
|
|
|
$qb = $this->db->getQueryBuilder(); |
|
87
|
|
|
$query = $qb |
|
88
|
|
|
->select($qb->createFunction('COUNT(' . $qb->getColumnName('id') . ')')) |
|
89
|
|
|
->from('face_recognition_images') |
|
90
|
|
|
->where($qb->expr()->eq('user', $qb->createParameter('user'))) |
|
91
|
|
|
->andWhere($qb->expr()->eq('model', $qb->createParameter('model'))) |
|
92
|
|
|
->andWhere($qb->expr()->eq('is_processed', $qb->createParameter('is_processed'))) |
|
93
|
|
|
->setParameter('user', $userId) |
|
94
|
|
|
->setParameter('model', $model) |
|
95
|
|
|
->setParameter('is_processed', True); |
|
96
|
|
|
$resultStatement = $query->execute(); |
|
97
|
|
|
$data = $resultStatement->fetch(\PDO::FETCH_NUM); |
|
98
|
|
|
$resultStatement->closeCursor(); |
|
99
|
|
|
|
|
100
|
|
|
return (int)$data[0]; |
|
101
|
|
|
} |
|
102
|
|
|
|
|
103
|
|
|
/** |
|
104
|
|
|
* @param IUser|null $user User for which to get images for. If not given, all images from instance are returned. |
|
105
|
|
|
*/ |
|
106
|
|
|
public function findImagesWithoutFaces(IUser $user = null) { |
|
107
|
|
|
$qb = $this->db->getQueryBuilder(); |
|
108
|
|
|
$params = array(); |
|
109
|
|
|
|
|
110
|
|
|
$query = $qb |
|
111
|
|
|
->select(['id', 'user', 'file', 'model']) |
|
112
|
|
|
->from('face_recognition_images') |
|
113
|
|
|
->where($qb->expr()->eq('is_processed', $qb->createParameter('is_processed'))); |
|
114
|
|
|
$params['is_processed'] = False; |
|
115
|
|
|
if (!is_null($user)) { |
|
116
|
|
|
$query->andWhere($qb->expr()->eq('user', $qb->createParameter('user'))); |
|
117
|
|
|
$params['user'] = $user->getUID(); |
|
118
|
|
|
} |
|
119
|
|
|
|
|
120
|
|
|
$images = $this->findEntities($qb->getSQL(), $params); |
|
121
|
|
|
return $images; |
|
122
|
|
|
} |
|
123
|
|
|
|
|
124
|
|
|
|
|
125
|
|
|
public function findImagesFromPerson(string $userId, string $name, int $model): array { |
|
126
|
|
|
$qb = $this->db->getQueryBuilder(); |
|
127
|
|
|
$qb->select('i.id', 'i.file') |
|
128
|
|
|
->from('face_recognition_images', 'i') |
|
129
|
|
|
->innerJoin('i', 'face_recognition_faces', 'f', $qb->expr()->eq('f.image', 'i.id')) |
|
130
|
|
|
->innerJoin('i', 'face_recognition_persons', 'p', $qb->expr()->eq('f.person', 'p.id')) |
|
131
|
|
|
->where($qb->expr()->eq('p.user', $qb->createParameter('user'))) |
|
132
|
|
|
->andWhere($qb->expr()->eq('model', $qb->createParameter('model'))) |
|
133
|
|
|
->andWhere($qb->expr()->eq('is_processed', $qb->createParameter('is_processed'))) |
|
134
|
|
|
->andWhere($qb->expr()->like($qb->func()->lower('p.name'), $qb->createParameter('query'))); |
|
135
|
|
|
|
|
136
|
|
|
$params = array(); |
|
137
|
|
|
$params['user'] = $userId; |
|
138
|
|
|
$params['model'] = $model; |
|
139
|
|
|
$params['is_processed'] = True; |
|
140
|
|
|
$params['query'] = '%' . $this->db->escapeLikeParameter(strtolower($name)) . '%'; |
|
141
|
|
|
$images = $this->findEntities($qb->getSQL(), $params); |
|
142
|
|
|
return $images; |
|
143
|
|
|
} |
|
144
|
|
|
|
|
145
|
|
|
/** |
|
146
|
|
|
* Writes to DB that image has been processed. Previously found faces are deleted and new ones are inserted. |
|
147
|
|
|
* If there is exception, its stack trace is also updated. |
|
148
|
|
|
* |
|
149
|
|
|
* @param Image $image Image to be updated |
|
150
|
|
|
* @param FaceNew[] $faces Faces to insert |
|
151
|
|
|
* @param int $duration Processing time, in milliseconds |
|
152
|
|
|
* @param \Exception|null $e Any exception that happened during image processing |
|
153
|
|
|
*/ |
|
154
|
|
|
public function imageProcessed(Image $image, array $faces, int $duration, \Exception $e = null) { |
|
155
|
|
|
$this->db->beginTransaction(); |
|
156
|
|
|
try { |
|
157
|
|
|
// Update image itself |
|
158
|
|
|
// |
|
159
|
|
|
$error = null; |
|
160
|
|
|
if ($e != null) { |
|
161
|
|
|
$error = substr($e->getMessage(), 0, 1024); |
|
162
|
|
|
} |
|
163
|
|
|
|
|
164
|
|
|
$qb = $this->db->getQueryBuilder(); |
|
165
|
|
|
$qb->update('face_recognition_images') |
|
166
|
|
|
->set("is_processed", $qb->createNamedParameter(true, IQueryBuilder::PARAM_BOOL)) |
|
167
|
|
|
->set("error", $qb->createNamedParameter($error)) |
|
168
|
|
|
->set("last_processed_time", $qb->createNamedParameter($currentDateTime, IQueryBuilder::PARAM_DATE)) |
|
|
|
|
|
|
169
|
|
|
->set("processing_duration", $qb->createNamedParameter($duration)) |
|
170
|
|
|
->where($qb->expr()->eq('id', $qb->createNamedParameter($image->id))) |
|
171
|
|
|
->execute(); |
|
172
|
|
|
|
|
173
|
|
|
// Delete all previous faces |
|
174
|
|
|
// |
|
175
|
|
|
$qb = $this->db->getQueryBuilder(); |
|
176
|
|
|
$qb->delete('face_recognition_faces') |
|
177
|
|
|
->where($qb->expr()->eq('image', $qb->createNamedParameter($image->id))) |
|
178
|
|
|
->execute(); |
|
179
|
|
|
|
|
180
|
|
|
// Insert all faces |
|
181
|
|
|
// |
|
182
|
|
|
foreach ($faces as $face) { |
|
183
|
|
|
// Simple INSERT will close cursor and we want to be in transaction, so use hard way |
|
184
|
|
|
// todo: should we move this to FaceNewMapper (don't forget to hand over connection though) |
|
185
|
|
|
$qb = $this->db->getQueryBuilder(); |
|
186
|
|
|
$qb->insert('face_recognition_faces') |
|
187
|
|
|
->values([ |
|
188
|
|
|
'image' => $qb->createNamedParameter($image->id), |
|
189
|
|
|
'person' => $qb->createNamedParameter(null), |
|
190
|
|
|
'left' => $qb->createNamedParameter($face->left), |
|
191
|
|
|
'right' => $qb->createNamedParameter($face->right), |
|
192
|
|
|
'top' => $qb->createNamedParameter($face->top), |
|
193
|
|
|
'bottom' => $qb->createNamedParameter($face->bottom), |
|
194
|
|
|
'descriptor' => $qb->createNamedParameter(json_encode($face->descriptor)), |
|
195
|
|
|
'creation_time' => $qb->createNamedParameter($face->creation_time, IQueryBuilder::PARAM_DATE), |
|
196
|
|
|
]) |
|
197
|
|
|
->execute(); |
|
198
|
|
|
} |
|
199
|
|
|
|
|
200
|
|
|
$this->db->commit(); |
|
201
|
|
|
} catch (\Exception $e) { |
|
202
|
|
|
$this->db->rollBack(); |
|
203
|
|
|
throw $e; |
|
204
|
|
|
} |
|
205
|
|
|
} |
|
206
|
|
|
|
|
207
|
|
|
/** |
|
208
|
|
|
* Resets image by deleting all associated faces and prepares it to be processed again |
|
209
|
|
|
* |
|
210
|
|
|
* @param Image $image Image to reset |
|
211
|
|
|
*/ |
|
212
|
|
|
public function resetImage(Image $image) { |
|
213
|
|
|
$qb = $this->db->getQueryBuilder(); |
|
214
|
|
|
$qb->update($this->getTableName()) |
|
215
|
|
|
->set("is_processed", $qb->createNamedParameter(false, IQueryBuilder::PARAM_BOOL)) |
|
216
|
|
|
->set("error", $qb->createNamedParameter(null)) |
|
217
|
|
|
->set("last_processed_time", $qb->createNamedParameter(null)) |
|
218
|
|
|
->where($qb->expr()->eq('user', $qb->createNamedParameter($image->getUser()))) |
|
219
|
|
|
->andWhere($qb->expr()->eq('file', $qb->createNamedParameter($image->getFile()))) |
|
220
|
|
|
->andWhere($qb->expr()->eq('model', $qb->createNamedParameter($image->getModel()))) |
|
221
|
|
|
->execute(); |
|
222
|
|
|
} |
|
223
|
|
|
} |
|
224
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.