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