Passed
Push — master ( a64fe0...7c9e0f )
by Matias
04:41
created

ExternalModel   A

Complexity

Total Complexity 29

Size/Duplication

Total Lines 198
Duplicated Lines 0 %

Test Coverage

Coverage 2.02%

Importance

Changes 11
Bugs 3 Features 0
Metric Value
eloc 96
c 11
b 3
f 0
dl 0
loc 198
ccs 2
cts 99
cp 0.0202
rs 10
wmc 29

13 Methods

Rating   Name   Duplication   Size   Complexity  
A isInstalled() 0 4 2
A __construct() 0 3 1
A getId() 0 2 1
A compute() 0 33 4
A meetDependencies() 0 10 3
A getDescription() 0 2 1
B detectFaces() 0 36 6
A getMaximumArea() 0 5 2
A getName() 0 2 1
A open() 0 29 4
A getPreferredMimeType() 0 5 2
A install() 0 3 1
A getDocumentation() 0 2 1
1
<?php
2
/**
3
 * @copyright Copyright (c) 2021, Matias De lellis <[email protected]>
4
 *
5
 * @author Matias De lellis <[email protected]>
6
 *
7
 * @license GNU AGPL version 3 or any later version
8
 *
9
 * This program is free software: you can redistribute it and/or modify
10
 * it under the terms of the GNU Affero General Public License as
11
 * published by the Free Software Foundation, either version 3 of the
12
 * License, or (at your option) any later version.
13
 *
14
 * This program is distributed in the hope that it will be useful,
15
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17
 * GNU Affero General Public License for more details.
18
 *
19
 * You should have received a copy of the GNU Affero General Public License
20
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
21
 *
22
 */
