Completed
Push — master ( f54c15...91bfdb )
by Matias
13s queued 11s
created

staleImagesRemovalForUser()   B

Complexity

Conditions 6
Paths 5

Size

Total Lines 59
Code Lines 31

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 30
CRAP Score 6.0087

Importance

Changes 2
Bugs 1 Features 0
Metric Value
cc 6
eloc 31
c 2
b 1
f 0
nc 5
nop 2
dl 0
loc 59
ccs 30
cts 32
cp 0.9375
crap 6.0087
rs 8.8017

How to fix   Long Method   

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
/**
3
 * @copyright Copyright (c) 2017-2020 Matias De lellis <[email protected]>
4
 * @copyright Copyright (c) 2018, Branko Kokanovic <[email protected]>
5
 *
6
 * @author Branko Kokanovic <[email protected]>
7
 *
8
 * @license GNU AGPL version 3 or any later version
9
 *
10
 * This program is free software: you can redistribute it and/or modify
11
 * it under the terms of the GNU Affero General Public License as
12
 * published by the Free Software Foundation, either version 3 of the
13
 * License, or (at your option) any later version.
14
 *
15
 * This program is distributed in the hope that it will be useful,
16
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18
 * GNU Affero General Public License for more details.
19
 *
20
 * You should have received a copy of the GNU Affero General Public License
21
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
22
 *
23
 */
24
namespace OCA\FaceRecognition\BackgroundJob\Tasks;
25
26
use OCP\IUser;
27
28
use OCP\Files\File;
29
use OCP\Files\Folder;
30
use OCP\Files\Node;
31
32
use OCA\FaceRecognition\BackgroundJob\FaceRecognitionBackgroundTask;
33
use OCA\FaceRecognition\BackgroundJob\FaceRecognitionContext;
34
35
use OCA\FaceRecognition\Db\Image;
36
use OCA\FaceRecognition\Db\ImageMapper;
37
use OCA\FaceRecognition\Db\FaceMapper;
38
use OCA\FaceRecognition\Db\PersonMapper;
39
40
use OCA\FaceRecognition\Service\FileService;
41
use OCA\FaceRecognition\Service\SettingsService;
42
43
/**
44
 * Task that, for each user, crawls for all images in database,
45
 * checks if they actually exist and removes them if they don't.
46
 * It should be executed rarely.
47
 */
