Completed
Pull Request — master (#30092)
by Juan Pablo
12:49
created

SMB::rename()   C

Complexity

Conditions 13
Paths 30

Size

Total Lines 52

Duplication

Lines 18
Ratio 34.62 %

Importance

Changes 0
Metric Value
cc 13
nc 30
nop 2
dl 18
loc 52
rs 6.6166
c 0
b 0
f 0

How to fix   Long Method    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
/**
3
 * @author Arthur Schiwon <[email protected]>
4
 * @author Jesús Macias <[email protected]>
5
 * @author Jörn Friedrich Dreyer <[email protected]>
6
 * @author Juan Pablo Villafañez <[email protected]>
7
 * @author Michael Gapczynski <[email protected]>
8
 * @author Morris Jobke <[email protected]>
9
 * @author Philipp Kapfer <[email protected]>
10
 * @author Robin Appelman <[email protected]>
11
 * @author Robin McCorkell <[email protected]>
12
 * @author Thomas Müller <[email protected]>
13
 * @author Vincent Petry <[email protected]>
14
 *
15
 * @copyright Copyright (c) 2018, ownCloud GmbH
16
 * @license AGPL-3.0
17
 *
18
 * This code is free software: you can redistribute it and/or modify
19
 * it under the terms of the GNU Affero General Public License, version 3,
20
 * as published by the Free Software Foundation.
21
 *
22
 * This program is distributed in the hope that it will be useful,
23
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
24
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
25
 * GNU Affero General Public License for more details.
26
 *
27
 * You should have received a copy of the GNU Affero General Public License, version 3,
28
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
29
 *
30
 */
31
32
namespace OCA\Files_External\Lib\Storage;
33
34
use Icewind\SMB\Exception\AlreadyExistsException;
35
use Icewind\SMB\Exception\ConnectException;
36
use Icewind\SMB\Exception\Exception;
37
use Icewind\SMB\Exception\ForbiddenException;
38
use Icewind\SMB\Exception\NotFoundException;
39
use Icewind\SMB\BasicAuth;
40
use Icewind\SMB\IFileInfo;
41
use Icewind\SMB\Native\NativeServer;
42
use Icewind\SMB\Wrapped\FileInfo;
43
use Icewind\SMB\ServerFactory;
44
use Icewind\SMB\System;
45
use Icewind\SMB\IShare;
46
use Icewind\Streams\CallbackWrapper;
47
use Icewind\Streams\IteratorDirectory;
48
use OC\Cache\CappedMemoryCache;
49
use OC\Files\Filesystem;
50
use OCP\Files\Cache\ICache;
51
use OCP\Files\StorageNotAvailableException;
52
use OCP\Util;
53
54
class SMB extends \OCP\Files\Storage\StorageAdapter {
55
	/**
56
	 * @var \Icewind\SMB\IServer
57
	 */
58
	protected $server;
59
60
	/**
61
	 * @var \Icewind\SMB\IShare
62
	 */
63
	protected $share;
64
65
	/**
66
	 * @var string
67
	 */
68
	protected $root;
69
70
	/**
71
	 * @var ICache
72
	 */
73
	protected $statCache;
74
75
	public function __construct($params) {
76
		$loggedParams = $params;
77
		// remove password from log if it is set
78
		if (!empty($loggedParams['password'])) {
79
			$loggedParams['password'] = '***removed***';
80
		}
81
		$this->log('enter: '.__FUNCTION__.'('.\json_encode($loggedParams).')');
82
83
		if (isset($params['host'], $params['user'], $params['password'], $params['share'])) {
84
			$auth = new BasicAuth($params['user'], '', $params['password']);
85
			$serverFactory = new ServerFactory();
86
			$this->server = $serverFactory->createServer($params['host'], $auth);
87
			$this->share = $this->server->getShare(\trim($params['share'], '/'));
88
89
			$shareClass = \get_class($this->share);
90
			$this->log("using $shareClass for the connection");
91
92
			$this->root = isset($params['root']) ? $params['root'] : '/';
93 View Code Duplication
			if (!$this->root || $this->root[0] != '/') {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
94
				$this->root = '/' . $this->root;
95
			}
96
			if (\substr($this->root, -1, 1) != '/') {
97
				$this->root .= '/';
98
			}
99
		} else {
100
			$ex = new \Exception('Invalid configuration');
101
			$this->leave(__FUNCTION__, $ex);
102
			throw $ex;
103
		}
104
		$this->statCache = new CappedMemoryCache();
0 ignored issues
show
Documentation Bug introduced by
It seems like new \OC\Cache\CappedMemoryCache() of type object<OC\Cache\CappedMemoryCache> is incompatible with the declared type object<OCP\Files\Cache\ICache> of property $statCache.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
105
		$this->log('leave: '.__FUNCTION__.', getId:'.$this->getId());
106
	}
107
108
	/**
109
	 * @return string
110
	 */
111
	public function getId() {
112
		// FIXME: double slash to keep compatible with the old storage ids,
113
		// failure to do so will lead to creation of a new storage id and
114
		// loss of shares from the storage
115
		return 'smb::' . $this->server->getAuth()->getUsername() . '@' . $this->server->getHost() . '//' . $this->share->getName() . '/' . $this->root;
116
	}
117
118
	/**
119
	 * @param string $path
120
	 * @return string
121
	 */
122
	protected function buildPath($path) {
123
		$this->log('enter: '.__FUNCTION__."($path)");
124
		$result = Filesystem::normalizePath($this->root . '/' . $path, true, false, true);
125
		return $this->leave(__FUNCTION__, $result);
126
	}
127
128
	/**
129
	 * @param string $path
130
	 * @return \Icewind\SMB\IFileInfo
131
	 * @throws StorageNotAvailableException
132
	 * @throws ForbiddenException
133
	 * @throws NotFoundException
134
	 */
135
	protected function getFileInfo($path) {
136
		$this->log('enter: '.__FUNCTION__."($path)");
137
		$path = $this->buildPath($path);
138
		if (!isset($this->statCache[$path])) {
139
			try {
140
				$this->log("stat fetching '$path'");
141
				try {
142
					$this->statCache[$path] = $this->share->stat($path);
143
				} catch (NotFoundException $e) {
144
					if ($this->share instanceof IShare) {
145
						// smbclient may have problems with the allinfo cmd
146
						$this->log("stat for '$path' failed, trying to read parent dir");
147
						$infos = $this->share->dir(\dirname($path));
148
						foreach ($infos as $fileInfo) {
149
							if ($fileInfo->getName() === \basename($path)) {
150
								$this->statCache[$path] = $fileInfo;
151
								break;
152
							}
153
						}
154
						if (empty($this->statCache[$path])) {
155
							$this->leave(__FUNCTION__, $e);
156
							throw $e;
157
						}
158
					} else {
159
						// trust the results of libsmb
160
						$this->leave(__FUNCTION__, $e);
161
						throw $e;
162
					}
163
				}
164
				if ($this->isRootDir($path) && $this->statCache[$path]->isHidden()) {
165
					$this->log("unhiding stat for '$path'");
166
					// make root never hidden, may happen when accessing a shared drive (mode is 22, archived and readonly - neither is true ... whatever)
167
					if ($this->statCache[$path]->isReadOnly()) {
168
						$mode = IFileInfo::MODE_DIRECTORY & IFileInfo::MODE_READONLY;
169
					} else {
170
						$mode = IFileInfo::MODE_DIRECTORY;
171
					}
172
					$this->statCache[$path] = new FileInfo($path, '', 0, $this->statCache[$path]->getMTime(), $mode);
173
				}
174
			} catch (ConnectException $e) {
175
				$ex = new StorageNotAvailableException(
176
						$e->getMessage(), $e->getCode(), $e);
177
				$this->leave(__FUNCTION__, $ex);
178
				throw $ex;
179
			} catch (ForbiddenException $e) {
180
				if ($this->remoteIsShare() && $this->isRootDir($path)) { //mtime may not work for share root
181
					$this->log("faking stat for forbidden '$path'");
182
					$this->statCache[$path] = new FileInfo($path, '', 0, $this->shareMTime(), IFileInfo::MODE_DIRECTORY);
183
				} else {
184
					$this->leave(__FUNCTION__, $e);
185
					throw $e;
186
				}
187
			}
188
		} else {
189
			$this->log("stat cache hit for '$path'");
190
		}
191
		$result = $this->statCache[$path];
192
		return $this->leave(__FUNCTION__, $result);
193
	}
194
195
	/**
196
	 * @param string $path
197
	 * @return \Icewind\SMB\IFileInfo[]
198
	 * @throws StorageNotAvailableException
199
	 */
200
	protected function getFolderContents($path) {
201
		$this->log('enter: '.__FUNCTION__."($path)");
202
		try {
203
			$path = $this->buildPath($path);
204
			$result = [];
205
			$children = $this->share->dir($path);
206
			foreach ($children as $fileInfo) {
207
				// check if the file is readable before adding it to the list
208
				// can't use "isReadable" function here, use smb internals instead
209
				try {
210
					if ($fileInfo->isHidden()) {
211
						$this->log("{$fileInfo->getName()} isn't readable, skipping", Util::DEBUG);
212
					} else {
213
						$result[] = $fileInfo;
214
						//remember entry so we can answer file_exists and filetype without a full stat
215
						$this->statCache[$path . '/' . $fileInfo->getName()] = $fileInfo;
216
					}
217
				} catch (NotFoundException $e) {
218
					$this->swallow(__FUNCTION__, $e);
219
				}
220
			}
221
		} catch (ConnectException $e) {
222
			$ex = new StorageNotAvailableException(
223
				$e->getMessage(), $e->getCode(), $e);
224
			$this->leave(__FUNCTION__, $ex);
225
			throw $ex;
226
		}
227
		return $this->leave(__FUNCTION__, $result);
228
	}
229
230
	/**
231
	 * @param \Icewind\SMB\IFileInfo $info
232
	 * @return array
233
	 */
234
	protected function formatInfo($info) {
235
		$result = [
236
			'size' => $info->getSize(),
237
			'mtime' => $info->getMTime(),
238
		];
239
		if ($info->isDirectory()) {
240
			$result['type'] = 'dir';
241
		} else {
242
			$result['type'] = 'file';
243
		}
244
		return $result;
245
	}
246
247
	/**
248
	 * Rename the files. If the source or the target is the root, the rename won't happen.
249
	 *
250
	 * @param string $source the old name of the path
251
	 * @param string $target the new name of the path
252
	 * @return bool true if the rename is successful, false otherwise
253
	 */
254
	public function rename($source, $target) {
255
		$this->log("enter: rename('$source', '$target')", Util::DEBUG);
256
257
		if ($this->isRootDir($source) || $this->isRootDir($target)) {
258
			$this->log("refusing to rename \"$source\" to \"$target\"");
259
			return $this->leave(__FUNCTION__, false);
260
		}
261
262
		try {
263
			$result = $this->share->rename($this->root . $source, $this->root . $target);
264
			if ($result) {
265
				$this->removeFromCache($this->root . $source);
266
				$this->removeFromCache($this->root . $target);
267
			}
268
		} catch (AlreadyExistsException $e) {
0 ignored issues
show
Bug introduced by
The class Icewind\SMB\Exception\AlreadyExistsException does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
269
			$this->swallow(__FUNCTION__, $e);
270 View Code Duplication
			if ($this->unlink($target)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
271
				$result = $this->share->rename($this->root . $source, $this->root . $target);
272
				if ($result) {
273
					$this->removeFromCache($this->root . $source);
274
					$this->removeFromCache($this->root . $target);
275
				}
276
			} else {
277
				$result = false;
278
			}
279
		} catch (Exception $e) {
280
			$this->swallow(__FUNCTION__, $e);
281
			// Icewind\SMB\Exception\Exception, not a plain exception
282
			if ($e->getCode() === 22) {
283 View Code Duplication
				if ($this->unlink($target)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
284
					$result = $this->share->rename($this->root . $source, $this->root . $target);
285
					if ($result) {
286
						$this->removeFromCache($this->root . $source);
287
						$this->removeFromCache($this->root . $target);
288
					}
289
				} else {
290
					$result = false;
291
				}
292
			} elseif ($e->getCode() === 16) {
293
				$this->swallow(__FUNCTION__, $e);
294
				$result = false;
295
			} else {
296
				$ex = new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e);
297
				$this->leave(__FUNCTION__, $ex);
298
				throw $ex;
299
			}
300
		} catch (\Exception $e) {
301
			$this->swallow(__FUNCTION__, $e);
302
			$result = false;
303
		}
304
		return $this->leave(__FUNCTION__, $result);
305
	}
306
307
	private function removeFromCache($path) {
308
		$path = \trim($path, '/');
309
		// TODO The CappedCache does not really clear by prefix. It just clears all.
310
		//$this->dirCache->clear($path);
311
		$this->statCache->clear($path);
312
		//$this->xattrCache->clear($path);
313
	}
314
	/**
315
	 * @param string $path
316
	 * @return array
317
	 */
318
	public function stat($path) {
319
		$this->log('enter: '.__FUNCTION__."($path)");
320
		try {
321
			$result = $this->formatInfo($this->getFileInfo($path));
322
		} catch (NotFoundException $e) {
323
			$this->swallow(__FUNCTION__, $e);
324
			$result = false;
325
		} catch (Exception $e) {
326
			$ex = new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e);
327
			$this->leave(__FUNCTION__, $ex);
328
			throw $ex;
329
		}
330
		return $this->leave(__FUNCTION__, $result);
331
	}
