1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* @copyright Copyright (c) 2016, Roeland Jago Douma <[email protected]> |
4
|
|
|
* @copyright Copyright (c) 2017, Matias De lellis <[email protected]> |
5
|
|
|
* |
6
|
|
|
* @author Roeland Jago Douma <[email protected]> |
7
|
|
|
* @author Matias De lellis <[email protected]> |
8
|
|
|
* |
9
|
|
|
* @license GNU AGPL version 3 or any later version |
10
|
|
|
* |
11
|
|
|
* This program is free software: you can redistribute it and/or modify |
12
|
|
|
* it under the terms of the GNU Affero General Public License as |
13
|
|
|
* published by the Free Software Foundation, either version 3 of the |
14
|
|
|
* License, or (at your option) any later version. |
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 |
22
|
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>. |
23
|
|
|
* |
24
|
|
|
*/ |
25
|
|
|
namespace OCA\FaceRecognition; |
26
|
|
|
|
27
|
|
|
use OCP\Files\Folder; |
28
|
|
|
use OCP\Files\IHomeStorage; |
29
|
|
|
use OCP\Files\Node; |
30
|
|
|
use OCP\IConfig; |
31
|
|
|
use OCP\ILogger; |
32
|
|
|
use OCP\IUserManager; |
33
|
|
|
use OCP\User; |
34
|
|
|
|
35
|
|
|
use OCA\FaceRecognition\FaceManagementService; |
36
|
|
|
use OCA\FaceRecognition\Service\FileService; |
37
|
|
|
|
38
|
|
|
use OCA\FaceRecognition\BackgroundJob\Tasks\AddMissingImagesTask; |
39
|
|
|
use OCA\FaceRecognition\BackgroundJob\Tasks\StaleImagesRemovalTask; |
40
|
|
|
use OCA\FaceRecognition\Db\Face; |
41
|
|
|
use OCA\FaceRecognition\Db\Image; |
42
|
|
|
use OCA\FaceRecognition\Db\FaceMapper; |
43
|
|
|
use OCA\FaceRecognition\Db\ImageMapper; |
44
|
|
|
use OCA\FaceRecognition\Db\PersonMapper; |
45
|
|
|
use OCA\FaceRecognition\Helper\Requirements; |
46
|
|
|
use OCA\FaceRecognition\Migration\AddDefaultFaceModel; |
47
|
|
|
|
48
|
|
|
class Watcher { |
49
|
|
|
|
50
|
|
|
/** @var IConfig Config */ |
51
|
|
|
private $config; |
52
|
|
|
|
53
|
|
|
/** @var ILogger Logger */ |
54
|
|
|
private $logger; |
55
|
|
|
|
56
|
|
|
/** @var IUserManager */ |
57
|
|
|
private $userManager; |
58
|
|
|
|
59
|
|
|
/** @var FaceMapper */ |
60
|
|
|
private $faceMapper; |
61
|
|
|
|
62
|
|
|
/** @var ImageMapper */ |
63
|
|
|
private $imageMapper; |
64
|
|
|
|
65
|
|
|
/** @var PersonMapper */ |
66
|
|
|
private $personMapper; |
67
|
|
|
|
68
|
|
|
/** @var FileService */ |
69
|
|
|
private $fileService; |
70
|
|
|
|
71
|
|
|
/** @var FaceManagementService */ |
72
|
|
|
private $faceManagementService; |
73
|
|
|
|
74
|
|
|
/** |
75
|
|
|
* Watcher constructor. |
76
|
|
|
* |
77
|
|
|
* @param IConfig $config |
78
|
|
|
* @param ILogger $logger |
79
|
|
|
* @param IUserManager $userManager |
80
|
|
|
* @param FaceMapper $faceMapper |
81
|
|
|
* @param ImageMapper $imageMapper |
82
|
|
|
* @param PersonMapper $personMapper |
83
|
|
|
* @param FileService $fileService |
84
|
|
|
* @param FaceManagementService $faceManagementService |
85
|
|
|
*/ |
86
|
|
|
public function __construct(IConfig $config, |
87
|
|
|
ILogger $logger, |
88
|
|
|
IUserManager $userManager, |
89
|
|
|
FaceMapper $faceMapper, |
90
|
|
|
ImageMapper $imageMapper, |
91
|
|
|
PersonMapper $personMapper, |
92
|
|
|
FileService $fileService, |
93
|
|
|
FaceManagementService $faceManagementService) |
94
|
|
|
{ |
95
|
|
|
$this->config = $config; |
96
|
|
|
$this->logger = $logger; |
97
|
|
|
$this->userManager = $userManager; |
98
|
|
|
$this->faceMapper = $faceMapper; |
99
|
|
|
$this->imageMapper = $imageMapper; |
100
|
|
|
$this->personMapper = $personMapper; |
101
|
|
|
$this->fileService = $fileService; |
102
|
|
|
$this->faceManagementService = $faceManagementService; |
103
|
|
|
} |
104
|
|
|
|
105
|
|
|
/** |
106
|
|
|
* A node has been updated. We just store the file id |
107
|
|
|
* with the current user in the DB |
108
|
|
|
* |
109
|
|
|
* @param Node $node |
110
|
|
|
*/ |
111
|
16 |
|
public function postWrite(Node $node) { |
112
|
16 |
|
$model = intval($this->config->getAppValue('facerecognition', 'model', AddDefaultFaceModel::DEFAULT_FACE_MODEL_ID)); |
113
|
16 |
|
$handleSharedFiles = $this->config->getAppValue('facerecognition', 'handle-shared-files', 'false'); |
114
|
|
|
|
115
|
16 |
|
if ($this->fileService->isUserFile($node)) { |
116
|
16 |
|
$owner = $node->getOwner()->getUid(); |
117
|
|
|
} |
118
|
16 |
|
else if ($handleSharedFiles === 'true' && $this->fileService->isSharedFile($node)) { |
119
|
|
|
// If we are going to analyze the shared files, we must 'appropriate' it. |
120
|
|
|
$owner = User::getUser(); |
|
|
|
|
121
|
|
|
} |
122
|
|
|
else { |
123
|
|
|
// Nextcloud also sends the Hooks when create thumbnails for example. |
124
|
16 |
|
return; |
125
|
|
|
} |
126
|
|
|
|
127
|
16 |
|
if ($node instanceof Folder) { |
128
|
16 |
|
return; |
129
|
|
|
} |
130
|
|
|
|
131
|
|
|
$enabled = $this->config->getUserValue($owner, 'facerecognition', 'enabled', 'false'); |
132
|
|
|
if ($enabled !== 'true') { |
133
|
|
|
$this->logger->debug('The user ' . $owner . ' not have the analysis enabled. Skipping'); |
134
|
|
|
return; |
135
|
|
|
} |
136
|
|
|
|
137
|
|
|
if ($node->getName() === '.nomedia') { |
138
|
|
|
// If user added this file, it means all images in this and all child directories should be removed. |
139
|
|
|
// Instead of doing that here, it's better to just add flag that image removal should be done. |
140
|
|
|
$this->config->setUserValue($owner, 'facerecognition', StaleImagesRemovalTask::STALE_IMAGES_REMOVAL_NEEDED_KEY, 'true'); |
141
|
|
|
return; |
142
|
|
|
} |
143
|
|
|
|
144
|
|
|
if ($node->getName() === '.facerecognition.json') { |
145
|
|
|
// This file can enable or disable the analysis, so I have to look for new files and forget others. |
146
|
|
|
$this->config->setUserValue($owner, 'facerecognition', StaleImagesRemovalTask::STALE_IMAGES_REMOVAL_NEEDED_KEY, 'true'); |
147
|
|
|
$this->config->setUserValue($owner, 'facerecognition', AddMissingImagesTask::FULL_IMAGE_SCAN_DONE_KEY, 'false'); |
148
|
|
|
return; |
149
|
|
|
} |
150
|
|
|
|
151
|
|
|
if (!Requirements::isImageTypeSupported($node->getMimeType())) { |
152
|
|
|
return; |
153
|
|
|
} |
154
|
|
|
|
155
|
|
|
if (!$this->userManager->userExists($owner)) { |
156
|
|
|
$this->logger->debug( |
157
|
|
|
"Skipping inserting image " . $node->getName() . " because it seems that user " . $owner . " doesn't exist"); |
158
|
|
|
return; |
159
|
|
|
} |
160
|
|
|
|
161
|
|
|
if ($this->fileService->isUnderNoDetection($node)) { |
162
|
|
|
$this->logger->debug( |
163
|
|
|
"Skipping inserting image " . $node->getName() . " because is inside an folder that contains a .nomedia file"); |
164
|
|
|
return; |
165
|
|
|
} |
166
|
|
|
|
167
|
|
|
$this->logger->debug("Inserting/updating image " . $node->getName() . " for face recognition"); |
168
|
|
|
|
169
|
|
|
$image = new Image(); |
170
|
|
|
$image->setUser($owner); |
171
|
|
|
$image->setFile($node->getId()); |
172
|
|
|
$image->setModel($model); |
173
|
|
|
|
174
|
|
|
$imageId = $this->imageMapper->imageExists($image); |
175
|
|
|
if ($imageId === null) { |
176
|
|
|
// todo: can we have larger transaction with bulk insert? |
177
|
|
|
$this->imageMapper->insert($image); |
178
|
|
|
} else { |
179
|
|
|
$this->imageMapper->resetImage($image); |
180
|
|
|
// note that invalidatePersons depends on existence of faces for a given image, |
181
|
|
|
// and we must invalidate before we delete faces! |
182
|
|
|
$this->personMapper->invalidatePersons($imageId); |
183
|
|
|
|
184
|
|
|
// Fetch all faces to be deleted before deleting them, and then delete them |
185
|
|
|
$facesToRemove = $this->faceMapper->findByImage($imageId); |
186
|
|
|
$this->faceMapper->removeFaces($imageId); |
187
|
|
|
|
188
|
|
|
// If any person is now without faces, remove those (empty) persons |
189
|
|
|
foreach ($facesToRemove as $faceToRemove) { |
190
|
|
|
if ($faceToRemove->getPerson() !== null) { |
191
|
|
|
$this->personMapper->removeIfEmpty($faceToRemove->getPerson()); |
192
|
|
|
} |
193
|
|
|
} |
194
|
|
|
} |
195
|
|
|
} |
196
|
|
|
|
197
|
|
|
/** |
198
|
|
|
* A node has been deleted. Remove faces with file id |
199
|
|
|
* with the current user in the DB |
200
|
|
|
* |
201
|
|
|
* @param Node $node |
202
|
|
|
*/ |
203
|
|
|
public function postDelete(Node $node) { |
204
|
|
|
$model = intval($this->config->getAppValue('facerecognition', 'model', AddDefaultFaceModel::DEFAULT_FACE_MODEL_ID)); |
205
|
|
|
$handleSharedFiles = $this->config->getAppValue('facerecognition', 'handle-shared-files', 'false'); |
206
|
|
|
|
207
|
|
|
if ($this->fileService->isUserFile($node)) { |
208
|
|
|
$owner = $node->getOwner()->getUid(); |
209
|
|
|
} |
210
|
|
|
else if ($handleSharedFiles === 'true' && $this->fileService->isSharedFile($node)) { |
211
|
|
|
// If we are going to analyze the shared files, we must 'appropriate' it. |
212
|
|
|
$owner = User::getUser(); |
|
|
|
|
213
|
|
|
} |
214
|
|
|
else { |
215
|
|
|
// Nextcloud also sends the Hooks when create thumbnails for example. |
216
|
|
|
return; |
217
|
|
|
} |
218
|
|
|
|
219
|
|
|
if ($node instanceof Folder) { |
220
|
|
|
return; |
221
|
|
|
} |
222
|
|
|
|
223
|
|
|
$enabled = $this->config->getUserValue($owner, 'facerecognition', 'enabled', 'false'); |
224
|
|
|
if ($enabled !== 'true') { |
225
|
|
|
$this->logger->debug('The user ' . $owner . ' not have the analysis enabled. Skipping'); |
226
|
|
|
return; |
227
|
|
|
} |
228
|
|
|
|
229
|
|
|
if ($node->getName() === '.nomedia') { |
230
|
|
|
// If user deleted file named .nomedia, that means all images in this and all child directories should be added. |
231
|
|
|
// But, instead of doing that here, better option seem to be to just reset flag that image scan is not done. |
232
|
|
|
// This will trigger another round of image crawling in AddMissingImagesTask for this user and those images will be added. |
233
|
|
|
$this->config->setUserValue($owner, 'facerecognition', AddMissingImagesTask::FULL_IMAGE_SCAN_DONE_KEY, 'false'); |
234
|
|
|
return; |
235
|
|
|
} |
236
|
|
|
|
237
|
|
|
if ($node->getName() === '.facerecognition.json') { |
238
|
|
|
// This file can enable or disable the analysis, so I have to look for new files and forget others. |
239
|
|
|
$this->config->setUserValue($owner, 'facerecognition', StaleImagesRemovalTask::STALE_IMAGES_REMOVAL_NEEDED_KEY, 'true'); |
240
|
|
|
$this->config->setUserValue($owner, 'facerecognition', AddMissingImagesTask::FULL_IMAGE_SCAN_DONE_KEY, 'false'); |
241
|
|
|
return; |
242
|
|
|
} |
243
|
|
|
|
244
|
|
|
if (!Requirements::isImageTypeSupported($node->getMimeType())) { |
245
|
|
|
return; |
246
|
|
|
} |
247
|
|
|
|
248
|
|
|
$this->logger->debug("Deleting image " . $node->getName() . " from face recognition"); |
249
|
|
|
|
250
|
|
|
$image = new Image(); |
251
|
|
|
$image->setUser($owner); |
252
|
|
|
$image->setFile($node->getId()); |
253
|
|
|
$image->setModel($model); |
254
|
|
|
|
255
|
|
|
$imageId = $this->imageMapper->imageExists($image); |
256
|
|
|
if ($imageId !== null) { |
257
|
|
|
// note that invalidatePersons depends on existence of faces for a given image, |
258
|
|
|
// and we must invalidate before we delete faces! |
259
|
|
|
$this->personMapper->invalidatePersons($imageId); |
260
|
|
|
|
261
|
|
|
// Fetch all faces to be deleted before deleting them, and then delete them |
262
|
|
|
$facesToRemove = $this->faceMapper->findByImage($imageId); |
263
|
|
|
$this->faceMapper->removeFaces($imageId); |
264
|
|
|
|
265
|
|
|
$image->setId($imageId); |
266
|
|
|
$this->imageMapper->delete($image); |
267
|
|
|
|
268
|
|
|
// If any person is now without faces, remove those (empty) persons |
269
|
|
|
foreach ($facesToRemove as $faceToRemove) { |
270
|
|
|
if ($faceToRemove->getPerson() !== null) { |
271
|
|
|
$this->personMapper->removeIfEmpty($faceToRemove->getPerson()); |
272
|
|
|
} |
273
|
|
|
} |
274
|
|
|
} |
275
|
|
|
} |
276
|
|
|
|
277
|
|
|
/** |
278
|
|
|
* A user has been deleted. Cleanup everything from this user. |
279
|
|
|
* |
280
|
|
|
* @param \OC\User\User $user Deleted user |
281
|
|
|
*/ |
282
|
16 |
|
public function postUserDelete(\OC\User\User $user) { |
|
|
|
|
283
|
16 |
|
$userId = $user->getUid(); |
284
|
16 |
|
$this->faceManagementService->resetAllForUser($userId); |
285
|
16 |
|
$this->logger->info("Removed all face recognition data for deleted user " . $userId); |
286
|
16 |
|
} |
287
|
|
|
} |
288
|
|
|
|
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.