Issues (125)

lib/BackgroundJob/BackgroundService.php (1 issue)

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\FaceRecognitionContext;
32
33
use OCA\FaceRecognition\BackgroundJob\Tasks\AddMissingImagesTask;
34
use OCA\FaceRecognition\BackgroundJob\Tasks\CheckCronTask;
35
use OCA\FaceRecognition\BackgroundJob\Tasks\CheckRequirementsTask;
36
use OCA\FaceRecognition\BackgroundJob\Tasks\CreateClustersTask;
37
use OCA\FaceRecognition\BackgroundJob\Tasks\DisabledUserRemovalTask;
38
use OCA\FaceRecognition\BackgroundJob\Tasks\EnumerateImagesMissingFacesTask;
39
use OCA\FaceRecognition\BackgroundJob\Tasks\ImageProcessingTask;
40
use OCA\FaceRecognition\BackgroundJob\Tasks\StaleImagesRemovalTask;
41
42
use Symfony\Component\Console\Output\OutputInterface;
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 $context */
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(OutputInterface $logger): void {
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
	 *
78
	 * @param int $timeout Maximum allowed time (in seconds) to execute
79
	 * @param bool $verbose Whether to be more verbose
80
	 * @param IUser|null $user ID of user to execute background operations for
81
	 * @param int|null $maxImageArea Max image area (in pixels^2) to be fed to neural network when doing face detection
82
	 * @param string $runMode The command execution mode
83
	 *
84
	 * @return void
85
	 */
86
	public function execute(int $timeout, bool $verbose, IUser $user = null, int $maxImageArea = null, string $runMode) {
87
		// Put to context all the stuff we are figuring only now
88
		//
89
		$this->context->user = $user;
90
		$this->context->verbose = $verbose;
91
		$this->context->setRunningThroughCommand();
92
		$this->context->propertyBag['max_image_area'] = $maxImageArea;
93
		$this->context->propertyBag['run_mode'] = $runMode;
94
95
		// Here we are defining all the tasks that will get executed.
96
		//
97
		$task_classes = [
98
			CheckRequirementsTask::class,
99
			CheckCronTask::class,
100
		];
101
102
		switch ($runMode)
103
		{
104
			case 'sync-mode':
105
				$task_classes[] = DisabledUserRemovalTask::class;
106
				$task_classes[] = StaleImagesRemovalTask::class;
107
				$task_classes[] = AddMissingImagesTask::class;
108
				break;
109
			case 'analyze-mode':
110
				$task_classes[] = EnumerateImagesMissingFacesTask::class;
111
				$task_classes[] = ImageProcessingTask::class;
112
				break;
113
			case 'cluster-mode':
114
				$task_classes[] = CreateClustersTask::class;
115
				break;
116
			case 'defer-mode':
117
				$task_classes[] = DisabledUserRemovalTask::class;
118
				$task_classes[] = StaleImagesRemovalTask::class;
119
				$task_classes[] = AddMissingImagesTask::class;
120
				$task_classes[] = EnumerateImagesMissingFacesTask::class;
121
				$task_classes[] = ImageProcessingTask::class;
122
				$task_classes[] = CreateClustersTask::class;
123
				break;
124
			case 'default-mode':
125
			default:
126
				$task_classes[] = DisabledUserRemovalTask::class;
127
				$task_classes[] = StaleImagesRemovalTask::class;
128
				$task_classes[] = CreateClustersTask::class;
129
				$task_classes[] = AddMissingImagesTask::class;
130
				$task_classes[] = EnumerateImagesMissingFacesTask::class;
131
				$task_classes[] = ImageProcessingTask::class;
132
				break;
133
		}
134
135
		// Main logic to iterate over all tasks and executes them.
136
		//
137
		$startTime = time();
138
		for ($i=0, $task_classes_count = count($task_classes); $i < $task_classes_count; $i++) {
139
			$task_class = $task_classes[$i];
140
			$task = $this->application->getContainer()->query($task_class);
0 ignored issues
show
Deprecated Code introduced by
The function OCP\IContainer::query() has been deprecated: 20.0.0 use \Psr\Container\ContainerInterface::get ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

140
			$task = /** @scrutinizer ignore-deprecated */ $this->application->getContainer()->query($task_class);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
141
			$this->context->logger->logInfo(sprintf("%d/%d - Executing task %s (%s)",
142
				$i+1, count($task_classes), (new \ReflectionClass($task_class))->getShortName(), $task->description()));
143
144
			try {
145
				$generator = $task->execute($this->context);
146
				// $generator can be either actual Generator or return value of boolean.
147
				// If it is Generator object, that means execute() had some yields.
148
				// Iterate through those yields and we will get end result through getReturn().
149
				if ($generator instanceof \Generator) {
150
					foreach ($generator as $_) {
151
						$currentTime = time();
152
						if (($timeout > 0) && ($currentTime - $startTime > $timeout)) {
153
							$this->context->logger->logInfo("Time out. Quitting...");
154
							return;
155
						}
156
157
						if ($this->context->verbose) {
158
							$this->context->logger->logDebug('yielding');
159
						}
160
					}
161
				}
162
163
				$shouldContinue = ($generator instanceof \Generator) ? $generator->getReturn() : $generator;
164
				if (!$shouldContinue) {
165
					$this->context->logger->logInfo(
166
						sprintf("Task %s signalled we should not continue, bailing out",
167
						(new \ReflectionClass($task_class))->getShortName()));
168
					return;
169
				}
170
			} catch (\Exception $e) {
171
				// Any exception is fatal, and we should quit background job
172
				//
173
				$this->context->logger->logInfo("Error during background task execution");
174
				$this->context->logger->logInfo("If error is not transient, this means that core component of face recognition is not working properly");
175
				$this->context->logger->logInfo("and that quantity and quality of detected faces and person will be low or suboptimal.");
176
				$this->context->logger->logInfo("You probably want to file an issue (please include exception below) to: https://github.com/matiasdelellis/facerecognition/issues");
177
				throw $e;
178
			}
179
		}
180
	}
181
}
182