Passed
Pull Request — master (#232)
by Matias
05:40 queued 04:10
created

Watcher   A

Complexity

Total Complexity 26

Size/Duplication

Total Lines 234
Duplicated Lines 0 %

Test Coverage

Coverage 19%

Importance

Changes 28
Bugs 5 Features 1
Metric Value
eloc 103
c 28
b 5
f 1
dl 0
loc 234
ccs 19
cts 100
cp 0.19
rs 10
wmc 26

4 Methods

Rating   Name   Duplication   Size   Complexity  
A postUserDelete() 0 4 1
C postWrite() 0 79 13
A __construct() 0 17 1
B postDelete() 0 67 11
1
<?php
2
/**
3
 * @copyright Copyright (c) 2016, Roeland Jago Douma <[email protected]>
4
 * @copyright Copyright (c) 2017-2020 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\Node;
29
use OCP\ILogger;
30
use OCP\IUserManager;
31
32
use OCA\FaceRecognition\Service\FaceManagementService;
33
use OCA\FaceRecognition\Service\FileService;
34
use OCA\FaceRecognition\Service\SettingsService;
35
36
use OCA\FaceRecognition\Db\Face;
37
use OCA\FaceRecognition\Db\Image;
38
39
use OCA\FaceRecognition\Db\FaceMapper;
40
use OCA\FaceRecognition\Db\ImageMapper;
41
use OCA\FaceRecognition\Db\PersonMapper;
42
43
use OCA\FaceRecognition\Helper\Requirements;
44
45
class Watcher {
46
47
	/** @var ILogger Logger */
48
	private $logger;
49
50
	/** @var IUserManager */
51
	private $userManager;
52
53
	/** @var FaceMapper */
54
	private $faceMapper;
55
56
	/** @var ImageMapper */
57
	private $imageMapper;
58
59
	/** @var PersonMapper */
60
	private $personMapper;
61
62
	/** @var SettingsService */
63
	private $settingsService;
64
65
	/** @var FileService */
66
	private $fileService;
67
68
	/** @var FaceManagementService */
69
	private $faceManagementService;
70
71
	/**
72
	 * Watcher constructor.
73
	 *
74
	 * @param ILogger $logger
75
	 * @param IUserManager $userManager
76
	 * @param FaceMapper $faceMapper
77
	 * @param ImageMapper $imageMapper
78
	 * @param PersonMapper $personMapper
79
	 * @param SettingsService $settingsService
80
	 * @param FileService $fileService
81
	 * @param FaceManagementService $faceManagementService
82
	 */
83 1
	public function __construct(ILogger               $logger,
84
	                            IUserManager          $userManager,
85
	                            FaceMapper            $faceMapper,
86
	                            ImageMapper           $imageMapper,
87
	                            PersonMapper          $personMapper,
88
	                            SettingsService       $settingsService,
89
	                            FileService           $fileService,
90
	                            FaceManagementService $faceManagementService)
91
	{
92 1
		$this->logger                = $logger;
93 1
		$this->userManager           = $userManager;
94 1
		$this->faceMapper            = $faceMapper;
95 1
		$this->imageMapper           = $imageMapper;
96 1
		$this->personMapper          = $personMapper;
97 1
		$this->settingsService       = $settingsService;
98 1
		$this->fileService           = $fileService;
99 1
		$this->faceManagementService = $faceManagementService;
100 1
	}
101
102
	/**
103
	 * A node has been updated. We just store the file id
104
	 * with the current user in the DB
105
	 *
106
	 * @param Node $node
107
	 */
108 28
	public function postWrite(Node $node) {
109 28
		if (!$this->fileService->isAllowedNode($node)) {
110
			// Nextcloud sends the Hooks when create thumbnails for example.
111 28
			return;
112
		}
113
114 28
		if ($node instanceof Folder) {
115 28
			return;
116
		}
117
118
		$modelId = $this->settingsService->getCurrentFaceModel();
119
		if ($modelId === SettingsService::FALLBACK_CURRENT_MODEL) {
120
			$this->logger->debug("Skipping inserting file since there are no configured model");
121
			return;
122
		}
123
124
		$owner = \OC::$server->getUserSession()->getUser()->getUID();
125
		if (!$this->userManager->userExists($owner)) {
126
			$this->logger->debug(
127
				"Skipping inserting file " . $node->getName() . " because it seems that user  " . $owner . " doesn't exist");
128
			return;
129
		}
130
131
		$enabled = $this->settingsService->getUserEnabled($owner);
132
		if (!$enabled) {
133
			$this->logger->debug('The user ' . $owner . ' not have the analysis enabled. Skipping');
134
			return;
135
		}
136
137
		if ($node->getName() === FileService::NOMEDIA_FILE) {
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->settingsService->setNeedRemoveStaleImages(true, $owner);
141
			return;
142
		}
143
144
		if ($node->getName() === FileService::FACERECOGNITION_SETTINGS_FILE) {
145
			// This file can enable or disable the analysis, so I have to look for new files and forget others.
146
			$this->settingsService->setNeedRemoveStaleImages(true, $owner);
147
			$this->settingsService->setUserFullScanDone(false, $owner);
148
			return;
149
		}
150
151
		if (!Requirements::isImageTypeSupported($node->getMimeType())) {
152
			// The file is not an image or the model does not support it
153
			return;
154
		}
155
156
		if ($this->fileService->isUnderNoDetection($node)) {
157
			$this->logger->debug(
158
				"Skipping inserting image " . $node->getName() . " because is inside an folder that contains a .nomedia file");
159
			return;
160
		}
161
162
		$this->logger->debug("Inserting/updating image " . $node->getName() . " for face recognition");
163
164
		$image = new Image();
165
		$image->setUser($owner);
166
		$image->setFile($node->getId());
167
		$image->setModel($modelId);
168
169
		$imageId = $this->imageMapper->imageExists($image);
170
		if ($imageId === null) {
171
			// todo: can we have larger transaction with bulk insert?
172
			$this->imageMapper->insert($image);
173
		} else {
174
			$this->imageMapper->resetImage($image);
175
			// note that invalidatePersons depends on existence of faces for a given image,
176
			// and we must invalidate before we delete faces!
177
			$this->personMapper->invalidatePersons($imageId);
178
179
			// Fetch all faces to be deleted before deleting them, and then delete them
180
			$facesToRemove = $this->faceMapper->findByImage($imageId);
181
			$this->faceMapper->removeFromImage($imageId);
182
183
			// If any person is now without faces, remove those (empty) persons
184
			foreach ($facesToRemove as $faceToRemove) {
185
				if ($faceToRemove->getPerson() !== null) {
186
					$this->personMapper->removeIfEmpty($faceToRemove->getPerson());
187
				}
188
			}
189
		}
190
	}
191
192
	/**
193
	 * A node has been deleted. Remove faces with file id
194
	 * with the current user in the DB
195
	 *
196
	 * @param Node $node
197
	 */
198
	public function postDelete(Node $node) {
199
		if (!$this->fileService->isAllowedNode($node)) {
200
			// Nextcloud sends the Hooks when create thumbnails for example.
201
			return;
202
		}
203
204
		if ($node instanceof Folder) {
205
			return;
206
		}
207
208
		$modelId = $this->settingsService->getCurrentFaceModel();
209
		if ($modelId === SettingsService::FALLBACK_CURRENT_MODEL) {
210
			$this->logger->debug("Skipping deleting file since there are no configured model");
211
			return;
212
		}
213
214
		$owner = \OC::$server->getUserSession()->getUser()->getUID();
215
		$enabled = $this->settingsService->getUserEnabled($owner);
216
		if (!$enabled) {
217
			$this->logger->debug('The user ' . $owner . ' not have the analysis enabled. Skipping');
218
			return;
219
		}
220
221
		if ($node->getName() === FileService::NOMEDIA_FILE) {
222
			// If user deleted file named .nomedia, that means all images in this and all child directories should be added.
223
			// But, instead of doing that here, better option seem to be to just reset flag that image scan is not done.
224
			// This will trigger another round of image crawling in AddMissingImagesTask for this user and those images will be added.
225
			$this->settingsService->setNeedRemoveStaleImages(true, $owner);
226
			return;
227
		}
228
229
		if ($node->getName() === FileService::FACERECOGNITION_SETTINGS_FILE) {
230
			// This file can enable or disable the analysis, so I have to look for new files and forget others.
231
			$this->settingsService->setNeedRemoveStaleImages(true, $owner);
232
			$this->settingsService->setUserFullScanDone(false, $owner);
233
			return;
234
		}
235
236
		if (!Requirements::isImageTypeSupported($node->getMimeType())) {
237
			// The file is not an image or the model does not support it
238
			return;
239
		}
240
241
		$this->logger->debug("Deleting image " . $node->getName() . " from face recognition");
242
243
		$image = new Image();
244
		$image->setUser($owner);
245
		$image->setFile($node->getId());
246
		$image->setModel($modelId);
247
248
		$imageId = $this->imageMapper->imageExists($image);
249
		if ($imageId !== null) {
250
			// note that invalidatePersons depends on existence of faces for a given image,
251
			// and we must invalidate before we delete faces!
252
			$this->personMapper->invalidatePersons($imageId);
253
254
			// Fetch all faces to be deleted before deleting them, and then delete them
255
			$facesToRemove = $this->faceMapper->findByImage($imageId);
256
			$this->faceMapper->removeFromImage($imageId);
257
258
			$image->setId($imageId);
259
			$this->imageMapper->delete($image);
260
261
			// If any person is now without faces, remove those (empty) persons
262
			foreach ($facesToRemove as $faceToRemove) {
263
				if ($faceToRemove->getPerson() !== null) {
264
					$this->personMapper->removeIfEmpty($faceToRemove->getPerson());
265
				}
266
			}
267
		}
268
	}
269
270
	/**
271
	 * A user has been deleted. Cleanup everything from this user.
272
	 *
273
	 * @param \OC\User\User $user Deleted user
274
	 */
275 28
	public function postUserDelete(\OC\User\User $user) {
0 ignored issues
show
Bug introduced by
The type OC\User\User 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...
276 28
		$userId = $user->getUid();
277 28
		$this->faceManagementService->resetAllForUser($userId);
278 28
		$this->logger->info("Removed all face recognition data for deleted user " . $userId);
279 28
	}
280
}
281