Passed
Push — pr-374 ( 1bdcc1 )
by Matias
27:20
created

BackgroundService::execute()   C

Complexity

Conditions 11
Paths 64

Size

Total Lines 81
Code Lines 56

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 132

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 11
eloc 56
c 1
b 0
f 0
nc 64
nop 5
dl 0
loc 81
ccs 0
cts 65
cp 0
crap 132
rs 6.8133

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
/**
3
 * @copyright Copyright (c) 2017, 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;
25
26
use OCP\IUser;
27
28
use OCA\FaceRecognition\AppInfo\Application;
29
use OCA\FaceRecognition\Helper\Requirements;
30
31
use OCA\FaceRecognition\BackgroundJob\Tasks\AddMissingImagesTask;
32
use OCA\FaceRecognition\BackgroundJob\Tasks\CheckCronTask;
33
use OCA\FaceRecognition\BackgroundJob\Tasks\CheckRequirementsTask;
34
use OCA\FaceRecognition\BackgroundJob\Tasks\CreateClustersTask;
35
use OCA\FaceRecognition\BackgroundJob\Tasks\DisabledUserRemovalTask;
36
use OCA\FaceRecognition\BackgroundJob\Tasks\EnumerateImagesMissingFacesTask;
37
use OCA\FaceRecognition\BackgroundJob\Tasks\ImageProcessingTask;
38
use OCA\FaceRecognition\BackgroundJob\Tasks\LockTask;
39
use OCA\FaceRecognition\BackgroundJob\Tasks\StaleImagesRemovalTask;
40
use OCA\FaceRecognition\BackgroundJob\Tasks\UnlockTask;
41
42
use Symfony\Component\Console\Output\OutputInterface;
0 ignored issues
show
Bug introduced by
The type Symfony\Component\Console\Output\OutputInterface 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...
43
44
/**
45
 * Background service. Both command and cron job are calling this service for long-running background operations.
46
 * Background processing for face recognition is comprised of several steps, called tasks. Each task is independent,
47
 * idempotent, DI-aware logic unit that yields. Since tasks are non-preemptive, they should yield from time to time,
48
 * so we son't end up working for more than given timeout.
49
 *
50
 * Tasks can be seen as normal sequential functions, but they are easier to work with,
51
 * reason about them and test them independently. Other than that, they are really glorified functions.
52
 */
53
class BackgroundService {
54
55
	/** @var Application $application */
56
	private $application;
57
58
	/** @var FaceRecognitionContext */
59
	private $context;
60
61
	public function __construct(Application $application, FaceRecognitionContext $context) {
62
		$this->application = $application;
63
		$this->context = $context;
64
	}
65
66
	public function setLogger($logger) {
67
		if (!is_null($this->context->logger)) {
68
			// If you get this exception, it means you already initialized context->logger. Double-check your flow.
69
			throw new \LogicException('You cannot call setLogger after you set it once');
70
		}
71
72
		$this->context->logger = new FaceRecognitionLogger($logger);
73
	}
74
75
	/**
76
	 * Starts background tasks sequentially.
77
	 * @param int $timeout Maximum allowed time (in seconds) to execute
78
	 * @param bool $verbose Whether to be more verbose
79
	 * @param IUser|null $user ID of user to execute background operations for
80
	 * @param int|null $maxImageArea Max image area (in pixels^2) to be fed to neural network when doing face detection
81
	 * @param bool $deferClustering defer the grouping at the end of the analysis.
82
	 */
83
	public function execute(int $timeout, bool $verbose, IUser $user = null, int $maxImageArea = null, bool $deferClustering) {
84
		// Put to context all the stuff we are figuring only now
85
		//
86
		$this->context->user = $user;
87
		$this->context->verbose = $verbose;
88
		$this->context->setRunningThroughCommand();
89
		$this->context->propertyBag['max_image_area'] = $maxImageArea;
90
91
		// Here we are defining all the tasks that will get executed.
92
		//
93
		if ($deferClustering === true) {
94
			$task_classes = [
95
				CheckRequirementsTask::class,
96
				CheckCronTask::class,
97
				LockTask::class,
98
				DisabledUserRemovalTask::class,
99
				StaleImagesRemovalTask::class,
100
				AddMissingImagesTask::class,
101
				EnumerateImagesMissingFacesTask::class,
102
				ImageProcessingTask::class,
103
				CreateClustersTask::class,
104
				UnlockTask::class
105
			];
106
		} else {
107
			$task_classes = [
108
				CheckRequirementsTask::class,
109
				CheckCronTask::class,
110
				LockTask::class,
111
				DisabledUserRemovalTask::class,
112
				StaleImagesRemovalTask::class,
113
				CreateClustersTask::class,
114
				AddMissingImagesTask::class,
115
				EnumerateImagesMissingFacesTask::class,
116
				ImageProcessingTask::class,
117
				UnlockTask::class
118
			];
119
		};
120
121
		// Main logic to iterate over all tasks and executes them.
122
		//
123
		$startTime = time();
124
		for ($i=0, $task_classes_count = count($task_classes); $i < $task_classes_count; $i++) {
125
			$task_class = $task_classes[$i];
126
			$task = $this->application->getContainer()->query($task_class);
127
			$this->context->logger->logInfo(sprintf("%d/%d - Executing task %s (%s)",
128
				$i+1, count($task_classes), (new \ReflectionClass($task_class))->getShortName(), $task->description()));
129
130
			try {
131
				$generator = $task->execute($this->context);
132
				// $generator can be either actual Generator or return value of boolean.
133
				// If it is Generator object, that means execute() had some yields.
134
				// Iterate through those yields and we will get end result through getReturn().
135
				if ($generator instanceof \Generator) {
136
					foreach ($generator as $_) {
137
						$currentTime = time();
138
						if (($timeout > 0) && ($currentTime - $startTime > $timeout)) {
139
							$this->context->logger->logInfo("Time out. Quitting...");
140
							return;
141
						}
142
143
						if ($this->context->verbose) {
144
							$this->context->logger->logDebug('yielding');
145
						}
146
					}
147
				}
148
149
				$shouldContinue = ($generator instanceof \Generator) ? $generator->getReturn() : $generator;
150
				if (!$shouldContinue) {
151
					$this->context->logger->logInfo(
152
						sprintf("Task %s signalled we should not continue, bailing out",
153
						(new \ReflectionClass($task_class))->getShortName()));
154
					return;
155
				}
156
			} catch (\Exception $e) {
157
				// Any exception is fatal, and we should quit background job
158
				//
159
				$this->context->logger->logInfo("Error during background task execution");
160
				$this->context->logger->logInfo("If error is not transient, this means that core component of face recognition is not working properly");
161
				$this->context->logger->logInfo("and that quantity and quality of detected faces and person will be low or suboptimal.");
162
				$this->context->logger->logInfo("You probably want to file an issue (please include exception below) to: https://github.com/matiasdelellis/facerecognition/issues");
163
				throw $e;
164
			}
165
		}
166
	}
167
}
168