23
24
namespace OCA\FaceRecognition\Model\ExternalModel;
25
26
use OCA\FaceRecognition\Service\SettingsService;
27
28
use OCA\FaceRecognition\Model\IModel;
29
30
use OCA\FaceRecognition\Model\Exceptions\UnavailableException;
31
32
33
class ExternalModel implements IModel {
34
	/*
35
	 * Model description
36
	 */
37
	const FACE_MODEL_ID = 5;
38
	const FACE_MODEL_NAME = 'ExternalModel';
39
	const FACE_MODEL_DESC = 'External model to separate image processing from the web server';
40
	const FACE_MODEL_DOC = 'https://github.com/matiasdelellis/facerecognition-external-model#run-service';
41
42
	/** This model practically does not consume memory. Directly set the limits. */
43
	const MINIMUM_MEMORY_REQUIREMENTS = 128 * 1024 * 1024;
44
45
	/** @var String|null model api endpoint */
46
	private $modelUrl = null;
47
48
	/** @var String|null model api key */
49
	private $modelApiKey = null;
50
51
	/** @var String|null preferred mimetype */
52
	private $preferredMimetype = null;
53
54
	/** @var int maximun image area */
55
	private $maximumImageArea = -1;
56
57
	/** @var SettingsService */
58
	private $settingsService;
59
60
	/**
61
	 * ExternalModel __construct.
62
	 *
63
	 * @param SettingsService $settingsService
64
	 */
65 1
	public function __construct(SettingsService $settingsService)
66
	{
67 1
		$this->settingsService = $settingsService;
68
	}
69
70
	public function getId(): int {
71
		return static::FACE_MODEL_ID;
72
	}
73
74
	public function getName(): string {
75
		return static::FACE_MODEL_NAME;
76
	}
77
78
	public function getDescription(): string {
79
		return static::FACE_MODEL_DESC;
80
	}
81
82
	public function getDocumentation(): string {
83
		return static::FACE_MODEL_DOC;
84
	}
85
86
	public function isInstalled(): bool {
87
		$this->modelUrl = $this->settingsService->getExternalModelUrl();
88
		$this->modelApiKey = $this->settingsService->getExternalModelApiKey();
89
		return !is_null($this->modelUrl) && !is_null($this->modelApiKey);
90
	}
91
92
	public function meetDependencies(string &$error_message): bool {
93
		if (is_null($this->settingsService->getExternalModelUrl())) {
94
			$error_message = "You still need to configure the URL of the service running the model.";
95
			return false;
96
		}
97
		if (is_null($this->settingsService->getExternalModelApiKey())) {
98
			$error_message = "You still need to configure the API KEY of the service running the model.";
99
			return false;
100
		}
101
		return true;
102
	}
103
104
	public function getMaximumArea(): int {
105
		if ($this->maximumImageArea < 0) {
106
			throw new \Exception('It seems that the model did not open correctly');
107
		}
108
		return $this->maximumImageArea;
109
	}
110
111
	public function getPreferredMimeType(): string {
112
		if (is_null($this->preferredMimetype)) {
113
			throw new \Exception('It seems that the model did not open correctly');
114
		}
115
		return $this->preferredMimetype;
116
	}
117
118
	/**
119
	 * @return void
120
	 */
121
	public function install() {
122
		$this->open();
123
		return;
124
	}
125
126
	/**
127
	 * @return void
128
	 */
129
	public function open() {
130
		$this->modelUrl = $this->settingsService->getExternalModelUrl();
131
		$this->modelApiKey = $this->settingsService->getExternalModelApiKey();
132
133
		$ch = curl_init();
134
		if ($ch === false) {
135
			throw new \Exception('Curl error: unable to initialize curl');
136
		}
137
138
		curl_setopt($ch, CURLOPT_URL, $this->modelUrl . '/open');
139
		curl_setopt($ch, CURLOPT_HTTPHEADER, ['x-api-key: ' . $this->modelApiKey]);
140
		curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
141
142
		$response = curl_exec($ch);
143
		if (is_bool($response)) {
144
			throw new UnavailableException(curl_error($ch));
145
		}
146
147
		$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
148
		if ($httpCode !== 200) {
149
			throw new \Exception('External model response /open with error. HTTP status code: ' . $httpCode);
150
		}
151
152
		$jsonResponse = json_decode($response, true);
153
154
		$this->maximumImageArea = intval($jsonResponse['maximum_area']);
155
		$this->preferredMimetype = $jsonResponse['preferred_mimetype'];
156
157
		curl_close($ch);
158
	}
159
160
	public function detectFaces(string $imagePath, bool $compute = true): array {
161
		$ch = curl_init();
162
		if ($ch === false) {
163
			throw new \Exception('Curl error: unable to initialize curl');
164
		}
165
166
		$cFile = curl_file_create($imagePath);
167
		$post = array('file'=> $cFile);
168
169
		curl_setopt($ch, CURLOPT_URL, $this->modelUrl . '/detect');
170
		curl_setopt($ch, CURLOPT_POST, true);
171
		curl_setopt($ch, CURLOPT_POSTFIELDS, $post);
172
		curl_setopt($ch, CURLOPT_HTTPHEADER, ['x-api-key: ' . $this->modelApiKey]);
173
		curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
174
175
		$response = curl_exec($ch);
176
		if (is_bool($response)) {
177
			throw new UnavailableException(curl_error($ch));
178
		}
179
180
		$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
181
		if ($httpCode !== 200) {
182
			throw new \Exception('External model response /detect with error. HTTP status code: ' . $httpCode);
183
		}
184
185
		curl_close($ch);
186
187
		$jsonResponse = json_decode($response, true);
188
189
		if (!is_array($jsonResponse))
190
			return [];
191
192
		if ($jsonResponse['faces-count'] == 0)
193
			return [];
194
195
		return $jsonResponse['faces'];
196
	}
197
198
	public function compute(string $imagePath, array $face): array {
199
		$ch = curl_init();
200
		if ($ch === false) {
201
			throw new \Exception('Curl error: unable to initialize curl');
202
		}
203
204
		$cFile = curl_file_create($imagePath, $this->preferredMimetype, basename($imagePath));
205
		$post = [
206
			'file' => $cFile,
207
			'face' => json_encode($face),
208
		];
209
210
		curl_setopt($ch, CURLOPT_URL, $this->modelUrl . '/compute');
211
		curl_setopt($ch, CURLOPT_POST, true);
212
		curl_setopt($ch, CURLOPT_POSTFIELDS, $post);
213
		curl_setopt($ch, CURLOPT_HTTPHEADER, ['x-api-key:' . $this->modelApiKey]);
214
		curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
215
216
		$response = curl_exec($ch);
217
		if (is_bool($response)) {
218
			throw new UnavailableException(curl_error($ch));
219
		}
220
221
		$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
222
		if ($httpCode !== 200) {
223
			throw new \Exception('External model response /compute with error. HTTP status code: ' . $httpCode);
224
		}
225
226
		curl_close($ch);
227
228
		$jsonResponse = json_decode($response, true);
229
230
		return $jsonResponse['face'];
231
	}
232
233
}
234