332
333
	/**
334
	 * get the best guess for the modification time of the share
335
	 * NOTE: modification times do not bubble up the directory tree, basically
336
	 * we are just guessing a time
337
	 *
338
	 * @return int the calculated mtime for the folder
339
	 */
340
	private function shareMTime() {
341
		$this->log('enter: '.__FUNCTION__, Util::DEBUG);
342
		$files = $this->share->dir($this->root);
343
		$result = 0;
344
		foreach ($files as $fileInfo) {
345
			if ($fileInfo->getMTime() > $result) {
346
				$result = $fileInfo->getMTime();
347
			}
348
		}
349
		return $this->leave(__FUNCTION__, $result);
350
	}
351
	/**
352
	 * Check if the path is our root dir (not the smb one)
353
	 *
354
	 * @param string $path the path
355
	 * @return bool true if it's root, false if not
356
	 */
357
	private function isRootDir($path) {
358
		$this->log('enter: '.__FUNCTION__."($path)", Util::DEBUG);
359
		$result = $path === '' || $path === '/' || $path === '.';
360
		return $this->leave(__FUNCTION__, $result);
361
	}
362
	/**
363
	 * Check if our root points to a smb share
364
	 *
365
	 * @return bool true if our root points to a share false otherwise
366
	 */
367
	private function remoteIsShare() {
368
		$this->log('enter: '.__FUNCTION__, Util::DEBUG);
369
		$result = $this->share->getName() && (!$this->root || $this->root === '/');
370
		return $this->leave(__FUNCTION__, $result);
371
	}
