PostWriteListener::__construct()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 15
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 7
c 1
b 0
f 0
nc 1
nop 7
dl 0
loc 15
ccs 0
cts 8
cp 0
crap 2
rs 10
1
<?php
2
declare(strict_types=1);
3
/**
4
 * @copyright Copyright (c) 2016, Roeland Jago Douma <[email protected]>
5
 * @copyright Copyright (c) 2017-2021 Matias De lellis <[email protected]>
6
 * @copyright Copyright (c) 2021 Ming Tsang <[email protected]>
7
 *
8
 * @author Roeland Jago Douma <[email protected]>
9
 * @author Matias De lellis <[email protected]>
10
 * @author Ming Tsang <[email protected]>
11
 *
12
 * @license GNU AGPL version 3 or any later version
13
 *
14
 * This program is free software: you can redistribute it and/or modify
15
 * it under the terms of the GNU Affero General Public License as
16
 * published by the Free Software Foundation, either version 3 of the
17
 * License, or (at your option) any later version.
18
 *
19
 * This program is distributed in the hope that it will be useful,
20
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22
 * GNU Affero General Public License for more details.
23
 *
24
 * You should have received a copy of the GNU Affero General Public License
25
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
26
 *
27
 */
28
29
namespace OCA\FaceRecognition\Listener;
30
31
use OCP\EventDispatcher\Event;
32
use OCP\EventDispatcher\IEventListener;
33
34
use OCP\Files\Folder;
35
use OCP\Files\Events\Node\NodeWrittenEvent;
36
37
use OCP\IUserManager;
38
39
use OCA\FaceRecognition\Service\FileService;
40
use OCA\FaceRecognition\Service\SettingsService;
41
use OCA\FaceRecognition\Db\FaceMapper;
42
use OCA\FaceRecognition\Db\Image;
43
use OCA\FaceRecognition\Db\ImageMapper;
44
use OCA\FaceRecognition\Db\PersonMapper;
45
46
use Psr\Log\LoggerInterface;
47
48
class PostWriteListener implements IEventListener {
49
50
	/** @var LoggerInterface $logger */
51
	private $logger;
52
53
	/** @var IUserManager */
54
	private $userManager;
55
56
	/** @var FaceMapper */
57
	private $faceMapper;
58
59
	/** @var ImageMapper */
60
	private $imageMapper;
61
62
	/** @var PersonMapper */
63
	private $personMapper;
64
65
	/** @var SettingsService */
66
	private $settingsService;
67
68
	/** @var FileService */
69
	private $fileService;
70
71
	public function __construct(LoggerInterface       $logger,
72
	                            IUserManager          $userManager,
73
	                            FaceMapper            $faceMapper,
74
	                            ImageMapper           $imageMapper,
75
	                            PersonMapper          $personMapper,
76
	                            SettingsService       $settingsService,
77
	                            FileService           $fileService)
78
	{
79
		$this->logger                = $logger;
80
		$this->userManager           = $userManager;
81
		$this->faceMapper            = $faceMapper;
82
		$this->imageMapper           = $imageMapper;
83
		$this->personMapper          = $personMapper;
84
		$this->settingsService       = $settingsService;
85
		$this->fileService           = $fileService;
86
	}
87
88
89
	/**
90
	 * A node has been updated. We just store the file id
91
	 * with the current user in the DB
92
	 */
93
	public function handle(Event $event): void {
94
		if (!($event instanceof NodeWrittenEvent)) {
95
			return;
96
		}
97
98
		$node = $event->getNode();
99
		if (!$this->fileService->isAllowedNode($node)) {
100
			// Nextcloud sends the Hooks when create thumbnails for example.
101
			return;
102
		}
103
104
		if ($node instanceof Folder) {
105
			return;
106
		}
107
108
		$modelId = $this->settingsService->getCurrentFaceModel();
109
		if ($modelId === SettingsService::FALLBACK_CURRENT_MODEL) {
110
			$this->logger->debug("Skipping inserting file since there are no configured model");
111
			return;
112
		}
113
114
		$owner = null;
115
		if ($this->fileService->isUserFile($node)) {
116
			$owner = $node->getOwner()->getUid();
117
		} else {
118
			if (!\OC::$server->getUserSession()->isLoggedIn()) {
119
				$this->logger->debug('Skipping interting file ' . $node->getName() . ' since we cannot determine the owner.');
120
				return;
121
			}
122
			$owner = \OC::$server->getUserSession()->getUser()->getUID();
123
		}
124
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
		    $node->getName() === FileService::NOIMAGE_FILE) {
139
			// If user added this file, it means all images in this and all child directories should be removed.
140
			// Instead of doing that here, it's better to just add flag that image removal should be done.
141
			$this->settingsService->setNeedRemoveStaleImages(true, $owner);
142
			return;
143
		}
144
145
		if ($node->getName() === FileService::FACERECOGNITION_SETTINGS_FILE) {
146
			// This file can enable or disable the analysis, so I have to look for new files and forget others.
147
			$this->settingsService->setNeedRemoveStaleImages(true, $owner);
148
			$this->settingsService->setUserFullScanDone(false, $owner);
149
			return;
150
		}
151
152
		if (!$this->settingsService->isAllowedMimetype($node->getMimeType())) {
153
			// The file is not an image or the model does not support it
154
			return;
155
		}
156
157
		if ($this->fileService->isUnderNoDetection($node)) {
158
			$this->logger->debug(
159
				"Skipping inserting image " . $node->getName() . " because is inside an folder that contains a .nomedia file");
160
			return;
161
		}
162
163
		$this->logger->debug("Inserting/updating image " . $node->getName() . " for face recognition");
164
165
		$image = new Image();
166
		$image->setUser($owner);
167
		$image->setFile($node->getId());
168
		$image->setModel($modelId);
169
170
		$imageId = $this->imageMapper->imageExists($image);
171
		if ($imageId === null) {
172
			// todo: can we have larger transaction with bulk insert?
173
			$this->imageMapper->insert($image);
174
		} else {
175
			$this->imageMapper->resetImage($image);
176
			// note that invalidatePersons depends on existence of faces for a given image,
177
			// and we must invalidate before we delete faces!
178
			$this->personMapper->invalidatePersons($imageId);
179
180
			// Fetch all faces to be deleted before deleting them, and then delete them
181
			$facesToRemove = $this->faceMapper->findByImage($imageId);
182
			$this->faceMapper->removeFromImage($imageId);
183
184
			// If any person is now without faces, remove those (empty) persons
185
			foreach ($facesToRemove as $faceToRemove) {
186
				if ($faceToRemove->getPerson() !== null) {
187
					$this->personMapper->removeIfEmpty($faceToRemove->getPerson());
188
				}
189
			}
190
		}
191
	}
192
193
}
194