Passed
Push — self-contained-model ( 4e2728...296628 )
by Matias
12:47
created

FileService::bunzip2()   B

Complexity

Conditions 11
Paths 7

Size

Total Lines 25
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 13.594

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 11
eloc 17
c 1
b 0
f 0
nc 7
nop 2
dl 0
loc 25
ccs 13
cts 18
cp 0.7221
crap 13.594
rs 7.3166

How to fix   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
declare(strict_types=1);
3
4
/**
5
 * @copyright Copyright (c) 2019-2020 Matias De lellis <[email protected]>
6
 *
7
 * @author Matias De lellis <[email protected]>
8
 *
9
 * @license GNU AGPL version 3 or any later version
10
 *
11
 * This program is free software: you can redistribute it and/or modify
12
 * it under the terms of the GNU Affero General Public License as
13
 * published by the Free Software Foundation, either version 3 of the
14
 * License, or (at your option) any later version.
15
 *
16
 * This program is distributed in the hope that it will be useful,
17
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19
 * GNU Affero General Public License for more details.
20
 *
21
 * You should have received a copy of the GNU Affero General Public License
22
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
23
 *
24
 */
25
26
namespace OCA\FaceRecognition\Service;
27
28
use OCP\Files\IRootFolder;
29
use OCP\Files\File;
30
use OCP\Files\Folder;
31
use OCP\Files\Node;
32
use OCP\ITempManager;
33
34
use OCP\Files\IHomeStorage;
35
use OCP\Files\NotFoundException;
36
37
use OCA\Files_Sharing\External\Storage as SharingExternalStorage;
0 ignored issues
show
Bug introduced by
The type OCA\Files_Sharing\External\Storage 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...
38
39
use OCA\FaceRecognition\Helper\Requirements;
40
use OCA\FaceRecognition\Service\SettingsService;
41
42
class FileService {
43
44
	const NOMEDIA_FILE = ".nomedia";
45
46
	const FACERECOGNITION_SETTINGS_FILE = ".facerecognition.json";
47
48
	/**  @var string|null */
49
	private $userId;
50
51
	/** @var IRootFolder */
52
	private $rootFolder;
53
54
	/** @var ITempManager */
55
	private $tempManager;
56
57
	/** @var SettingsService */
58
	private $settingsService;
59
60 1
	public function __construct($userId,
61
	                            IRootFolder     $rootFolder,
62
	                            ITempManager    $tempManager,
63
	                            SettingsService $settingsService)
64
	{
65 1
		$this->userId          = $userId;
66 1
		$this->rootFolder      = $rootFolder;
67 1
		$this->tempManager     = $tempManager;
68 1
		$this->settingsService = $settingsService;
69 1
	}
70
71
	/**
72
	 * TODO: Describe exactly when necessary.
73
	 */
74 11
	public function setupFS(string $userId) {
75 11
		\OC_Util::tearDownFS();
0 ignored issues
show
Bug introduced by
The type OC_Util 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...
76 11
		\OC_Util::setupFS($userId);
77
78 11
		$this->userId = $userId;
79 11
	}
80
81
	/**
82
	 * Get a Node from userFolder
83
	 * @param int $id the id of the Node
84
	 * @param string $userId
85
	 * @return Node | null
86
	 */
87 6
	public function getFileById($fileId, $userId = null): ?Node {
88 6
		$files = $this->rootFolder->getUserFolder($this->userId ?? $userId)->getById($fileId);
89 6
		if (count($files) === 0) {
90 1
			return null;
91
		}
92
93 5
		return $files[0];
94
	}
95
96
	/**
97
	 * Get a Node from userFolder
98
	 * @param string $fullpath the fullpath of the Node
99
	 * @param string $userId
100
	 * @return Node | null
101
	 */
102
	public function getFileByPath($fullpath, $userId = null): ?Node {
103
		$file = $this->rootFolder->getUserFolder($this->userId ?? $userId)->get($fullpath);
104
		return $file;
105
	}
106
107
	/**
108
	 * Checks if this file is located somewhere under .nomedia file and should be therefore ignored.
109
	 * Or with an .facerecognition.json setting file that disable tha analysis
110
	 *
111
	 * @param File $file File to search for
112
	 * @return bool True if file is located under .nomedia or .facerecognition.json that disabled
113
	 * analysis, false otherwise
114
	 */
115 1
	public function isUnderNoDetection(Node $node): bool {
116
		// If we detect .nomedia file anywhere on the path to root folder (id===null), bail out
117 1
		$parentNode = $node->getParent();
118 1
		while (($parentNode instanceof Folder) && ($parentNode->getId() !== null)) {
119 1
			$allowDetection = $this->getDescendantDetection($parentNode);
120 1
			if (!$allowDetection)
121 1
				return true;
122 1
			$parentNode = $parentNode->getParent();
123
		}
124 1
		return false;
125
	}
126
127
	/**
128
	 * Checks if this folder has .nomedia file an .facerecognition.json setting file that
129
	 * disable that analysis.
130
	 *
131
	 * @param Folder $folder Folder to search for
132
	 * @return bool true if folder dont have an .nomedia file or .facerecognition.json that disabled
133
	 * analysis, false otherwise
134
	 */
135 3
	public function getDescendantDetection(Folder $folder): bool {
136 3
		if ($folder->nodeExists(FileService::NOMEDIA_FILE)) {
137 2
			return false;
138
		}
139 3
		if ($folder->nodeExists(FileService::FACERECOGNITION_SETTINGS_FILE)) {
140
			$file = $folder->get(FileService::FACERECOGNITION_SETTINGS_FILE);
141
			$settings = json_decode($file->getContent(), true);
0 ignored issues
show
Bug introduced by
The method getContent() does not exist on OCP\Files\Node. It seems like you code against a sub-type of OCP\Files\Node such as OCP\Files\File. ( Ignorable by Annotation )

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

141
			$settings = json_decode($file->/** @scrutinizer ignore-call */ getContent(), true);
Loading history...
142
			if ($settings === null || !array_key_exists('detection', $settings))
143
				return true;
144
145
			if ($settings['detection'] === 'off')
146
				return false;
147
		}
148
149 3
		return true;
150
	}
151
152
	/**
153
	 * Set this folder to enable or disable the analysis using the .facerecognition.json file.
154
	 *
155
	 * @param Folder $folder Folder to enable/disable for
156
	 * @return bool true if the change is done. False if failed.
157
	 */
158
	public function setDescendantDetection(Folder $folder, bool $detection): bool {
159
		if ($folder->nodeExists(FileService::FACERECOGNITION_SETTINGS_FILE)) {
160
			$file = $folder->get(FileService::FACERECOGNITION_SETTINGS_FILE);
161
			$settings = json_decode($file->getContent(), true);
162
			if ($settings === null) {
163
				// Invalid json.
164
				return false;
165
			}
166
		}
167
		else {
168
			$file = $folder->newFile(FileService::FACERECOGNITION_SETTINGS_FILE);
169
			$settings = array();
170
		}
171
172
		$settings['detection'] = $detection ? "on" : "off";
173
		$file->putContent(json_encode($settings));
0 ignored issues
show
Bug introduced by
The method putContent() does not exist on OCP\Files\Node. It seems like you code against a sub-type of OCP\Files\Node such as OCP\Files\File. ( Ignorable by Annotation )

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

173
		$file->/** @scrutinizer ignore-call */ 
174
         putContent(json_encode($settings));
Loading history...
174
175
		return true;
176
	}
177
178
	/**
179
	 * Returns if the file is inside a shared storage.
180
	 */
181 28
	public function isSharedFile(Node $node): bool {
182 28
		return $node->getStorage()->instanceOfStorage(SharingExternalStorage::class);
183
	}
184
185
	/**
186
	 * Returns if the file is inside HomeStorage.
187
	 */
188 28
	public function isUserFile(Node $node): bool {
189 28
		return $node->getStorage()->instanceOfStorage(IHomeStorage::class);
190
	}
191
192
	/**
193
	 * Returns if the Node is allowed based on preferences.
194
	 */
195 28
	public function isAllowedNode(Node $node): bool {
196 28
		if ($this->isUserFile($node)) {
197 28
			return true;
198 28
		} else if ($this->isSharedFile($node)) {
199
			return $this->settingsService->getHandleSharedFiles();
200
		}
201 28
		return false;
202
	}
203
204
	/**
205
	 * Get a path to either the local file or temporary file
206
	 *
207
	 * @param File $file
208
	 * @param int $maxSize maximum size for temporary files
209
	 * @return string
210
	 */
211 4
	public function getLocalFile(File $file, int $maxSize = null): string {
212 4
		$useTempFile = $file->isEncrypted() || !$file->getStorage()->isLocal();
213 4
		if ($useTempFile) {
214
			$absPath = $this->tempManager->getTemporaryFile();
215
216
			$content = $file->fopen('r');
217
			if ($maxSize !== null) {
218
				$content = stream_get_contents($content, $maxSize);
219
			}
220
			file_put_contents($absPath, $content);
221
222
			return $absPath;
223
		} else {
224 4
			return $file->getStorage()->getLocalFile($file->getInternalPath());
0 ignored issues
show
Bug Best Practice introduced by
The expression return $file->getStorage...ile->getInternalPath()) could return the type false which is incompatible with the type-hinted return string. Consider adding an additional type-check to rule them out.
Loading history...
225
		}
226
	}
