Passed
Pull Request — master (#444)
by Matias
14:01 queued 11:32
created

FileService::downloaldFile()   B

Complexity

Conditions 7
Paths 8

Size

Total Lines 59
Code Lines 40

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 11.8162

Importance

Changes 0
Metric Value
cc 7
eloc 40
c 0
b 0
f 0
nc 8
nop 1
dl 0
loc 59
ccs 14
cts 26
cp 0.5385
crap 11.8162
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 11
	 * TODO: Describe exactly when necessary.
75 11
	 */
76 11
	public function setupFS(string $userId) {
77
		\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 11
80
		$this->userId = $userId;
81
	}
82
83
	/**
84
	 * Get root user folder
85
	 * @param string $userId
86 10
	 * @return Folder
87 10
	 */
88
	public function getUserFolder($userId = null): Folder {
89
		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 6
	 * @return Node | null
97 6
	 */
98 6
	public function getFileById($fileId, $userId = null): ?Node {
99 1
		$files = $this->rootFolder->getUserFolder($this->userId ?? $userId)->getById($fileId);
100
		if (count($files) === 0) {
101
			return null;
102 5
		}
103
104
		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 1
	 * analysis, false otherwise
125
	 */
126 1
	public function isUnderNoDetection(Node $node): bool {
127 1
		// 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
				return true;
133 1
			$parentNode = $parentNode->getParent();
134
		}
135
		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 3
	 * analysis, false otherwise
145
	 */
146 3
	public function getDescendantDetection(Folder $folder): bool {
147 2
		try {
148
			if ($folder->nodeExists(FileService::NOMEDIA_FILE) ||
149 3
			    $folder->nodeExists(FileService::NOIMAGE_FILE)) {
150
				return false;
151
			}
152
153
			if (!$folder->nodeExists(FileService::FACERECOGNITION_SETTINGS_FILE)) {
154
				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 3
				return true;
160
161
			$settings = json_decode($file->getContent(), true);
162
			if ($settings === null || !array_key_exists('detection', $settings)) {
163
				return true;
164
			}
165 3
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 28
		$file->putContent(json_encode($settings));
199 28
200 28
		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
		        ($node->getStorage()->instanceOfStorage(IHomeStorage::class)));
209
	}
210
211
	/**
212
	 * Returns if the file is inside a shared mount storage.
213 28
	 */
214 28
	public function isSharedFile(Node $node): bool {
215
		return ($node->getMountPoint()->getMountType() === 'shared');
216
	}
217
218
	/**
219
	 * Returns if the file is inside a external mount storage.
220 28
	 */
221 28
	public function isExternalFile(Node $node): bool {
222
		return ($node->getMountPoint()->getMountType() === 'external');
223
	}
224
225
	/**
226
	 * Returns if the file is inside a group folder storage.
227 28
	 */
228 28
	public function isGroupFile(Node $node): bool {
229 28
		return ($node->getMountPoint()->getMountType() === 'group');
230 28
	}
231
232 28
	/**
233
	 * Returns if the Node is allowed based on preferences.
234 28
	 */
235
	public function isAllowedNode(Node $node): bool {
236
		if ($this->isUserFile($node)) {
237 28
			return true;
238
		} else if ($this->isSharedFile($node)) {
239
			return $this->settingsService->getHandleSharedFiles();
240
		} else if ($this->isExternalFile($node)) {
241
			return $this->settingsService->getHandleExternalFiles();
242
		} else if ($this->isGroupFile($node)) {
243
			return $this->settingsService->getHandleGroupFiles();
244
		}
245
		return false;
246
	}
247 4
248 4
	/**
249 4
	 * 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
	public function getLocalFile(File $file, int $maxSize = null): ?string {
256
		$useTempFile = $file->isEncrypted() || !$file->getStorage()->isLocal();
257
		if ($useTempFile) {
258
			$absPath = $this->tempManager->getTemporaryFile();
259
260 4
			$content = $file->fopen('r');
261 4
			if ($maxSize !== null) {
262
				$content = stream_get_contents($content, $maxSize);
263
			}
264
			file_put_contents($absPath, $content);
265
266
			return $absPath;
267
		} else {
268
			$localPath = $file->getStorage()->getLocalFile($file->getInternalPath());
269
			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 10
	/**
274 10
	 * Return all images from a given folder.
275 10
	 *
276 8
	 * TODO: It is inefficient since it copies the array recursively.
277
	 *
278
	 * @param Folder $folder Folder to get images from
279 8
	 * @return array List of all images and folders to continue recursive crawling
280 3
	 */
281
	public function getPicturesFromFolder(Folder $folder, $results = array()) {
282 8
		$nodes = $folder->getDirectoryListing();
283 8
		foreach ($nodes as $node) {
284 7
			if (!$this->isAllowedNode($node)) {
285
				continue;
286
			}
287
			if ($node instanceof Folder && $this->getDescendantDetection($node)) {
288 10
				$results = $this->getPicturesFromFolder($node, $results);
289
			}
290
			else if ($node instanceof File) {
291
				if ($this->settingsService->isAllowedMimetype($node->getMimeType())) {
292
					$results[] = $node;
293
				}
294
			}
295
		}
296
		return $results;
297
	}
298
299
300 1
	/**
301 1
	 * Download a file in a temporary folder
302 1
	 *
303
	 * @param string $fileUrl url to download.
304 1
	 * @return string temp file downloaded.
305 1
	 *
306
	 * @throws \Exception
307
	 */
308
	public function downloaldFile(string $fileUrl): string {
309 1
		$tempFolder = $this->tempManager->getTemporaryFolder('/facerecognition/');
310 1
		$tempFile = $tempFolder . basename($fileUrl);
311
312
		$fp = fopen($tempFile, 'w+');
313
		if ($fp === false) {
314 1
			throw new \Exception('Could not open the file to write: ' . $tempFile);
315 1
		}
316 1
317 1
		$ch = curl_init($fileUrl);
318 1
		if ($ch === false) {
319 1
			throw new \Exception('Curl error: unable to initialize curl');
320
		}
321
322 1
		curl_setopt_array($ch, [
323
			CURLOPT_FILE => $fp,
324
			CURLOPT_FOLLOWLOCATION => true,
325
			CURLOPT_SSL_VERIFYPEER => false,
326 1
			CURLOPT_SSL_VERIFYHOST => 0,
327 1
			CURLOPT_USERAGENT => 'Nextcloud Facerecognition Service',
328
		]);
329
330
		if (curl_exec($ch) === false) {
331
			throw new \Exception('Curl error: ' . curl_error($ch));
332
		}
333
334
		$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
335
		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 1
			if(!empty($curlErrorMessage)) {
356 1
				$message .= ' - curl error message: ' . $curlErrorMessage;
357
			}
358 1
			$message .= ' - URL: ' . htmlentities($fileUrl);
359
360
			throw new \Exception($message);
361
		}
362
363
		curl_close($ch);
364
		fclose($fp);
365
366
		return $tempFile;
367
	}
368
369 1
	/**
370 1
	 * Uncompressing the file with the bzip2-extension
371
	 *
372
	 * @param string $in
373 1
	 * @param string $out
374 1
	 *
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 1
381
		if ((!file_exists($outputFile) && !is_writeable(dirname($outputFile))) ||
382
		    (file_exists($outputFile) && !is_writable($outputFile)))
383 1
			throw new \Exception('The file ' . $outputFile . ' exists or is not writable');
384 1
385
		$in_file = bzopen ($inputFile, "r");
386 1
		$out_file = fopen ($outputFile, "w");
387
388
		if ($out_file === false)
389 1
			throw new \Exception('Could not open the file to write: ' . $outputFile);
390
391
		while ($buffer = bzread ($in_file, 4096)) {
392 1
			if($buffer === false)
393 1
				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
			fwrite ($out_file, $buffer, 4096);
398
		}
399
400
		bzclose ($in_file);
401
		fclose ($out_file);
402
	}
403
404
	/**
405
	 * Create a temporary file and return the path
406 4
	 */
407 4
	public function getTemporaryFile(string $postFix = ''): string {
408 4
		return $this->tempManager->getTemporaryFile($postFix);
409
	}
410
411
	/**
412
	 * Remove any temporary file from the service.
413
	 */
414
	public function clean() {
415
		$this->tempManager->clean();
416
	}
417
418
}
419