Passed
Push — imaginary ( eaed66...5e9e61 )
by Matias
06:36 queued 12s
created

FileService::downloaldFile()   B

Complexity

Conditions 7
Paths 8

Size

Total Lines 59
Code Lines 40

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 20
CRAP Score 14.0419

Importance

Changes 0
Metric Value
cc 7
eloc 40
c 0
b 0
f 0
nc 8
nop 1
dl 0
loc 59
ccs 20
cts 42
cp 0.4762
crap 14.0419
rs 8.3466

How to fix   Long Method   

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
136
			try {
137 1
				$parentNode = $parentNode->getParent();
138 1
			} catch (NotFoundException $e) {
139 1
				return false;
140
			}
141
		}
142
		return false;
143
	}
144
145
	/**
146
	 * Checks if this folder has .nomedia file an .facerecognition.json setting file that
147
	 * disable that analysis.
148
	 *
149
	 * @param Folder $folder Folder to search for
150
	 * @return bool true if folder dont have an .nomedia file or .facerecognition.json that disabled
151
	 * analysis, false otherwise
152
	 */
153 3
	public function getDescendantDetection(Folder $folder): bool {
154
		try {
155 3
			if ($folder->nodeExists(FileService::NOMEDIA_FILE) ||
156 3
			    $folder->nodeExists(FileService::NOIMAGE_FILE)) {
157 2
				return false;
158
			}
159
160 3
			if (!$folder->nodeExists(FileService::FACERECOGNITION_SETTINGS_FILE)) {
161 3
				return true;
162
			}
163
164
			$file = $folder->get(FileService::FACERECOGNITION_SETTINGS_FILE);
165
			if (!($file instanceof File)) // Maybe the node exists but it can be a folder.
166
				return true;
167
168
			$settings = json_decode($file->getContent(), true);
169
			if ($settings === null || !array_key_exists('detection', $settings)) {
170
				return true;
171
			}
172
173
			if ($settings['detection'] === 'off') {
174
				return false;
175
			}
176
		} catch (StorageNotAvailableException $e) {
177
			return false;
178
		}
179
180
		return true;
181
	}
182
183
	/**
184
	 * Set this folder to enable or disable the analysis using the .facerecognition.json file.
185
	 *
186
	 * @param Folder $folder Folder to enable/disable for
187
	 * @return bool true if the change is done. False if failed.
188
	 */
189
	public function setDescendantDetection(Folder $folder, bool $detection): bool {
190
		if ($folder->nodeExists(FileService::FACERECOGNITION_SETTINGS_FILE)) {
191
			$file = $folder->get(FileService::FACERECOGNITION_SETTINGS_FILE);
192
			if (!($file instanceof File)) // Maybe the node exists but it can be a folder.
193
				return false;
194
195
			$settings = json_decode($file->getContent(), true);
196
			if ($settings === null) // Invalid json
197
				return false;
198
		}
199
		else {
200
			$file = $folder->newFile(FileService::FACERECOGNITION_SETTINGS_FILE);
201
			$settings = array();
202
		}
203
204
		$settings['detection'] = $detection ? "on" : "off";
205
		$file->putContent(json_encode($settings));
206
207
		return true;
208
	}
209
210
	/**
211
	 * Returns if the file is inside a native home storage.
212
	 */
213 8
	public function isUserFile(Node $node): bool {
214 8
		return (($node->getMountPoint()->getMountType() === '') &&
215 8
		        ($node->getStorage()->instanceOfStorage(IHomeStorage::class)));
216
	}
217
218
	/**
219
	 * Returns if the file is inside a shared mount storage.
220
	 */
221
	public function isSharedFile(Node $node): bool {
222
		return ($node->getMountPoint()->getMountType() === 'shared');
223
	}
224
225
	/**
226
	 * Returns if the file is inside a external mount storage.
227
	 */
228
	public function isExternalFile(Node $node): bool {
229
		return ($node->getMountPoint()->getMountType() === 'external');
230
	}
231
232
	/**
233
	 * Returns if the file is inside a group folder storage.
234
	 */
235
	public function isGroupFile(Node $node): bool {
236
		return ($node->getMountPoint()->getMountType() === 'group');
237
	}
238
239
	/**
240
	 * Returns if the Node is allowed based on preferences.
241
	 */
242 8
	public function isAllowedNode(Node $node): bool {
243 8
		if ($this->isUserFile($node)) {
244 8
			return true;
245
		} else if ($this->isSharedFile($node)) {
246
			return $this->settingsService->getHandleSharedFiles();
247
		} else if ($this->isExternalFile($node)) {
248
			return $this->settingsService->getHandleExternalFiles();
249
		} else if ($this->isGroupFile($node)) {
250
			return $this->settingsService->getHandleGroupFiles();
251
		}
252
		return false;
253
	}
254
255
	/**
256
	 * Get a path to either the local file or temporary file
257
	 *
258
	 * @param File $file
259
	 * @param int $maxSize maximum size for temporary files
260
	 * @return string|null
261
	 */
262 4
	public function getLocalFile(File $file, int $maxSize = null): ?string {
263 4
		$useTempFile = $file->isEncrypted() || !$file->getStorage()->isLocal();
264 4
		if ($useTempFile) {
265
			$absPath = $this->tempManager->getTemporaryFile();
266
267
			$content = $file->fopen('r');
268
			if ($maxSize !== null) {
269
				$content = stream_get_contents($content, $maxSize);
270
			}
271
			file_put_contents($absPath, $content);
272
273
			return $absPath;
274
		} else {
275 4
			$localPath = $file->getStorage()->getLocalFile($file->getInternalPath());
276 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...
277
		}
278
	}
