Passed
Pull Request — master (#293)
by Matias
06:20 queued 04:27
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 Node is allowed based on preferences.
219
	 */
220 28
	public function isAllowedNode(Node $node): bool {
221 28
		if ($this->isUserFile($node)) {
222 28
			return true;
223 28
		} else if ($this->isSharedFile($node)) {
224
			return $this->settingsService->getHandleSharedFiles();
225 28
		} else if ($this->isExternalFile($node)) {
226
			return $this->settingsService->getHandleExternalFiles();
227
		}
228 28
		return false;
229
	}
230
231
	/**
232
	 * Get a path to either the local file or temporary file
233
	 *
234
	 * @param File $file
235
	 * @param int $maxSize maximum size for temporary files
236
	 * @return string|null
237
	 */
238 4
	public function getLocalFile(File $file, int $maxSize = null): ?string {
239 4
		$useTempFile = $file->isEncrypted() || !$file->getStorage()->isLocal();
240 4
		if ($useTempFile) {
241
			$absPath = $this->tempManager->getTemporaryFile();
242
243
			$content = $file->fopen('r');
244
			if ($maxSize !== null) {
245
				$content = stream_get_contents($content, $maxSize);
246
			}
247
			file_put_contents($absPath, $content);
248
249
			return $absPath;
250
		} else {
251 4
			$localPath = $file->getStorage()->getLocalFile($file->getInternalPath());
252 4
			return ($localPath !== false) ? $localPath : null;
253
		}
254
	}
255
256
	/**
257
	 * Return all images from a given folder.
258
	 *
259
	 * TODO: It is inefficient since it copies the array recursively.
260
	 *
261
	 * @param Folder $folder Folder to get images from
262
	 * @return array List of all images and folders to continue recursive crawling
263
	 */
264 10
	public function getPicturesFromFolder(Folder $folder, $results = array()) {
265 10
		$nodes = $folder->getDirectoryListing();
266 10
		foreach ($nodes as $node) {
267 8
			if (!$this->isAllowedNode($node)) {
268
				continue;
269
			}
270 8
			if ($node instanceof Folder && $this->getDescendantDetection($node)) {
271 3
				$results = $this->getPicturesFromFolder($node, $results);
272
			}
273 8
			else if ($node instanceof File) {
274 8
				if ($this->settingsService->isAllowedMimetype($node->getMimeType())) {
275 7
					$results[] = $node;
276
				}
277
			}
278
		}
279 10
		return $results;
280
	}
281
282
283
	/**
284
	 * Download a file in a temporary folder
285
	 *
286
	 * @param string $fileUrl url to download.
287
	 * @return string temp file downloaded.
288
	 *
289
	 * @throws \Exception
290
	 */
291 1
	public function downloaldFile(string $fileUrl): string {
292 1
		$tempFolder = $this->tempManager->getTemporaryFolder('/facerecognition/');
293 1
		$tempFile = $tempFolder . basename($fileUrl);
294
295 1
		$fp = fopen($tempFile, 'w+');
296 1
		if ($fp === false) {
297
			throw new \Exception('Could not open the file to write: ' . $tempFile);
298
		}
299
300 1
		$ch = curl_init($fileUrl);
301 1
		if ($ch === false) {
302
			throw new \Exception('Curl error: unable to initialize curl');
303
		}
304
305 1
		curl_setopt_array($ch, [
306 1
			CURLOPT_FILE => $fp,
307 1
			CURLOPT_FOLLOWLOCATION => true,
308 1
			CURLOPT_SSL_VERIFYPEER => false,
309 1
			CURLOPT_SSL_VERIFYHOST => 0,
310 1
			CURLOPT_USERAGENT => 'Nextcloud Facerecognition Service',
311
		]);
312
313 1
		if (curl_exec($ch) === false) {
314
			throw new \Exception('Curl error: ' . curl_error($ch));
315
		}
316
317 1
		$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
318 1
		if ($httpCode !== 200) {
319
			$statusCodes = [
320
				400 => 'Bad request',
321
				401 => 'Unauthorized',
322
				403 => 'Forbidden',
323
				404 => 'Not Found',
324
				500 => 'Internal Server Error',
325
				502 => 'Bad Gateway',
326
				503 => 'Service Unavailable',
327
				504 => 'Gateway Timeout',
328
			];
329
330
			$message = 'Download failed';
331
			if(isset($statusCodes[$httpCode])) {
332
				$message .= ' - ' . $statusCodes[$httpCode] . ' (HTTP ' . $httpCode . ')';
333
			} else {
334
				$message .= ' - HTTP status code: ' . $httpCode;
335
			}
336
337
			$curlErrorMessage = curl_error($ch);
338
			if(!empty($curlErrorMessage)) {
339
				$message .= ' - curl error message: ' . $curlErrorMessage;
340
			}
341
			$message .= ' - URL: ' . htmlentities($fileUrl);
342
343
			throw new \Exception($message);
344
		}
345
346 1
		curl_close($ch);
347 1
		fclose($fp);
348
349 1
		return $tempFile;
350
	}
351
352
	/**
353
	 * Uncompressing the file with the bzip2-extension
354
	 *
355
	 * @param string $in
356
	 * @param string $out
357
	 *
358
	 * @throws \Exception
359
	 */
360 1
	public function bunzip2(string $inputFile, string $outputFile) {
361 1
		if (!file_exists ($inputFile) || !is_readable ($inputFile))
362
			throw new \Exception('The file ' . $inputFile . ' not exists or is not readable');
363
364 1
		if ((!file_exists($outputFile) && !is_writeable(dirname($outputFile))) ||
365 1
		    (file_exists($outputFile) && !is_writable($outputFile)))
366
			throw new \Exception('The file ' . $outputFile . ' exists or is not writable');
367
368 1
		$in_file = bzopen ($inputFile, "r");
369 1
		$out_file = fopen ($outputFile, "w");
370
371 1
		if ($out_file === false)
372
			throw new \Exception('Could not open the file to write: ' . $outputFile);
373
374 1
		while ($buffer = bzread ($in_file, 4096)) {
375 1
			if($buffer === false)
376
				throw new \Exception('Read problem: ' . bzerrstr($in_file));
377 1
			if(bzerrno($in_file) !== 0)
378
				throw new \Exception('Compression problem: '. bzerrstr($in_file));
379
380 1
			fwrite ($out_file, $buffer, 4096);
381
		}
382
383 1
		bzclose ($in_file);
384 1
		fclose ($out_file);
385 1
	}
386
387
	/**
388
	 * Create a temporary file and return the path
389
	 */
390
	public function getTemporaryFile(string $postFix = ''): string {
391
		return $this->tempManager->getTemporaryFile($postFix);
392
	}
393
394
	/**
395
	 * Remove any temporary file from the service.
396
	 */
397 4
	public function clean() {
398 4
		$this->tempManager->clean();
399 4
	}
400
401
}
402