Passed
Pull Request — master (#246)
by Matias
07:19 queued 04:11
created

FileService::bunzip2()   B

Complexity

Conditions 11
Paths 7

Size

Total Lines 25
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 13.594

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 11
eloc 17
c 1
b 0
f 0
nc 7
nop 2
dl 0
loc 25
ccs 13
cts 18
cp 0.7221
crap 13.594
rs 7.3166

How to fix   Complexity   

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
				$settings = json_decode($file->getContent(), true);
0 ignored issues
show
Bug introduced by
The method getContent() does not exist on OCP\Files\Node. It seems like you code against a sub-type of OCP\Files\Node such as OCP\Files\File. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

151
				$settings = json_decode($file->/** @scrutinizer ignore-call */ getContent(), true);
Loading history...
152
				if ($settings === null || !array_key_exists('detection', $settings))
153
					return true;
154
155
				if ($settings['detection'] === 'off')
156 3
					return false;
157
			}
158
		} catch (StorageNotAvailableException $e) {
159
			return false;
160
		}
161
162 3
		return true;
163
	}
164
165
	/**
166
	 * Set this folder to enable or disable the analysis using the .facerecognition.json file.
167
	 *
168
	 * @param Folder $folder Folder to enable/disable for
169
	 * @return bool true if the change is done. False if failed.
170
	 */
171
	public function setDescendantDetection(Folder $folder, bool $detection): bool {
172
		if ($folder->nodeExists(FileService::FACERECOGNITION_SETTINGS_FILE)) {
173
			$file = $folder->get(FileService::FACERECOGNITION_SETTINGS_FILE);
174
			$settings = json_decode($file->getContent(), true);
175
			if ($settings === null) {
176
				// Invalid json.
177
				return false;
178
			}
179
		}
180
		else {
181
			$file = $folder->newFile(FileService::FACERECOGNITION_SETTINGS_FILE);
182
			$settings = array();
183
		}
184
185
		$settings['detection'] = $detection ? "on" : "off";
186
		$file->putContent(json_encode($settings));
0 ignored issues
show
Bug introduced by
The method putContent() does not exist on OCP\Files\Node. It seems like you code against a sub-type of OCP\Files\Node such as OCP\Files\File. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

186
		$file->/** @scrutinizer ignore-call */ 
187
         putContent(json_encode($settings));
Loading history...
187
188
		return true;
189
	}
190
191
	/**
192
	 * Returns if the file is inside a native home storage.
193
	 */
194 28
	public function isUserFile(Node $node): bool {
195 28
		return (($node->getMountPoint()->getMountType() === '') &&
196 28
		        ($node->getStorage()->instanceOfStorage(IHomeStorage::class)));
197
	}
198
199
	/**
200
	 * Returns if the file is inside a shared mount storage.
201
	 */
202 28
	public function isSharedFile(Node $node): bool {
203 28
		return ($node->getMountPoint()->getMountType() === 'shared');
204
	}
205
206
	/**
207
	 * Returns if the file is inside a external mount storage.
208
	 */
209 28
	public function isExternalFile(Node $node): bool {
210 28
		return ($node->getMountPoint()->getMountType() === 'external');
211
	}
212
213
	/**
214
	 * Returns if the Node is allowed based on preferences.
215
	 */
216 28
	public function isAllowedNode(Node $node): bool {
217 28
		if ($this->isUserFile($node)) {
218 28
			return true;
219 28
		} else if ($this->isSharedFile($node)) {
220
			return $this->settingsService->getHandleSharedFiles();
221 28
		} else if ($this->isExternalFile($node)) {
222
			return $this->settingsService->getHandleExternalFiles();
223
		}
224 28
		return false;
225
	}
226
227
	/**
228
	 * Get a path to either the local file or temporary file
229
	 *
230
	 * @param File $file
231
	 * @param int $maxSize maximum size for temporary files
232
	 * @return string
233
	 */
234 4
	public function getLocalFile(File $file, int $maxSize = null): string {
235 4
		$useTempFile = $file->isEncrypted() || !$file->getStorage()->isLocal();
236 4
		if ($useTempFile) {
237
			$absPath = $this->tempManager->getTemporaryFile();
238
239
			$content = $file->fopen('r');
240
			if ($maxSize !== null) {
241
				$content = stream_get_contents($content, $maxSize);
242
			}
243
			file_put_contents($absPath, $content);
244
245
			return $absPath;
246
		} else {
247 4
			return $file->getStorage()->getLocalFile($file->getInternalPath());
0 ignored issues
show
Bug Best Practice introduced by
The expression return $file->getStorage...ile->getInternalPath()) could return the type false which is incompatible with the type-hinted return string. Consider adding an additional type-check to rule them out.
Loading history...
248
		}
249
	}
250
251
	/**
252
	 * Return all images from a given folder.
253
	 *
254
	 * TODO: It is inefficient since it copies the array recursively.
255
	 *
256
	 * @param Folder $folder Folder to get images from
257
	 * @return array List of all images and folders to continue recursive crawling
258
	 */
