Passed
Push — php8 ( 3e2147...190cd5 )
by Matias
10:58 queued 08:41
created

FileService::downloaldFile()   B

Complexity

Conditions 7
Paths 8

Size

Total Lines 59
Code Lines 40

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 19
CRAP Score 10.284

Importance

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