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
|
|||
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 |
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.