259 10
	public function getPicturesFromFolder(Folder $folder, $results = array()) {
260 10
		$nodes = $folder->getDirectoryListing();
261 10
		foreach ($nodes as $node) {
262 8
			if (!$this->isAllowedNode($node)) {
263
				continue;
264
			}
265 8
			if ($node instanceof Folder && $this->getDescendantDetection($node)) {
266 3
				$results = $this->getPicturesFromFolder($node, $results);
267
			}
268 8
			else if ($node instanceof File) {
269 8
				if ($this->settingsService->isAllowedMimetype($node->getMimeType())) {
270 8
					$results[] = $node;
271
				}
272
			}
273
		}
274 10
		return $results;
275
	}
276
277
278
	/**
279
	 * Download a file in a temporary folder
280
	 *
281
	 * @param string $fileUrl url to download.
282
	 * @return string temp file downloaded.
283
	 *
284
	 * @throws \Exception
285
	 */
286 1
	public function downloaldFile(string $fileUrl): string {
287 1
		$tempFolder = $this->tempManager->getTemporaryFolder('/facerecognition/');
288 1
		$tempFile = $tempFolder . basename($fileUrl);
289
290 1
		$fp = fopen($tempFile, 'w+');
291 1
		if ($fp === false) {
292
			throw new \Exception('Could not open the file to write: ' . $tempFile);
293
		}
294
295 1
		$ch = curl_init($fileUrl);
296 1
		if ($ch === false) {
297
			throw new \Exception('Curl error: ' . curl_error($ch));
0 ignored issues
show
Bug introduced by
It seems like $ch can also be of type false; however, parameter $ch of curl_error() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

297
			throw new \Exception('Curl error: ' . curl_error(/** @scrutinizer ignore-type */ $ch));
Loading history...
298
		}
299
300 1
		curl_setopt_array($ch, [
301 1
			CURLOPT_FILE => $fp,
302 1
			CURLOPT_FOLLOWLOCATION => true,
303 1
			CURLOPT_SSL_VERIFYPEER => false,
304 1
			CURLOPT_SSL_VERIFYHOST => 0,
305 1
			CURLOPT_USERAGENT => 'Nextcloud Facerecognition Service',
306
		]);
307
308 1
		if (curl_exec($ch) === false) {
309
			throw new \Exception('Curl error: ' . curl_error($ch));
310
		}
311
312 1
		$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
313 1
		if ($httpCode !== 200) {
314
			$statusCodes = [
315
				400 => 'Bad request',
316
				401 => 'Unauthorized',
317
				403 => 'Forbidden',
318
				404 => 'Not Found',
319
				500 => 'Internal Server Error',
320
				502 => 'Bad Gateway',
321
				503 => 'Service Unavailable',
322
				504 => 'Gateway Timeout',
323
			];
324
325
			$message = 'Download failed';
326
			if(isset($statusCodes[$httpCode])) {
327
				$message .= ' - ' . $statusCodes[$httpCode] . ' (HTTP ' . $httpCode . ')';
328
			} else {
329
				$message .= ' - HTTP status code: ' . $httpCode;
330
			}
331
332
			$curlErrorMessage = curl_error($ch);
333
			if(!empty($curlErrorMessage)) {
334
				$message .= ' - curl error message: ' . $curlErrorMessage;
335
			}
336
			$message .= ' - URL: ' . htmlentities($fileUrl);
337
338
			throw new \Exception($message);
339
		}
340
341 1
		curl_close($ch);
342 1
		fclose($fp);
343
344 1
		return $tempFile;
345
	}
346
347
	/**
348
	 * Uncompressing the file with the bzip2-extension
349
	 *
350
	 * @param string $in
351
	 * @param string $out
352
	 *
353
	 * @throws \Exception
354
	 */
355 1
	public function bunzip2(string $inputFile, string $outputFile) {
356 1
		if (!file_exists ($inputFile) || !is_readable ($inputFile))
357
			throw new \Exception('The file ' . $inputFile . ' not exists or is not readable');
358
359 1
		if ((!file_exists($outputFile) && !is_writeable(dirname($outputFile))) ||
360 1
		    (file_exists($outputFile) && !is_writable($outputFile)))
361
			throw new \Exception('The file ' . $outputFile . ' exists or is not writable');
362
363 1
		$in_file = bzopen ($inputFile, "r");
364 1
		$out_file = fopen ($outputFile, "w");
365
366 1
		if ($out_file === false)
367
			throw new \Exception('Could not open the file to write: ' . $outputFile);
368
369 1
		while ($buffer = bzread ($in_file, 4096)) {
370 1
			if($buffer === false)
371
				throw new \Exception('Read problem: ' . bzerrstr($in_file));
372 1
			if(bzerrno($in_file) !== 0)
373
				throw new \Exception('Compression problem: '. bzerrstr($in_file));
374
375 1
			fwrite ($out_file, $buffer, 4096);
376
		}
377
378 1
		bzclose ($in_file);
379 1
		fclose ($out_file);
380 1
	}
381
382
	/**
383
	 * Create a temporary file and return the path
384
	 */
385
	public function getTemporaryFile(string $postFix = ''): string {
386
		return $this->tempManager->getTemporaryFile($postFix);
387
	}
388
389
	/**
390
	 * Remove any temporary file from the service.
391
	 */
392 4
	public function clean() {
393 4
		$this->tempManager->clean();
394 4
	}
395
396
}
397