372
	/**
373
	 * @param string $path
374
	 * @return bool
375
	 * @throws StorageNotAvailableException
376
	 */
377
	public function unlink($path) {
378
		$this->log('enter: '.__FUNCTION__."($path)");
379
380
		if ($this->isRootDir($path)) {
381
			$this->log("refusing to unlink \"$path\"");
382
			return $this->leave(__FUNCTION__, false);
383
		}
384
385
		$result = false;
386
		try {
387
			if ($this->is_dir($path)) {
388
				$result = $this->rmdir($path);
389
			} else {
390
				$path = $this->buildPath($path);
391
				$this->share->del($path);
392
				unset($this->statCache[$path]);
393
				$result = true;
394
			}
395
		} catch (NotFoundException $e) {
396
			$this->swallow(__FUNCTION__, $e);
397
		} catch (ForbiddenException $e) {
398
			$this->swallow(__FUNCTION__, $e);
399
		} catch (Exception $e) {
400 View Code Duplication
			if ($e->getCode() === 16) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
401
				$this->swallow(__FUNCTION__, $e);
402
			} else {
403
				$ex = new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e);
404
				$this->leave(__FUNCTION__, $ex);
405
				throw $ex;
406
			}
407
		}
408
		return $this->leave(__FUNCTION__, $result);
