PostDeleteListener::handle()   C
last analyzed

Complexity

Conditions 15
Paths 21

Size

Total Lines 83
Code Lines 48

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 240

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 15
eloc 48
c 1
b 0
f 0
nc 21
nop 1
dl 0
loc 83
ccs 0
cts 48
cp 0
crap 240
rs 5.9166

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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\NodeDeletedEvent;
36
37
use OCA\FaceRecognition\Service\FileService;
38
use OCA\FaceRecognition\Service\SettingsService;
39
use OCA\FaceRecognition\Db\FaceMapper;
40
use OCA\FaceRecognition\Db\Image;
41
use OCA\FaceRecognition\Db\ImageMapper;
42
use OCA\FaceRecognition\Db\PersonMapper;
43
44
use Psr\Log\LoggerInterface;
45
46
class PostDeleteListener implements IEventListener {
47
48
	/** @var LoggerInterface $logger */
49
	private $logger;
50
51
	/** @var FaceMapper */
52
	private $faceMapper;
53
54
	/** @var ImageMapper */
55
	private $imageMapper;
56
57
	/** @var PersonMapper */
58
	private $personMapper;
59
60
	/** @var SettingsService */
61
	private $settingsService;
62
63
	/** @var FileService */
64
	private $fileService;
65
66
	public function __construct(LoggerInterface       $logger,
67
	                            FaceMapper            $faceMapper,
68
	                            ImageMapper           $imageMapper,
69
	                            PersonMapper          $personMapper,
70
	                            SettingsService       $settingsService,
71
	                            FileService           $fileService)
72
	{
73
		$this->logger                = $logger;
74
		$this->faceMapper            = $faceMapper;
75
		$this->imageMapper           = $imageMapper;
76
		$this->personMapper          = $personMapper;
77
		$this->settingsService       = $settingsService;
78
		$this->fileService           = $fileService;
79
	}
80
81
82
	/**
83
	 * A node has been deleted. Remove faces with file id
84
	 * with the current user in the DB
85
	 */
86
	public function handle(Event $event): void {
87
		if (!($event instanceof NodeDeletedEvent)) {
88
			return;
89
		}
90
91
		$node = $event->getNode();
92
		if (!$this->fileService->isAllowedNode($node)) {
93
			// Nextcloud sends the Hooks when create thumbnails for example.
94
			return;
95
		}
96
97
		if ($node instanceof Folder) {
98
			return;
99
		}
100
101
		$modelId = $this->settingsService->getCurrentFaceModel();
102
		if ($modelId === SettingsService::FALLBACK_CURRENT_MODEL) {
103
			$this->logger->debug("Skipping deleting file since there are no configured model");
104
			return;
105
		}
106
107
		$owner = null;
108
		if ($this->fileService->isUserFile($node)) {
109
			$owner = $node->getOwner()->getUid();
110
		} else {
111
			if (!\OC::$server->getUserSession()->isLoggedIn()) {
112
				$this->logger->debug('Skipping deleting the file ' . $node->getName() .  ' since we cannot determine the owner');
113
				return;
114
			}
115
			$owner = \OC::$server->getUserSession()->getUser()->getUID();
116
		}
117
118
		$enabled = $this->settingsService->getUserEnabled($owner);
119
		if (!$enabled) {
120
			$this->logger->debug('The user ' . $owner . ' not have the analysis enabled. Skipping');
121
			return;
122
		}
123
124
		if ($node->getName() === FileService::NOMEDIA_FILE ||
125
		    $node->getName() === FileService::NOIMAGE_FILE) {
126
			// If user deleted file named .nomedia, that means all images in this and all child directories should be added.
127
			// But, instead of doing that here, better option seem to be to just reset flag that image scan is not done.
128
			// This will trigger another round of image crawling in AddMissingImagesTask for this user and those images will be added.
129
			$this->settingsService->setUserFullScanDone(false, $owner);
130
			return;
131
		}
132
133
		if ($node->getName() === FileService::FACERECOGNITION_SETTINGS_FILE) {
134
			// This file can enable or disable the analysis, so I have to look for new files and forget others.
135
			$this->settingsService->setNeedRemoveStaleImages(true, $owner);
136
			$this->settingsService->setUserFullScanDone(false, $owner);
137
			return;
138
		}
139
140
		if (!$this->settingsService->isAllowedMimetype($node->getMimeType())) {
141
			// The file is not an image or the model does not support it
142
			return;
143
		}
144
145
		$this->logger->debug("Deleting image " . $node->getName() . " from face recognition");
146
147
		$image = new Image();
148
		$image->setUser($owner);
149
		$image->setFile($node->getId());
150
		$image->setModel($modelId);
151
152
		$imageId = $this->imageMapper->imageExists($image);
153
		if ($imageId !== null) {
154
			// note that invalidatePersons depends on existence of faces for a given image,
155
			// and we must invalidate before we delete faces!
156
			$this->personMapper->invalidatePersons($imageId);
157
158
			// Fetch all faces to be deleted before deleting them, and then delete them
159
			$facesToRemove = $this->faceMapper->findByImage($imageId);
160
			$this->faceMapper->removeFromImage($imageId);
161
162
			$image->setId($imageId);
163
			$this->imageMapper->delete($image);
164
165
			// If any person is now without faces, remove those (empty) persons
166
			foreach ($facesToRemove as $faceToRemove) {
167
				if ($faceToRemove->getPerson() !== null) {
168
					$this->personMapper->removeIfEmpty($faceToRemove->getPerson());
169
				}
170
			}
171
		}
172
	}
173
174
}
175