227
228
	/**
229
	 * Return all images from a given folder.
230
	 *
231
	 * TODO: It is inefficient since it copies the array recursively.
232
	 *
233
	 * @param Folder $folder Folder to get images from
234
	 * @return array List of all images and folders to continue recursive crawling
235
	 */
236 10
	public function getPicturesFromFolder(Folder $folder, $results = array()) {
237 10
		$nodes = $folder->getDirectoryListing();
238 10
		foreach ($nodes as $node) {
239 8
			if (!$this->isAllowedNode($node)) {
240
				continue;
241
			}
242 8
			if ($node instanceof Folder && $this->getDescendantDetection($node)) {
243 3
				$results = $this->getPicturesFromFolder($node, $results);
244
			}
245 8
			else if ($node instanceof File) {
246 8
				if (Requirements::isImageTypeSupported($node->getMimeType())) {
247 8
					$results[] = $node;
248
				}
249
			}
250
		}
251 10
		return $results;
252
	}
253
254
255
	/**
256
	 * Download a file in a temporary folder
257
	 *
258
	 * @param string $fileUrl url to download.
259
	 * @return string temp file downloaded.
260
	 *
261
	 * @throws \Exception
262
	 */
263 1
	public function downloaldFile(string $fileUrl): string {
264 1
		$tempFolder = $this->tempManager->getTemporaryFolder('/facerecognition/');
265 1
		$tempFile = $tempFolder . basename($fileUrl);
266
267 1
		$fp = fopen($tempFile, 'w+');
268 1
		if ($fp === false) {
269
			throw new \Exception('Could not open the file to write: ' . $tempFile);
270
		}
271
272 1
		$ch = curl_init($fileUrl);
273 1
		if ($ch === false) {
274
			throw new \Exception('Curl error: ' . curl_error($ch));
0 ignored issues
show
Bug introduced by
It seems like $ch can also be of type false; however, parameter $ch of curl_error() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

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

274
			throw new \Exception('Curl error: ' . curl_error(/** @scrutinizer ignore-type */ $ch));
Loading history...
275
		}
276
277 1
		curl_setopt_array($ch, [
278 1
			CURLOPT_FILE => $fp,
279 1
			CURLOPT_FOLLOWLOCATION => true,
280 1
			CURLOPT_SSL_VERIFYPEER => false,
281 1
			CURLOPT_SSL_VERIFYHOST => 0,
282 1
			CURLOPT_USERAGENT => 'Nextcloud Facerecognition Service',
283
		]);
284
285 1
		if (curl_exec($ch) === false) {
286
			throw new \Exception('Curl error: ' . curl_error($ch));
287
		}
288
289 1
		$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
290 1
		if ($httpCode !== 200) {
291
			$statusCodes = [
292
				400 => 'Bad request',
293
				401 => 'Unauthorized',
294
				403 => 'Forbidden',
295
				404 => 'Not Found',
296
				500 => 'Internal Server Error',
297
				502 => 'Bad Gateway',
298
				503 => 'Service Unavailable',
299
				504 => 'Gateway Timeout',
300
			];
301
302
			$message = 'Download failed';
303
			if(isset($statusCodes[$httpCode])) {
304
				$message .= ' - ' . $statusCodes[$httpCode] . ' (HTTP ' . $httpCode . ')';
305
			} else {
306
				$message .= ' - HTTP status code: ' . $httpCode;
307
			}
308
309
			$curlErrorMessage = curl_error($ch);
310
			if(!empty($curlErrorMessage)) {
311
				$message .= ' - curl error message: ' . $curlErrorMessage;
312
			}
313
			$message .= ' - URL: ' . htmlentities($fileUrl);
314
315
			throw new \Exception($message);
316
		}
317
318 1
		curl_close($ch);
319 1
		fclose($fp);
320
321 1
		return $tempFile;
322
	}