279
280
	/**
281
	 * Return all images from a given folder.
282
	 *
283
	 * TODO: It is inefficient since it copies the array recursively.
284
	 *
285
	 * @param Folder $folder Folder to get images from
286
	 * @return array List of all images and folders to continue recursive crawling
287
	 */
288 10
	public function getPicturesFromFolder(Folder $folder, $results = array()) {
289 10
		$nodes = $folder->getDirectoryListing();
290 10
		foreach ($nodes as $node) {
291 8
			if (!$this->isAllowedNode($node)) {
292
				continue;
293
			}
294 8
			if ($node instanceof Folder && $this->getDescendantDetection($node)) {
295 3
				$results = $this->getPicturesFromFolder($node, $results);
296
			}
297 8
			else if ($node instanceof File) {
298 8
				if ($this->settingsService->isAllowedMimetype($node->getMimeType())) {
299 7
					$results[] = $node;
300
				}
301
			}
302
		}
303 10
		return $results;
304
	}
305
306
307
	/**
308
	 * Download a file in a temporary folder
309
	 *
310
	 * @param string $fileUrl url to download.
311
	 * @return string temp file downloaded.
312
	 *
313
	 * @throws \Exception
314
	 */
315 1
	public function downloaldFile(string $fileUrl): string {
316 1
		$tempFolder = $this->tempManager->getTemporaryFolder('/facerecognition/');
317 1
		$tempFile = $tempFolder . basename($fileUrl);
318
319 1
		$fp = fopen($tempFile, 'w+');
320 1
		if ($fp === false) {
321
			throw new \Exception('Could not open the file to write: ' . $tempFile);
322
		}
323
324 1
		$ch = curl_init($fileUrl);
325 1
		if ($ch === false) {
326
			throw new \Exception('Curl error: unable to initialize curl');
327
		}
328
329 1
		curl_setopt_array($ch, [
330 1
			CURLOPT_FILE => $fp,
331 1
			CURLOPT_FOLLOWLOCATION => true,
332 1
			CURLOPT_SSL_VERIFYPEER => false,
333 1
			CURLOPT_SSL_VERIFYHOST => 0,
334 1
			CURLOPT_USERAGENT => 'Nextcloud Facerecognition Service',
335 1
		]);
336
337 1
		if (curl_exec($ch) === false) {
338
			throw new \Exception('Curl error: ' . curl_error($ch));
339
		}
340
341 1
		$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
342 1
		if ($httpCode !== 200) {
343
			$statusCodes = [
344
				400 => 'Bad request',
345
				401 => 'Unauthorized',
346
				403 => 'Forbidden',
347
				404 => 'Not Found',
348
				500 => 'Internal Server Error',
349
				502 => 'Bad Gateway',
350
				503 => 'Service Unavailable',
351
				504 => 'Gateway Timeout',
352
			];
353
354
			$message = 'Download failed';
355
			if(isset($statusCodes[$httpCode])) {
356
				$message .= ' - ' . $statusCodes[$httpCode] . ' (HTTP ' . $httpCode . ')';
357
			} else {
358
				$message .= ' - HTTP status code: ' . $httpCode;
359
			}
360
361
			$curlErrorMessage = curl_error($ch);
362
			if(!empty($curlErrorMessage)) {
363
				$message .= ' - curl error message: ' . $curlErrorMessage;
364
			}
365
			$message .= ' - URL: ' . htmlentities($fileUrl);
366
367
			throw new \Exception($message);
368
		}
369
370 1
		curl_close($ch);
371 1
		fclose($fp);
372
373 1
		return $tempFile;
374
	}
375
376
	/**
377
	 * Uncompressing the file with the bzip2-extension
378
	 *
379
	 * @param string $in
380
	 * @param string $out
381
	 *
382
	 * @throws \Exception
383
	 *
384
	 * @return void
385
	 */
386 1
	public function bunzip2(string $inputFile, string $outputFile): void {
387 1
		if (!file_exists ($inputFile) || !is_readable ($inputFile))
388
			throw new \Exception('The file ' . $inputFile . ' not exists or is not readable');
389
390 1
		if ((!file_exists($outputFile) && !is_writeable(dirname($outputFile))) ||
391 1
		    (file_exists($outputFile) && !is_writable($outputFile)))
392
			throw new \Exception('The file ' . $outputFile . ' exists or is not writable');
393
394 1
		$in_file = bzopen ($inputFile, "r");
395 1
		$out_file = fopen ($outputFile, "w");
396
397 1
		if ($out_file === false)
398
			throw new \Exception('Could not open the file to write: ' . $outputFile);
399
400 1
		while ($buffer = bzread ($in_file, 4096)) {
401 1
			if($buffer === false)
402
				throw new \Exception('Read problem: ' . bzerrstr($in_file));
403 1
			if(bzerrno($in_file) !== 0)
404
				throw new \Exception('Compression problem: '. bzerrstr($in_file));
405
406 1
			fwrite ($out_file, $buffer, 4096);
407
		}
408
409 1
		bzclose ($in_file);
410 1
		fclose ($out_file);
411
	}
412
413
	/**
414
	 * Create a temporary file and return the path
415
	 */
416
	public function getTemporaryFile(string $postFix = ''): string {
417
		return $this->tempManager->getTemporaryFile($postFix);
418
	}
419
420
	/**
421
	 * Remove any temporary file from the service.
422
	 *
423
	 * @return void
424
	 */
425 4
	public function clean(): void {
426 4
		$this->tempManager->clean();
427
	}
428
429
}
430