409
	}
410
411
	/**
412
	 * check if a file or folder has been updated since $time
413
	 *
414
	 * @param string $path
415
	 * @param int $time
416
	 * @return bool
417
	 */
418
	public function hasUpdated($path, $time) {
419
		$this->log('enter: '.__FUNCTION__."($path, $time)");
420
		$actualTime = $this->filemtime($path);
421
		$result = $actualTime > $time;
422
		return $this->leave(__FUNCTION__, $result);
423
	}
424
425
	/**
426
	 * @param string $path
427
	 * @param string $mode
428
	 * @return resource
429
	 * @throws StorageNotAvailableException
430
	 */
431
	public function fopen($path, $mode) {
432
		$this->log('enter: '.__FUNCTION__."($path, $mode)");
433
		$fullPath = $this->buildPath($path);
434
		$result = false;
435
		try {
436
			switch ($mode) {
437
				case 'r':
438
				case 'rb':
439
					if ($this->file_exists($path)) {
440
						$result = $this->share->read($fullPath);
441
					}
442
					break;
443
				case 'w':
444
				case 'wb':
445
					$source = $this->share->write($fullPath);
446
					$result = CallBackWrapper::wrap($source, null, null, function () use ($fullPath) {
447
						unset($this->statCache[$fullPath]);
448
					});
449
					break;
450
				case 'a':
451
				case 'ab':
452
				case 'r+':
453
				case 'w+':
454
				case 'wb+':
455
				case 'a+':
456
				case 'x':
457
				case 'x+':
458
				case 'c':
459
				case 'c+':
460
					//emulate these
461
					if (\strrpos($path, '.') !== false) {
462
						$ext = \substr($path, \strrpos($path, '.'));
463
					} else {
464
						$ext = '';
465
					}
466
					if ($this->file_exists($path)) {
467
						if (!$this->isUpdatable($path)) {
468
							break;
469
						}
470
						$tmpFile = $this->getCachedFile($path);
471
					} else {
472
						if (!$this->isCreatable(\dirname($path))) {
473
							break;
474
						}
475
						$tmpFile = \OC::$server->getTempManager()->getTemporaryFile($ext);
476
					}
477
					$source = \fopen($tmpFile, $mode);
478
					$share = $this->share;
479
					$result = CallbackWrapper::wrap($source, null, null, function () use ($tmpFile, $fullPath, $share) {
480
						unset($this->statCache[$fullPath]);
481
						$share->put($tmpFile, $fullPath);
482
						\unlink($tmpFile);
483
					});
484
			}
485
		} catch (NotFoundException $e) {
486
			$this->swallow(__FUNCTION__, $e);
487
		} catch (ForbiddenException $e) {
488
			$this->swallow(__FUNCTION__, $e);
489
		} catch (Exception $e) {
490 View Code Duplication
			if ($e->getCode() === 16) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
491
				$this->swallow(__FUNCTION__, $e);
492
			} else {
493
				$ex = new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e);
494
				$this->leave(__FUNCTION__, $ex);
495
				throw $ex;
496
			}