323
324
	/**
325
	 * Uncompressing the file with the bzip2-extension
326
	 *
327
	 * @param string $in
328
	 * @param string $out
329
	 *
330
	 * @throws \Exception
331
	 */
332 1
	public function bunzip2(string $inputFile, string $outputFile) {
333 1
		if (!file_exists ($inputFile) || !is_readable ($inputFile))
334
			throw new \Exception('The file ' . $inputFile . ' not exists or is not readable');
335
336 1
		if ((!file_exists($outputFile) && !is_writeable(dirname($outputFile))) ||
337 1
		    (file_exists($outputFile) && !is_writable($outputFile)))
338
			throw new \Exception('The file ' . $outputFile . ' exists or is not writable');
339
340 1
		$in_file = bzopen ($inputFile, "r");
341 1
		$out_file = fopen ($outputFile, "w");
342
343 1
		if ($out_file === false)
344
			throw new \Exception('Could not open the file to write: ' . $outputFile);
345
346 1
		while ($buffer = bzread ($in_file, 4096)) {
347 1
			if($buffer === false)
348
				throw new \Exception('Read problem: ' . bzerrstr($in_file));
349 1
			if(bzerrno($in_file) !== 0)
350
				throw new \Exception('Compression problem: '. bzerrstr($in_file));
351
352 1
			fwrite ($out_file, $buffer, 4096);
353
		}
354
355 1
		bzclose ($in_file);
356 1
		fclose ($out_file);
357 1
	}
358
359
	/**
360
	 * Create a temporary file and return the path
361
	 */
362 2
	public function getTemporaryFile(string $postFix = ''): string {
363 2
		return $this->tempManager->getTemporaryFile($postFix);
364
	}
365
366
	/**
367
	 * Remove any temporary file from the service.
368
	 */
369 4
	public function clean() {
370 4
		$this->tempManager->clean();
371 4
	}
372
373
}
374