48
class StaleImagesRemovalTask extends FaceRecognitionBackgroundTask {
49
50
	/** @var ImageMapper Image mapper */
51
	private $imageMapper;
52
53
	/** @var FaceMapper Face mapper */
54
	private $faceMapper;
55
56
	/** @var PersonMapper Person mapper */
57
	private $personMapper;
58
59
	/** @var FileService  File service*/
60
	private $fileService;
61
62
	/** @var SettingsService */
63
	private $settingsService;
64
65
	/**
66
	 * @param ImageMapper $imageMapper Image mapper
67
	 * @param FaceMapper $faceMapper Face mapper
68
	 * @param PersonMapper $personMapper Person mapper
69
	 * @param FileService $fileService File Service
70
	 * @param SettingsService $settingsService Settings Service
71
	 */
72 3
	public function __construct(ImageMapper     $imageMapper,
73
	                            FaceMapper      $faceMapper,
74
	                            PersonMapper    $personMapper,
75
	                            FileService     $fileService,
76
	                            SettingsService $settingsService)
77
	{
78 3
		parent::__construct();
79
80 3
		$this->imageMapper     = $imageMapper;
81 3
		$this->faceMapper      = $faceMapper;
82 3
		$this->personMapper    = $personMapper;
83 3
		$this->fileService     = $fileService;
84 3
		$this->settingsService = $settingsService;
85 3
	}
86
87
	/**
88
	 * @inheritdoc
89
	 */
90 2
	public function description() {
91 2
		return "Crawl for stale images (either missing in filesystem or under .nomedia) and remove them from DB";
92
	}
93
94
	/**
95
	 * @inheritdoc
96
	 */
97 3
	public function execute(FaceRecognitionContext $context) {
98 3
		$this->setContext($context);
99
100
		// Check if we are called for one user only, or for all user in instance.
101 3
		$staleRemovedImages = 0;
102 3
		$eligable_users = array();
103 3
		if (is_null($this->context->user)) {
104
			$this->context->userManager->callForSeenUsers(function (IUser $user) use (&$eligable_users) {
105 3
				$eligable_users[] = $user->getUID();
106 3
			});
107
		} else {
108
			$eligable_users[] = $this->context->user->getUID();
109
		}
110
111 3
		foreach($eligable_users as $user) {
112 3
			if (!$this->settingsService->getNeedRemoveStaleImages($user)) {
113
				// Completely skip this task for this user, seems that we already did full scan for him
114 3
				$this->logDebug(sprintf('Skipping stale images removal for user %s as there is no need for it', $user));
115 3
				continue;
116
			}
117
118
			// Since method below can take long time, it is generator itself
119 2
			$generator = $this->staleImagesRemovalForUser($user, $this->settingsService->getCurrentFaceModel());
120 2
			foreach ($generator as $_) {
121 2
				yield;
122
			}
123 2
			$staleRemovedImages += $generator->getReturn();
124
125 2
			$this->settingsService->setNeedRemoveStaleImages(false, $user);
126
127 2
			yield;
128
		}
129
130 3
		$this->context->propertyBag['StaleImagesRemovalTask_staleRemovedImages'] = $staleRemovedImages;
131 3
		return true;
132
	}
133
134
	/**
135
	 * Gets all images in database for a given user. For each image, check if it
136
	 * actually present in filesystem (and there is no .nomedia for it) and removes
137
	 * it from database if it is not present.
138
	 *
139
	 * @param string $userId ID of the user for which to remove stale images for
140
	 * @param int $model Used model
141
	 * @return \Generator|int Returns generator during yielding and finally returns int,
142
	 * which represent number of stale images removed
143
	 */
144 2
	private function staleImagesRemovalForUser(string $userId, int $model) {
145
146 2
		$this->fileService->setupFS($userId);
147
148 2
		$this->logDebug(sprintf('Getting all images for user %s', $userId));
149 2
		$allImages = $this->imageMapper->findImages($userId, $model);
150 2
		$this->logDebug(sprintf('Found %d images for user %s', count($allImages), $userId));
151 2
		yield;
152
153
		// Find if we stopped somewhere abruptly before. If we are, we need to start from that point.
154
		// If there is value, we start from beggining. Important is that:
155
		// * There needs to be some (any!) ordering here, we used "id" for ordering key
156
		// * New images will be processed, or some might be checked more than once, and that is OK
157
		//   Important part is that we make continuous progess.
158
159 2
		$lastChecked = $this->settingsService->getLastStaleImageChecked($userId);
160 2
		$this->logDebug(sprintf('Last checked image id for user %s is %d', $userId, $lastChecked));
161 2
		yield;
162
163
		// Now filter by those above last checked and sort remaining images
164
		$allImages = array_filter($allImages, function ($i) use($lastChecked) {
165 2
			return $i->id > $lastChecked;
166 2
		});
167
		usort($allImages, function ($i1, $i2) {
168 1
			return $i1->id <=> $i2->id;
169 2
		});
170 2
		$this->logDebug(sprintf(
171 2
			'After filtering and sorting, there is %d remaining stale images to check for user %s',
172 2
			count($allImages), $userId));
173 2
		yield;
174
175
		// Now iterate and check remaining images
176 2
		$processed = 0;
177 2
		$imagesRemoved = 0;
178 2
		foreach ($allImages as $image) {
179 2
			$file = $this->fileService->getFileById($image->getFile(), $userId);
180
181
			// Delete image doesn't exist anymore in filesystem or it is under .nomedia
182 2
			if (($file === null) || (!$this->fileService->isAllowedNode($file)) ||
183 2
			    ($this->fileService->isUnderNoDetection($file))) {
184 2
				$this->deleteImage($image, $userId);
185 2
				$imagesRemoved++;
186
			}
187
188
			// Remember last processed image
189 2
			$this->settingsService->setLastStaleImageChecked($image->id, $userId);
190
191
			// Yield from time to time
192 2
			$processed++;
193 2
			if ($processed % 10 === 0) {
194
				$this->logDebug(sprintf('Processed %d/%d stale images for user %s', $processed, count($allImages), $userId));
195
				yield;
196
			}
197
		}
198
199
		// Remove this value when we are done, so next cleanup can start from 0
200 2
		$this->settingsService->setLastStaleImageChecked(0, $userId);
201
202 2
		return $imagesRemoved;
203
	}
204
205 2
	private function deleteImage(Image $image, string $userId) {
206 2
		$this->logInfo(sprintf('Removing stale image %d for user %s', $image->id, $userId));
207
		// note that invalidatePersons depends on existence of faces for a given image,
208
		// and we must invalidate before we delete faces!
209
		// TODO: this is same method as in Watcher, find where to unify them.
210 2
		$this->personMapper->invalidatePersons($image->id);
211 2
		$this->faceMapper->removeFromImage($image->id);
212 2
		$this->imageMapper->delete($image);
213 2
	}
214
}
215