497
		}
498
		return $this->leave(__FUNCTION__, $result);
499
	}
500
501
	public function rmdir($path) {
502
		$this->log('enter: '.__FUNCTION__."($path)");
503
504
		if ($this->isRootDir($path)) {
505
			$this->log("refusing to delete \"$path\"");
506
			return $this->leave(__FUNCTION__, false);
507
		}
508
509
		$result = false;
510
		try {
511
			$this->removeFromCache($path);
512
			$content = $this->share->dir($this->buildPath($path));
513
			foreach ($content as $file) {
514
				if ($file->isDirectory()) {
515
					$this->rmdir($path . '/' . $file->getName());
516
				} else {
517
					$this->share->del($file->getPath());
518
				}
519
			}
520
			$this->share->rmdir($this->buildPath($path));
521
			$result = true;
522
		} catch (NotFoundException $e) {
523
			$this->swallow(__FUNCTION__, $e);
524
		} catch (ForbiddenException $e) {
525
			$this->swallow(__FUNCTION__, $e);
526
		} catch (Exception $e) {
527 View Code Duplication
			if ($e->getCode() === 16) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
528
				$this->swallow(__FUNCTION__, $e);
529
			} else {
530
				$ex = new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e);
531
				$this->leave(__FUNCTION__, $ex);
532
				throw $ex;
533
			}
534
		}
535
		return $this->leave(__FUNCTION__, $result);
536
	}
537
538
	public function touch($path, $time = null) {
539
		$this->log('enter: '.__FUNCTION__."($path, $time)");
540
		$result = false;
541
		try {
542
			if (!$this->file_exists($path)) {
543
				$fh = $this->share->write($this->buildPath($path));
544
				\fclose($fh);
545
				$result = true;
546
			}
547
		} catch (Exception $e) {
548 View Code Duplication
			if ($e->getCode() === 16) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
549
				$this->swallow(__FUNCTION__, $e);
550
			} else {
551
				$ex = new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e);
552
				$this->leave(__FUNCTION__, $ex);
553
				throw $ex;
554
			}
555
		}
