Passed
Pull Request — master (#232)
by Matias
05:40 queued 04:10
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
37
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...
38
39
use OCA\FaceRecognition\Helper\Requirements;
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 3
		if ($folder->nodeExists(FileService::NOMEDIA_FILE)) {
146 2
			return false;
147
		}
148 3
		if ($folder->nodeExists(FileService::FACERECOGNITION_SETTINGS_FILE)) {
149
			$file = $folder->get(FileService::FACERECOGNITION_SETTINGS_FILE);
150
			$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

150
			$settings = json_decode($file->/** @scrutinizer ignore-call */ getContent(), true);
Loading history...
151
			if ($settings === null || !array_key_exists('detection', $settings))
152
				return true;
153
154
			if ($settings['detection'] === 'off')
155
				return false;
156
		}
157
158 3
		return true;
159
	}
160
161
	/**
162
	 * Set this folder to enable or disable the analysis using the .facerecognition.json file.
163
	 *
164
	 * @param Folder $folder Folder to enable/disable for
165
	 * @return bool true if the change is done. False if failed.
166
	 */
167
	public function setDescendantDetection(Folder $folder, bool $detection): bool {
168
		if ($folder->nodeExists(FileService::FACERECOGNITION_SETTINGS_FILE)) {
169
			$file = $folder->get(FileService::FACERECOGNITION_SETTINGS_FILE);
170
			$settings = json_decode($file->getContent(), true);
171
			if ($settings === null) {
172
				// Invalid json.
173
				return false;
174
			}
175
		}
176
		else {
177
			$file = $folder->newFile(FileService::FACERECOGNITION_SETTINGS_FILE);
178
			$settings = array();
179
		}
180
181
		$settings['detection'] = $detection ? "on" : "off";
182
		$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

182
		$file->/** @scrutinizer ignore-call */ 
183
         putContent(json_encode($settings));
Loading history...
183
184
		return true;
185
	}
186
187
	/**
188
	 * Returns if the file is inside a shared storage.
189
	 */
190 28
	public function isSharedFile(Node $node): bool {
191 28
		return $node->getStorage()->instanceOfStorage(SharingExternalStorage::class);
192
	}
193
194
	/**
195
	 * Returns if the file is inside HomeStorage.
196
	 */
197 28
	public function isUserFile(Node $node): bool {
198 28
		return $node->getStorage()->instanceOfStorage(IHomeStorage::class);
199
	}
200
201
	/**
202
	 * Returns if the Node is allowed based on preferences.
203
	 */
204 28
	public function isAllowedNode(Node $node): bool {
205 28
		if ($this->isUserFile($node)) {
206 28
			return true;
207 28
		} else if ($this->isSharedFile($node)) {
208
			return $this->settingsService->getHandleSharedFiles();
209
		}
210 28
		return false;
211
	}
212
213
	/**
214
	 * Get a path to either the local file or temporary file
215
	 *
216
	 * @param File $file
217
	 * @param int $maxSize maximum size for temporary files
218
	 * @return string
219
	 */
220 4
	public function getLocalFile(File $file, int $maxSize = null): string {
221 4
		$useTempFile = $file->isEncrypted() || !$file->getStorage()->isLocal();
222 4
		if ($useTempFile) {
223
			$absPath = $this->tempManager->getTemporaryFile();
224
225
			$content = $file->fopen('r');
226
			if ($maxSize !== null) {
227
				$content = stream_get_contents($content, $maxSize);
228
			}
229
			file_put_contents($absPath, $content);
230
231
			return $absPath;
232
		} else {
233 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...
234
		}
235
	}
236
237
	/**
238
	 * Return all images from a given folder.
239
	 *
240
	 * TODO: It is inefficient since it copies the array recursively.
241
	 *
242
	 * @param Folder $folder Folder to get images from
243
	 * @return array List of all images and folders to continue recursive crawling
244
	 */
245 10
	public function getPicturesFromFolder(Folder $folder, $results = array()) {
246 10
		$nodes = $folder->getDirectoryListing();
247 10
		foreach ($nodes as $node) {
248 8
			if (!$this->isAllowedNode($node)) {
249
				continue;
250
			}
251 8
			if ($node instanceof Folder && $this->getDescendantDetection($node)) {
252 3
				$results = $this->getPicturesFromFolder($node, $results);
253
			}
254 8
			else if ($node instanceof File) {
255 8
				if (Requirements::isImageTypeSupported($node->getMimeType())) {
256 8
					$results[] = $node;
257
				}
258
			}
259
		}
260 10
		return $results;
261
	}
262
263
264
	/**
265
	 * Download a file in a temporary folder
266
	 *
267
	 * @param string $fileUrl url to download.
268
	 * @return string temp file downloaded.
269
	 *
270
	 * @throws \Exception
271
	 */
272 1
	public function downloaldFile(string $fileUrl): string {
273 1
		$tempFolder = $this->tempManager->getTemporaryFolder('/facerecognition/');
274 1
		$tempFile = $tempFolder . basename($fileUrl);
275
276 1
		$fp = fopen($tempFile, 'w+');
277 1
		if ($fp === false) {
278
			throw new \Exception('Could not open the file to write: ' . $tempFile);
279
		}
280
281 1
		$ch = curl_init($fileUrl);
282 1
		if ($ch === false) {
283
			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

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