Passed
Push — dependabot/npm_and_yarn/minimi... ( f33d6d )
by
unknown
11:45
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
use OCP\Files\StorageNotAvailableException;
37
38
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...
39
40
use OCA\FaceRecognition\Service\SettingsService;
41
42
class FileService {
43
44
	const NOMEDIA_FILE = ".nomedia";
45
46
	const NOIMAGE_FILE = ".noimage";
47
48
	const FACERECOGNITION_SETTINGS_FILE = ".facerecognition.json";
49
50
	/**  @var string|null */
51
	private $userId;
52
53
	/** @var IRootFolder */
54
	private $rootFolder;
55
56
	/** @var ITempManager */
57
	private $tempManager;
58
59
	/** @var SettingsService */
60
	private $settingsService;
61
62 1
	public function __construct($userId,
63
	                            IRootFolder     $rootFolder,
64
	                            ITempManager    $tempManager,
65
	                            SettingsService $settingsService)
66
	{
67 1
		$this->userId          = $userId;
68 1
		$this->rootFolder      = $rootFolder;
69 1
		$this->tempManager     = $tempManager;
70 1
		$this->settingsService = $settingsService;
71
	}
72
73
	/**
74
	 * TODO: Describe exactly when necessary.
75
	 *
76
	 * @return void
77
	 */
78 11
	public function setupFS(string $userId): void {
79 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...
80 11
		\OC_Util::setupFS($userId);
81
82 11
		$this->userId = $userId;
83
	}
84
85
	/**
86
	 * Get root user folder
87
	 * @param string $userId
88
	 * @return Folder
89
	 */
90 10
	public function getUserFolder($userId = null): Folder {
91 10
		return $this->rootFolder->getUserFolder($this->userId ?? $userId);
92
	}
93
94
	/**
95
	 * Get a Node from userFolder
96
	 * @param int $id the id of the Node
97
	 * @param string $userId
98
	 * @return Node | null
99
	 */
100 6
	public function getFileById(int $fileId, $userId = null): ?Node {
101 6
		$files = $this->rootFolder->getUserFolder($this->userId ?? $userId)->getById($fileId);
102 6
		if (count($files) === 0) {
103 1
			return null;
104
		}
105
106 5
		return $files[0];
107
	}
108
109
	/**
110
	 * Get a Node from userFolder
111
	 * @param string $fullpath the fullpath of the Node
112
	 * @param string $userId
113
	 * @return Node | null
114
	 */
115
	public function getFileByPath($fullpath, $userId = null): ?Node {
116
		$file = $this->rootFolder->getUserFolder($this->userId ?? $userId)->get($fullpath);
117
		return $file;
118
	}
119
120
	/**
121
	 * Checks if this file is located somewhere under .nomedia file and should be therefore ignored.
122
	 * Or with an .facerecognition.json setting file that disable tha analysis
123
	 *
124
	 * @param File $file File to search for
125
	 * @return bool True if file is located under .nomedia or .facerecognition.json that disabled
126
	 * analysis, false otherwise
127
	 */
128 1
	public function isUnderNoDetection(Node $node): bool {
129
		// If we detect .nomedia file anywhere on the path to root folder (id===null), bail out
130 1
		$parentNode = $node->getParent();
131 1
		while (($parentNode instanceof Folder) && ($parentNode->getId() !== null)) {
132 1
			$allowDetection = $this->getDescendantDetection($parentNode);
133 1
			if (!$allowDetection)
134 1
				return true;
135 1
			$parentNode = $parentNode->getParent();
136
		}
137 1
		return false;
138
	}
139
140
	/**
141
	 * Checks if this folder has .nomedia file an .facerecognition.json setting file that
142
	 * disable that analysis.
143
	 *
144
	 * @param Folder $folder Folder to search for
145
	 * @return bool true if folder dont have an .nomedia file or .facerecognition.json that disabled
146
	 * analysis, false otherwise
147
	 */
148 3
	public function getDescendantDetection(Folder $folder): bool {
149
		try {
150 3
			if ($folder->nodeExists(FileService::NOMEDIA_FILE) ||
151 3
			    $folder->nodeExists(FileService::NOIMAGE_FILE)) {
152 2
				return false;
153
			}
154
155 3
			if (!$folder->nodeExists(FileService::FACERECOGNITION_SETTINGS_FILE)) {
156 3
				return true;
157
			}
158
159
			$file = $folder->get(FileService::FACERECOGNITION_SETTINGS_FILE);
160
			if (!($file instanceof File)) // Maybe the node exists but it can be a folder.
161
				return true;
162
163
			$settings = json_decode($file->getContent(), true);
164
			if ($settings === null || !array_key_exists('detection', $settings)) {
165
				return true;
166
			}
167
168
			if ($settings['detection'] === 'off') {
169
				return false;
170
			}
171
		} catch (StorageNotAvailableException $e) {
172
			return false;
173
		}
174
175
		return true;
176
	}
177
178
	/**
179
	 * Set this folder to enable or disable the analysis using the .facerecognition.json file.
180
	 *
181
	 * @param Folder $folder Folder to enable/disable for
182
	 * @return bool true if the change is done. False if failed.
183
	 */
184
	public function setDescendantDetection(Folder $folder, bool $detection): bool {
185
		if ($folder->nodeExists(FileService::FACERECOGNITION_SETTINGS_FILE)) {
186
			$file = $folder->get(FileService::FACERECOGNITION_SETTINGS_FILE);
187
			if (!($file instanceof File)) // Maybe the node exists but it can be a folder.
188
				return false;
189
190
			$settings = json_decode($file->getContent(), true);
191
			if ($settings === null) // Invalid json
192
				return false;
193
		}
194
		else {
195
			$file = $folder->newFile(FileService::FACERECOGNITION_SETTINGS_FILE);
196
			$settings = array();
197
		}
198
199
		$settings['detection'] = $detection ? "on" : "off";
200
		$file->putContent(json_encode($settings));
201
202
		return true;
203
	}
204
205
	/**
206
	 * Returns if the file is inside a native home storage.
207
	 */
208 8
	public function isUserFile(Node $node): bool {
209 8
		return (($node->getMountPoint()->getMountType() === '') &&
210 8
		        ($node->getStorage()->instanceOfStorage(IHomeStorage::class)));
211
	}
212
213
	/**
214
	 * Returns if the file is inside a shared mount storage.
215
	 */
216
	public function isSharedFile(Node $node): bool {
217
		return ($node->getMountPoint()->getMountType() === 'shared');
218
	}
219
220
	/**
221
	 * Returns if the file is inside a external mount storage.
222
	 */
223
	public function isExternalFile(Node $node): bool {
224
		return ($node->getMountPoint()->getMountType() === 'external');
225
	}
226
227
	/**
228
	 * Returns if the file is inside a group folder storage.
229
	 */
230
	public function isGroupFile(Node $node): bool {
231
		return ($node->getMountPoint()->getMountType() === 'group');
232
	}
233
234
	/**
235
	 * Returns if the Node is allowed based on preferences.
236
	 */
237 8
	public function isAllowedNode(Node $node): bool {
238 8
		if ($this->isUserFile($node)) {
239 8
			return true;
240
		} else if ($this->isSharedFile($node)) {
241
			return $this->settingsService->getHandleSharedFiles();
242
		} else if ($this->isExternalFile($node)) {
243
			return $this->settingsService->getHandleExternalFiles();
244
		} else if ($this->isGroupFile($node)) {
245
			return $this->settingsService->getHandleGroupFiles();
246
		}
247
		return false;
248
	}
249
250
	/**
251
	 * Get a path to either the local file or temporary file
252
	 *
253
	 * @param File $file
254
	 * @param int $maxSize maximum size for temporary files
255
	 * @return string|null
256
	 */
257 4
	public function getLocalFile(File $file, int $maxSize = null): ?string {
258 4
		$useTempFile = $file->isEncrypted() || !$file->getStorage()->isLocal();
259 4
		if ($useTempFile) {
260
			$absPath = $this->tempManager->getTemporaryFile();
261
262
			$content = $file->fopen('r');
263
			if ($maxSize !== null) {
264
				$content = stream_get_contents($content, $maxSize);
265
			}
266
			file_put_contents($absPath, $content);
267
268
			return $absPath;
269
		} else {
270 4
			$localPath = $file->getStorage()->getLocalFile($file->getInternalPath());
271 4
			return ($localPath !== false) ? $localPath : null;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $localPath !== false ? $localPath : null could return the type true which is incompatible with the type-hinted return null|string. Consider adding an additional type-check to rule them out.
Loading history...
272
		}
273
	}
274
275
	/**
276
	 * Return all images from a given folder.
277
	 *
278
	 * TODO: It is inefficient since it copies the array recursively.
279
	 *
280
	 * @param Folder $folder Folder to get images from
281
	 * @return array List of all images and folders to continue recursive crawling
282
	 */
283 10
	public function getPicturesFromFolder(Folder $folder, $results = array()) {
284 10
		$nodes = $folder->getDirectoryListing();
285 10
		foreach ($nodes as $node) {
286 8
			if (!$this->isAllowedNode($node)) {
287
				continue;
288
			}
289 8
			if ($node instanceof Folder && $this->getDescendantDetection($node)) {
290 3
				$results = $this->getPicturesFromFolder($node, $results);
291
			}
292 8
			else if ($node instanceof File) {
293 8
				if ($this->settingsService->isAllowedMimetype($node->getMimeType())) {
294 7
					$results[] = $node;
295
				}
296
			}
297
		}
298 10
		return $results;
299
	}
300
301
302
	/**
303
	 * Download a file in a temporary folder
304
	 *
305
	 * @param string $fileUrl url to download.
306
	 * @return string temp file downloaded.
307
	 *
308
	 * @throws \Exception
309
	 */
310 1
	public function downloaldFile(string $fileUrl): string {
311 1
		$tempFolder = $this->tempManager->getTemporaryFolder('/facerecognition/');
312 1
		$tempFile = $tempFolder . basename($fileUrl);
313
314 1
		$fp = fopen($tempFile, 'w+');
315 1
		if ($fp === false) {
316
			throw new \Exception('Could not open the file to write: ' . $tempFile);
317
		}
318
319 1
		$ch = curl_init($fileUrl);
320 1
		if ($ch === false) {
321
			throw new \Exception('Curl error: unable to initialize curl');
322
		}
323
324 1
		curl_setopt_array($ch, [
325
			CURLOPT_FILE => $fp,
326
			CURLOPT_FOLLOWLOCATION => true,
327
			CURLOPT_SSL_VERIFYPEER => false,
328
			CURLOPT_SSL_VERIFYHOST => 0,
329
			CURLOPT_USERAGENT => 'Nextcloud Facerecognition Service',
330
		]);
331
332 1
		if (curl_exec($ch) === false) {
333
			throw new \Exception('Curl error: ' . curl_error($ch));
334
		}
335
336 1
		$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
337 1
		if ($httpCode !== 200) {
338
			$statusCodes = [
339
				400 => 'Bad request',
340
				401 => 'Unauthorized',
341
				403 => 'Forbidden',
342
				404 => 'Not Found',
343
				500 => 'Internal Server Error',
344
				502 => 'Bad Gateway',
345
				503 => 'Service Unavailable',
346
				504 => 'Gateway Timeout',
347
			];
348
349
			$message = 'Download failed';
350
			if(isset($statusCodes[$httpCode])) {
351
				$message .= ' - ' . $statusCodes[$httpCode] . ' (HTTP ' . $httpCode . ')';
352
			} else {
353
				$message .= ' - HTTP status code: ' . $httpCode;
354
			}
355
356
			$curlErrorMessage = curl_error($ch);
357
			if(!empty($curlErrorMessage)) {
358
				$message .= ' - curl error message: ' . $curlErrorMessage;
359
			}
360
			$message .= ' - URL: ' . htmlentities($fileUrl);
361
362
			throw new \Exception($message);
363
		}
364
365 1
		curl_close($ch);
366 1
		fclose($fp);
367
368 1
		return $tempFile;
369
	}
370
371
	/**
372
	 * Uncompressing the file with the bzip2-extension
373
	 *
374
	 * @param string $in
375
	 * @param string $out
376
	 *
377
	 * @throws \Exception
378
	 *
379
	 * @return void
380
	 */
381 1
	public function bunzip2(string $inputFile, string $outputFile): void {
382 1
		if (!file_exists ($inputFile) || !is_readable ($inputFile))
383
			throw new \Exception('The file ' . $inputFile . ' not exists or is not readable');
384
385 1
		if ((!file_exists($outputFile) && !is_writeable(dirname($outputFile))) ||
386 1
		    (file_exists($outputFile) && !is_writable($outputFile)))
387
			throw new \Exception('The file ' . $outputFile . ' exists or is not writable');
388
389 1
		$in_file = bzopen ($inputFile, "r");
390 1
		$out_file = fopen ($outputFile, "w");
391
392 1
		if ($out_file === false)
393
			throw new \Exception('Could not open the file to write: ' . $outputFile);
394
395 1
		while ($buffer = bzread ($in_file, 4096)) {
396 1
			if($buffer === false)
397
				throw new \Exception('Read problem: ' . bzerrstr($in_file));
398 1
			if(bzerrno($in_file) !== 0)
399
				throw new \Exception('Compression problem: '. bzerrstr($in_file));
400
401 1
			fwrite ($out_file, $buffer, 4096);
402
		}
403
404 1
		bzclose ($in_file);
405 1
		fclose ($out_file);
406
	}
407
408
	/**
409
	 * Create a temporary file and return the path
410
	 */
411
	public function getTemporaryFile(string $postFix = ''): string {
412
		return $this->tempManager->getTemporaryFile($postFix);
413
	}
414
415
	/**
416
	 * Remove any temporary file from the service.
417
	 *
418
	 * @return void
419
	 */
420 4
	public function clean(): void {
421 4
		$this->tempManager->clean();
422
	}
423
424
}
425