556
		return $this->leave(__FUNCTION__, $result);
557
	}
558
559
	public function opendir($path) {
560
		$this->log('enter: '.__FUNCTION__."($path)");
561
		$result = false;
562
		try {
563
			$files = $this->getFolderContents($path);
564
			$names = \array_map(function ($info) {
565
				/** @var \Icewind\SMB\IFileInfo $info */
566
				return $info->getName();
567
			}, $files);
568
			$result = IteratorDirectory::wrap($names);
569
		} catch (NotFoundException $e) {
570
			$this->swallow(__FUNCTION__, $e);
571
		} catch (ForbiddenException $e) {
572
			$this->swallow(__FUNCTION__, $e);
573
		} catch (Exception $e) {
574
			$ex = new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e);
575
			$this->leave(__FUNCTION__, $ex);
576
			throw $ex;
577
		}
578
		return $this->leave(__FUNCTION__, $result);
579
	}
580
581
	public function filetype($path) {
582
		$this->log('enter: '.__FUNCTION__."($path)");
583
		$result = false;
584
		try {
585
			$result = $this->getFileInfo($path)->isDirectory() ? 'dir' : 'file';
586
		} catch (NotFoundException $e) {
587
			$this->swallow(__FUNCTION__, $e);
588
		} catch (ForbiddenException $e) {
589
			$this->swallow(__FUNCTION__, $e);
590
		} catch (Exception $e) {
591
			$ex = new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e);
592
			$this->leave(__FUNCTION__, $ex);
593
			throw $ex;
594
		}
595
		return $this->leave(__FUNCTION__, $result);
596
	}
597
598
	public function mkdir($path) {
599
		$this->log('enter: '.__FUNCTION__."($path)");
600
		$result = false;
601
		$path = $this->buildPath($path);
602
		try {
603
			$result = $this->share->mkdir($path);
604
		} catch (ForbiddenException $e) {
605
			$this->swallow(__FUNCTION__, $e);
606
		} catch (AlreadyExistsException $e) {
0 ignored issues
show
Bug introduced by
The class Icewind\SMB\Exception\AlreadyExistsException does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
607
			$this->swallow(__FUNCTION__, $e);
608
		} catch (Exception $e) {
609 View Code Duplication
			if ($e->getCode() === 16) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
610
				$this->swallow(__FUNCTION__, $e);
611
			} else {
612
				$ex = new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e);
613
				$this->leave(__FUNCTION__, $ex);
614
				throw $ex;
615
			}
616
		}
617
		return $this->leave(__FUNCTION__, $result);
618
	}
619
620
	public function file_exists($path) {
621
		$this->log('enter: '.__FUNCTION__."($path)");
622
		$result = false;
623
		try {
624
			$this->getFileInfo($path);
625
			$result = true;
626
		} catch (NotFoundException $e) {
627
			$this->swallow(__FUNCTION__, $e);
628
		} catch (ForbiddenException $e) {
629
			$this->swallow(__FUNCTION__, $e);
630
		} catch (Exception $e) {
631
			$ex = new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e);
632
			$this->leave(__FUNCTION__, $ex);
633
			throw $ex;
634
		}
635
		return $this->leave(__FUNCTION__, $result);
636
	}
637
638
	public function isReadable($path) {
639
		$this->log('enter: '.__FUNCTION__."($path)");
640
		$result = false;
641
		try {
642
			$info = $this->getFileInfo($path);
643
			$result = !$info->isHidden();
644
		} catch (NotFoundException $e) {
645
			$this->swallow(__FUNCTION__, $e);
646
		} catch (ForbiddenException $e) {
647
			$this->swallow(__FUNCTION__, $e);
648
		} catch (Exception $e) {
649
			$ex = new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e);
650
			$this->leave(__FUNCTION__, $ex);
651
			throw $ex;
652
		}
653
		return $this->leave(__FUNCTION__, $result);
654
	}
655
656 View Code Duplication
	public function isUpdatable($path) {
657
		$this->log('enter: '.__FUNCTION__."($path)");
658
		$result = false;
659
		try {
660
			$info = $this->getFileInfo($path);
661
			// following windows behaviour for read-only folders: they can be written into
662
			// (https://support.microsoft.com/en-us/kb/326549 - "cause" section)
663
			$result = !$info->isHidden() && (!$info->isReadOnly() || $this->is_dir($path));
664
		} catch (NotFoundException $e) {
665
			$this->swallow(__FUNCTION__, $e);
666
		} catch (ForbiddenException $e) {
667
			$this->swallow(__FUNCTION__, $e);
668
		} catch (Exception $e) {
669
			$ex = new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e);
670
			$this->leave(__FUNCTION__, $ex);
671
			throw $ex;
672
		}
