Passed
Push — master ( 1bbfec...6e5dc9 )
by Branko
04:57 queued 01:04
created

Watcher   A

Complexity

Total Complexity 17

Size/Duplication

Total Lines 168
Duplicated Lines 0 %

Test Coverage

Coverage 22.39%

Importance

Changes 0
Metric Value
eloc 71
dl 0
loc 168
ccs 15
cts 67
cp 0.2239
rs 10
c 0
b 0
f 0
wmc 17

3 Methods

Rating   Name   Duplication   Size   Complexity  
B postWrite() 0 60 10
A __construct() 0 15 1
B postDelete() 0 42 6
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\IDBConnection;
32
use OCP\ILogger;
33
use OCP\IUserManager;
34
35
use OCA\FaceRecognition\BackgroundJob\Tasks\AddMissingImagesTask;
36
use OCA\FaceRecognition\BackgroundJob\Tasks\StaleImagesRemovalTask;
37
use OCA\FaceRecognition\Db\Face;
38
use OCA\FaceRecognition\Db\Image;
39
use OCA\FaceRecognition\Db\FaceMapper;
40
use OCA\FaceRecognition\Db\ImageMapper;
41
use OCA\FaceRecognition\Db\PersonMapper;
42
use OCA\FaceRecognition\Helper\Requirements;
43
use OCA\FaceRecognition\Migration\AddDefaultFaceModel;
44
45
class Watcher {
46
47
	/** @var IConfig Config */
48
	private $config;
49
50
	/** @var ILogger Logger */
51
	private $logger;
52
53
	/** @var IDBConnection */
54
	private $connection;
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
	/**
69
	 * Watcher constructor.
70
	 *
71
	 * @param IConfig $config
72
	 * @param ILogger $logger
73
	 * @param IDBConnection $connection
74
	 * @param IUserManager $userManager
75
	 * @param FaceMapper $faceMapper
76
	 * @param ImageMapper $imageMapper
77
	 * @param PersonMapper $personMapper
78
	 */
79 8
	public function __construct(IConfig       $config,
80
	                            ILogger       $logger,
81
	                            IDBConnection $connection,
82
	                            IUserManager  $userManager,
83
	                            FaceMapper    $faceMapper,
84
	                            ImageMapper   $imageMapper,
85
	                            PersonMapper  $personMapper)
86
	{
87 8
		$this->config = $config;
88 8
		$this->logger = $logger;
89 8
		$this->connection = $connection;
90 8
		$this->userManager = $userManager;
91 8
		$this->faceMapper = $faceMapper;
92 8
		$this->imageMapper = $imageMapper;
93 8
		$this->personMapper = $personMapper;
94 8
	}
95
96
	/**
97
	 * A node has been updated. We just store the file id
98
	 * with the current user in the DB
99
	 *
100
	 * @param Node $node
101
	 */
102 8
	public function postWrite(Node $node) {
103 8
		$model = intval($this->config->getAppValue('facerecognition', 'model', AddDefaultFaceModel::DEFAULT_FACE_MODEL_ID));
104
105
		// todo: should we also care about this too: instanceOfStorage(ISharedStorage::class);
106 8
		if ($node->getStorage()->instanceOfStorage(IHomeStorage::class) === false) {
107 8
			return;
108
		}
109
110 8
		if ($node instanceof Folder) {
111 8
			return;
112
		}
113
114
		$owner = $node->getOwner()->getUid();
115
116
		if ($node->getName() === '.nomedia') {
117
			// If user added this file, it means all images in this and all child directories should be removed.
118
			// Instead of doing that here, it's better to just add flag that image removal should be done.
119
			$this->config->setUserValue($owner, 'facerecognition', StaleImagesRemovalTask::STALE_IMAGES_REMOVAL_NEEDED_KEY, 'true');
120
			return;
121
		}
122
123
		if (!Requirements::isImageTypeSupported($node->getMimeType())) {
124
			return;
125
		}
126
127
		if (!$this->userManager->userExists($owner)) {
128
			$this->logger->debug(
129
				"Skipping inserting image " . $node->getName() . " because it seems that user  " . $owner . " doesn't exist");
130
			return;
131
		}
132
133
		// If we detect .nomedia file anywhere on the path to root folder (id===null), bail out
134
		$parentNode = $node->getParent();
135
		while (($parentNode instanceof Folder) && ($parentNode->getId() !== null)) {
136
			if ($parentNode->nodeExists('.nomedia')) {
137
				$this->logger->debug(
138
					"Skipping inserting image " . $node->getName() . " because directory " . $parentNode->getName() . " contains .nomedia file");
139
				return;
140
			}
141
142
			$parentNode = $parentNode->getParent();
143
		}
144
145
		$this->logger->debug("Inserting/updating image " . $node->getName() . " for face recognition");
146
147
		$image = new Image();
148
		$image->setUser($owner);
149
		$image->setFile($node->getId());
150
		$image->setModel($model);
151
152
		$imageId = $this->imageMapper->imageExists($image);
153
		if ($imageId === null) {
154
			// todo: can we have larger transaction with bulk insert?
155
			$this->imageMapper->insert($image);
156
		} else {
157
			$this->imageMapper->resetImage($image);
158
			// note that invalidatePersons depends on existence of faces for a given image,
159
			// and we must invalidate before we delete faces!
160
			$this->personMapper->invalidatePersons($imageId);
161
			$this->faceMapper->removeFaces($imageId);
162
		}
163
	}
164
165
	/**
166
	 * A node has been delete. Remove faces with file id
167
	 * with the current user in the DB
168
	 *
169
	 * @param Node $node
170
	 */
171
	public function postDelete(Node $node) {
172
		$model = intval($this->config->getAppValue('facerecognition', 'model', AddDefaultFaceModel::DEFAULT_FACE_MODEL_ID));
173
174
		// todo: should we also care about this too: instanceOfStorage(ISharedStorage::class);
175
		if ($node->getStorage()->instanceOfStorage(IHomeStorage::class) === false) {
176
			return;
177
		}
178
179
		if ($node instanceof Folder) {
180
			return;
181
		}
182
183
		$owner = $node->getOwner()->getUid();
184
185
		if ($node->getName() === '.nomedia') {
186
			// If user deleted file named .nomedia, that means all images in this and all child directories should be added.
187
			// But, instead of doing that here, better option seem to be to just reset flag that image scan is not done.
188
			// This will trigger another round of image crawling in AddMissingImagesTask for this user and those images will be added.
189
			$this->config->setUserValue($owner, 'facerecognition', AddMissingImagesTask::FULL_IMAGE_SCAN_DONE_KEY, 'false');
190
			return;
191
		}
192
193
		if (!Requirements::isImageTypeSupported($node->getMimeType())) {
194
			return;
195
		}
196
197
		$this->logger->debug("Deleting image " . $node->getName() . " from face recognition");
198
199
		$image = new Image();
200
		$image->setUser($owner);
201
		$image->setFile($node->getId());
202
		$image->setModel($model);
203
204
		$imageId = $this->imageMapper->imageExists($image);
205
		if ($imageId !== null) {
206
			// note that invalidatePersons depends on existence of faces for a given image,
207
			// and we must invalidate before we delete faces!
208
			$this->personMapper->invalidatePersons($imageId);
209
			$this->faceMapper->removeFaces($imageId);
210
211
			$image->setId($imageId);
212
			$this->imageMapper->delete($image);
213
		}
214
	}
215
}
216