673
		return $this->leave(__FUNCTION__, $result);
674
	}
675
676 View Code Duplication
	public function isDeletable($path) {
677
		$this->log('enter: '.__FUNCTION__."($path)");
678
		$result = false;
679
		try {
680
			$info = $this->getFileInfo($path);
681
			$result = !$info->isHidden() && !$info->isReadOnly();
682
		} catch (NotFoundException $e) {
683
			$this->swallow(__FUNCTION__, $e);
684
		} catch (ForbiddenException $e) {
685
			$this->swallow(__FUNCTION__, $e);
686
		} catch (Exception $e) {
687
			$ex = new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e);
688
			$this->leave(__FUNCTION__, $ex);
689
			throw $ex;
690
		}
691
		return $this->leave(__FUNCTION__, $result);
692
	}
693
694
	/**
695
	 * check if smbclient is installed
696
	 */
697
	public static function checkDependencies() {
698
		return (
699
			(bool)\OC_Helper::findBinaryPath('smbclient')
700
			|| NativeServer::available(new System())
701
		) ? true : ['smbclient'];
702
	}
703
704
	/**
705
	 * Test a storage for availability
706
	 *
707
	 * @return bool
708
	 */
709
	public function test() {
710
		$this->log('enter: '.__FUNCTION__."()");
711
		$result = false;
712
		try {
713
			$result = parent::test();
714
		} catch (Exception $e) {
715
			$this->swallow(__FUNCTION__, $e);
716
		}
717
		return $this->leave(__FUNCTION__, $result);
718
	}
719
720
	/**
721
	 * @param string $message
722
	 * @param int $level
723
	 * @param string $from
724
	 */
725
	private function log($message, $level = Util::DEBUG, $from = 'smb') {
726
		if (\OC::$server->getConfig()->getSystemValue('smb.logging.enable', false) === true) {
727
			Util::writeLog($from, $message, $level);
728
		}
729
	}
730
731
	/**
732
	 * if smb.logging.enable is set to true in the config will log a leave line
733
	 * with the given function, the return value or the exception
734
	 *
735
	 * @param $function
736
	 * @param mixed $result an exception will be logged and then returned
737
	 * @return mixed
738
	 */
739
	private function leave($function, $result) {
740
		if (\OC::$server->getConfig()->getSystemValue('smb.logging.enable', false) === false) {
741
			//don't bother building log strings
742
			return $result;
743
		} elseif ($result === true) {
744
			Util::writeLog('smb', "leave: $function, return true", Util::DEBUG);
745
		} elseif ($result === false) {
746
			Util::writeLog('smb', "leave: $function, return false", Util::DEBUG);
747
		} elseif (\is_string($result)) {
748
			Util::writeLog('smb', "leave: $function, return '$result'", Util::DEBUG);
749
		} elseif (\is_resource($result)) {
750
			Util::writeLog('smb', "leave: $function, return resource", Util::DEBUG);
751
		} elseif ($result instanceof \Exception) {
752
			Util::writeLog('smb', "leave: $function, throw ".\get_class($result)
753
				.' - code: '.$result->getCode()
754
				.' message: '.$result->getMessage()
755
				.' trace: '.$result->getTraceAsString(), Util::DEBUG);
756
		} else {
757
			Util::writeLog('smb', "leave: $function, return ".\json_encode($result, true), Util::DEBUG);
758
		}
759
		return $result;
760
	}
761
762
	private function swallow($function, \Exception $exception) {
763
		if (\OC::$server->getConfig()->getSystemValue('smb.logging.enable', false) === true) {
764
			Util::writeLog('smb', "$function swallowing ".\get_class($exception)
765
				.' - code: '.$exception->getCode()
766
				.' message: '.$exception->getMessage()
767
				.' trace: '.$exception->getTraceAsString(), Util::DEBUG);
768
		}
769
	}
770
771
	/**
772
	 * immediately close / free connection
773
	 */
774
	public function __destruct() {
775
		unset($this->share);
776
	}
777
}
778