Completed
Pull Request — master (#9578)
by Robin
35:46
created
apps/files_external/lib/Lib/Storage/SMB.php 2 patches
Spacing   +13 added lines, -13 removed lines patch added patch discarded remove patch
@@ -85,8 +85,8 @@  discard block
 block discarded – undo
85 85
 			$this->share = $this->server->getShare(trim($params['share'], '/'));
86 86
 
87 87
 			$this->root = $params['root'] ?? '/';
88
-			$this->root = '/' . ltrim($this->root, '/');
89
-			$this->root = rtrim($this->root, '/') . '/';
88
+			$this->root = '/'.ltrim($this->root, '/');
89
+			$this->root = rtrim($this->root, '/').'/';
90 90
 		} else {
91 91
 			throw new \Exception('Invalid configuration');
92 92
 		}
@@ -101,7 +101,7 @@  discard block
 block discarded – undo
101 101
 		// FIXME: double slash to keep compatible with the old storage ids,
102 102
 		// failure to do so will lead to creation of a new storage id and
103 103
 		// loss of shares from the storage
104
-		return 'smb::' . $this->server->getAuth()->getUsername() . '@' . $this->server->getHost() . '//' . $this->share->getName() . '/' . $this->root;
104
+		return 'smb::'.$this->server->getAuth()->getUsername().'@'.$this->server->getHost().'//'.$this->share->getName().'/'.$this->root;
105 105
 	}
106 106
 
107 107
 	/**
@@ -109,7 +109,7 @@  discard block
 block discarded – undo
109 109
 	 * @return string
110 110
 	 */
111 111
 	protected function buildPath($path) {
112
-		return Filesystem::normalizePath($this->root . '/' . $path, true, false, true);
112
+		return Filesystem::normalizePath($this->root.'/'.$path, true, false, true);
113 113
 	}
114 114
 
115 115
 	protected function relativePath($fullPath) {
@@ -149,9 +149,9 @@  discard block
 block discarded – undo
149 149
 			$path = $this->buildPath($path);
150 150
 			$files = $this->share->dir($path);
151 151
 			foreach ($files as $file) {
152
-				$this->statCache[$path . '/' . $file->getName()] = $file;
152
+				$this->statCache[$path.'/'.$file->getName()] = $file;
153 153
 			}
154
-			return array_filter($files, function (IFileInfo $file) {
154
+			return array_filter($files, function(IFileInfo $file) {
155 155
 				try {
156 156
 					return !$file->isHidden();
157 157
 				} catch (ForbiddenException $e) {
@@ -325,7 +325,7 @@  discard block
 block discarded – undo
325 325
 				case 'w':
326 326
 				case 'wb':
327 327
 					$source = $this->share->write($fullPath);
328
-					return CallBackWrapper::wrap($source, null, null, function () use ($fullPath) {
328
+					return CallBackWrapper::wrap($source, null, null, function() use ($fullPath) {
329 329
 						unset($this->statCache[$fullPath]);
330 330
 					});
331 331
 				case 'a':
@@ -357,7 +357,7 @@  discard block
 block discarded – undo
357 357
 					}
358 358
 					$source = fopen($tmpFile, $mode);
359 359
 					$share = $this->share;
360
-					return CallbackWrapper::wrap($source, null, null, function () use ($tmpFile, $fullPath, $share) {
360
+					return CallbackWrapper::wrap($source, null, null, function() use ($tmpFile, $fullPath, $share) {
361 361
 						unset($this->statCache[$fullPath]);
362 362
 						$share->put($tmpFile, $fullPath);
363 363
 						unlink($tmpFile);
@@ -383,7 +383,7 @@  discard block
 block discarded – undo
383 383
 			$content = $this->share->dir($this->buildPath($path));
384 384
 			foreach ($content as $file) {
385 385
 				if ($file->isDirectory()) {
386
-					$this->rmdir($path . '/' . $file->getName());
386
+					$this->rmdir($path.'/'.$file->getName());
387 387
 				} else {
388 388
 					$this->share->del($file->getPath());
389 389
 				}
@@ -420,7 +420,7 @@  discard block
 block discarded – undo
420 420
 		} catch (ForbiddenException $e) {
421 421
 			return false;
422 422
 		}
423
-		$names = array_map(function ($info) {
423
+		$names = array_map(function($info) {
424 424
 			/** @var \Icewind\SMB\IFileInfo $info */
425 425
 			return $info->getName();
426 426
 		}, $files);
@@ -502,7 +502,7 @@  discard block
 block discarded – undo
502 502
 	 */
503 503
 	public static function checkDependencies() {
504 504
 		return (
505
-			(bool)\OC_Helper::findBinaryPath('smbclient')
505
+			(bool) \OC_Helper::findBinaryPath('smbclient')
506 506
 			|| NativeServer::available(new System())
507 507
 		) ? true : ['smbclient'];
508 508
 	}
@@ -521,7 +521,7 @@  discard block
 block discarded – undo
521 521
 	}
522 522
 
523 523
 	public function listen($path, callable $callback) {
524
-		$this->notify($path)->listen(function (IChange $change) use ($callback) {
524
+		$this->notify($path)->listen(function(IChange $change) use ($callback) {
525 525
 			if ($change instanceof IRenameChange) {
526 526
 				return $callback($change->getType(), $change->getPath(), $change->getTargetPath());
527 527
 			} else {
@@ -531,7 +531,7 @@  discard block
 block discarded – undo
531 531
 	}
532 532
 
533 533
 	public function notify($path) {
534
-		$path = '/' . ltrim($path, '/');
534
+		$path = '/'.ltrim($path, '/');
535 535
 		$shareNotifyHandler = $this->share->notify($this->buildPath($path));
536 536
 		return new SMBNotifyHandler($shareNotifyHandler, $this->root);
537 537
 	}
Please login to merge, or discard this patch.
Indentation   +489 added lines, -489 removed lines patch added patch discarded remove patch
@@ -57,493 +57,493 @@
 block discarded – undo
57 57
 use OCP\ILogger;
58 58
 
59 59
 class SMB extends Common implements INotifyStorage {
60
-	/**
61
-	 * @var \Icewind\SMB\IServer
62
-	 */
63
-	protected $server;
64
-
65
-	/**
66
-	 * @var \Icewind\SMB\IShare
67
-	 */
68
-	protected $share;
69
-
70
-	/**
71
-	 * @var string
72
-	 */
73
-	protected $root;
74
-
75
-	/**
76
-	 * @var \Icewind\SMB\IFileInfo[]
77
-	 */
78
-	protected $statCache;
79
-
80
-	public function __construct($params) {
81
-		if (isset($params['host']) && isset($params['user']) && isset($params['password']) && isset($params['share'])) {
82
-			list($workgroup, $user) = $this->splitUser($params['user']);
83
-			$auth = new BasicAuth($user, $workgroup, $params['password']);
84
-			$serverFactory = new ServerFactory();
85
-			$this->server = $serverFactory->createServer($params['host'], $auth);
86
-			$this->share = $this->server->getShare(trim($params['share'], '/'));
87
-
88
-			$this->root = $params['root'] ?? '/';
89
-			$this->root = '/' . ltrim($this->root, '/');
90
-			$this->root = rtrim($this->root, '/') . '/';
91
-		} else {
92
-			throw new \Exception('Invalid configuration');
93
-		}
94
-		$this->statCache = new CappedMemoryCache();
95
-		parent::__construct($params);
96
-	}
97
-
98
-	private function splitUser($user) {
99
-		if (strpos($user, '/')) {
100
-			return explode('/', $user, 2);
101
-		} elseif (strpos($user, '\\')) {
102
-			return explode('\\', $user);
103
-		} else {
104
-			return [null, $user];
105
-		}
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
-		return Filesystem::normalizePath($this->root . '/' . $path, true, false, true);
124
-	}
125
-
126
-	protected function relativePath($fullPath) {
127
-		if ($fullPath === $this->root) {
128
-			return '';
129
-		} else if (substr($fullPath, 0, strlen($this->root)) === $this->root) {
130
-			return substr($fullPath, strlen($this->root));
131
-		} else {
132
-			return null;
133
-		}
134
-	}
135
-
136
-	/**
137
-	 * @param string $path
138
-	 * @return \Icewind\SMB\IFileInfo
139
-	 * @throws StorageNotAvailableException
140
-	 */
141
-	protected function getFileInfo($path) {
142
-		try {
143
-			$path = $this->buildPath($path);
144
-			if (!isset($this->statCache[$path])) {
145
-				$this->statCache[$path] = $this->share->stat($path);
146
-			}
147
-			return $this->statCache[$path];
148
-		} catch (ConnectException $e) {
149
-			throw new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e);
150
-		}
151
-	}
152
-
153
-	/**
154
-	 * @param string $path
155
-	 * @return \Icewind\SMB\IFileInfo[]
156
-	 * @throws StorageNotAvailableException
157
-	 */
158
-	protected function getFolderContents($path) {
159
-		try {
160
-			$path = $this->buildPath($path);
161
-			$files = $this->share->dir($path);
162
-			foreach ($files as $file) {
163
-				$this->statCache[$path . '/' . $file->getName()] = $file;
164
-			}
165
-			return array_filter($files, function (IFileInfo $file) {
166
-				try {
167
-					return !$file->isHidden();
168
-				} catch (ForbiddenException $e) {
169
-					return false;
170
-				} catch (NotFoundException $e) {
171
-					return false;
172
-				}
173
-			});
174
-		} catch (ConnectException $e) {
175
-			throw new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e);
176
-		}
177
-	}
178
-
179
-	/**
180
-	 * @param \Icewind\SMB\IFileInfo $info
181
-	 * @return array
182
-	 */
183
-	protected function formatInfo($info) {
184
-		$result = [
185
-			'size' => $info->getSize(),
186
-			'mtime' => $info->getMTime(),
187
-		];
188
-		if ($info->isDirectory()) {
189
-			$result['type'] = 'dir';
190
-		} else {
191
-			$result['type'] = 'file';
192
-		}
193
-		return $result;
194
-	}
195
-
196
-	/**
197
-	 * Rename the files. If the source or the target is the root, the rename won't happen.
198
-	 *
199
-	 * @param string $source the old name of the path
200
-	 * @param string $target the new name of the path
201
-	 * @return bool true if the rename is successful, false otherwise
202
-	 */
203
-	public function rename($source, $target) {
204
-		if ($this->isRootDir($source) || $this->isRootDir($target)) {
205
-			return false;
206
-		}
207
-
208
-		$absoluteSource = $this->buildPath($source);
209
-		$absoluteTarget = $this->buildPath($target);
210
-		try {
211
-			$result = $this->share->rename($absoluteSource, $absoluteTarget);
212
-		} catch (AlreadyExistsException $e) {
213
-			$this->remove($target);
214
-			$result = $this->share->rename($absoluteSource, $absoluteTarget);
215
-		} catch (\Exception $e) {
216
-			\OC::$server->getLogger()->logException($e, ['level' => ILogger::WARN]);
217
-			return false;
218
-		}
219
-		unset($this->statCache[$absoluteSource], $this->statCache[$absoluteTarget]);
220
-		return $result;
221
-	}
222
-
223
-	public function stat($path) {
224
-		try {
225
-			$result = $this->formatInfo($this->getFileInfo($path));
226
-		} catch (ForbiddenException $e) {
227
-			return false;
228
-		} catch (NotFoundException $e) {
229
-			return false;
230
-		}
231
-		if ($this->remoteIsShare() && $this->isRootDir($path)) {
232
-			$result['mtime'] = $this->shareMTime();
233
-		}
234
-		return $result;
235
-	}
236
-
237
-	/**
238
-	 * get the best guess for the modification time of the share
239
-	 *
240
-	 * @return int
241
-	 */
242
-	private function shareMTime() {
243
-		$highestMTime = 0;
244
-		$files = $this->share->dir($this->root);
245
-		foreach ($files as $fileInfo) {
246
-			try {
247
-				if ($fileInfo->getMTime() > $highestMTime) {
248
-					$highestMTime = $fileInfo->getMTime();
249
-				}
250
-			} catch (NotFoundException $e) {
251
-				// Ignore this, can happen on unavailable DFS shares
252
-			}
253
-		}
254
-		return $highestMTime;
255
-	}
256
-
257
-	/**
258
-	 * Check if the path is our root dir (not the smb one)
259
-	 *
260
-	 * @param string $path the path
261
-	 * @return bool
262
-	 */
263
-	private function isRootDir($path) {
264
-		return $path === '' || $path === '/' || $path === '.';
265
-	}
266
-
267
-	/**
268
-	 * Check if our root points to a smb share
269
-	 *
270
-	 * @return bool true if our root points to a share false otherwise
271
-	 */
272
-	private function remoteIsShare() {
273
-		return $this->share->getName() && (!$this->root || $this->root === '/');
274
-	}
275
-
276
-	/**
277
-	 * @param string $path
278
-	 * @return bool
279
-	 */
280
-	public function unlink($path) {
281
-		if ($this->isRootDir($path)) {
282
-			return false;
283
-		}
284
-
285
-		try {
286
-			if ($this->is_dir($path)) {
287
-				return $this->rmdir($path);
288
-			} else {
289
-				$path = $this->buildPath($path);
290
-				unset($this->statCache[$path]);
291
-				$this->share->del($path);
292
-				return true;
293
-			}
294
-		} catch (NotFoundException $e) {
295
-			return false;
296
-		} catch (ForbiddenException $e) {
297
-			return false;
298
-		} catch (ConnectException $e) {
299
-			throw new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e);
300
-		}
301
-	}
302
-
303
-	/**
304
-	 * check if a file or folder has been updated since $time
305
-	 *
306
-	 * @param string $path
307
-	 * @param int $time
308
-	 * @return bool
309
-	 */
310
-	public function hasUpdated($path, $time) {
311
-		if (!$path and $this->root === '/') {
312
-			// mtime doesn't work for shares, but giving the nature of the backend,
313
-			// doing a full update is still just fast enough
314
-			return true;
315
-		} else {
316
-			$actualTime = $this->filemtime($path);
317
-			return $actualTime > $time;
318
-		}
319
-	}
320
-
321
-	/**
322
-	 * @param string $path
323
-	 * @param string $mode
324
-	 * @return resource|false
325
-	 */
326
-	public function fopen($path, $mode) {
327
-		$fullPath = $this->buildPath($path);
328
-		try {
329
-			switch ($mode) {
330
-				case 'r':
331
-				case 'rb':
332
-					if (!$this->file_exists($path)) {
333
-						return false;
334
-					}
335
-					return $this->share->read($fullPath);
336
-				case 'w':
337
-				case 'wb':
338
-					$source = $this->share->write($fullPath);
339
-					return CallBackWrapper::wrap($source, null, null, function () use ($fullPath) {
340
-						unset($this->statCache[$fullPath]);
341
-					});
342
-				case 'a':
343
-				case 'ab':
344
-				case 'r+':
345
-				case 'w+':
346
-				case 'wb+':
347
-				case 'a+':
348
-				case 'x':
349
-				case 'x+':
350
-				case 'c':
351
-				case 'c+':
352
-					//emulate these
353
-					if (strrpos($path, '.') !== false) {
354
-						$ext = substr($path, strrpos($path, '.'));
355
-					} else {
356
-						$ext = '';
357
-					}
358
-					if ($this->file_exists($path)) {
359
-						if (!$this->isUpdatable($path)) {
360
-							return false;
361
-						}
362
-						$tmpFile = $this->getCachedFile($path);
363
-					} else {
364
-						if (!$this->isCreatable(dirname($path))) {
365
-							return false;
366
-						}
367
-						$tmpFile = \OC::$server->getTempManager()->getTemporaryFile($ext);
368
-					}
369
-					$source = fopen($tmpFile, $mode);
370
-					$share = $this->share;
371
-					return CallbackWrapper::wrap($source, null, null, function () use ($tmpFile, $fullPath, $share) {
372
-						unset($this->statCache[$fullPath]);
373
-						$share->put($tmpFile, $fullPath);
374
-						unlink($tmpFile);
375
-					});
376
-			}
377
-			return false;
378
-		} catch (NotFoundException $e) {
379
-			return false;
380
-		} catch (ForbiddenException $e) {
381
-			return false;
382
-		} catch (ConnectException $e) {
383
-			throw new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e);
384
-		}
385
-	}
386
-
387
-	public function rmdir($path) {
388
-		if ($this->isRootDir($path)) {
389
-			return false;
390
-		}
391
-
392
-		try {
393
-			$this->statCache = array();
394
-			$content = $this->share->dir($this->buildPath($path));
395
-			foreach ($content as $file) {
396
-				if ($file->isDirectory()) {
397
-					$this->rmdir($path . '/' . $file->getName());
398
-				} else {
399
-					$this->share->del($file->getPath());
400
-				}
401
-			}
402
-			$this->share->rmdir($this->buildPath($path));
403
-			return true;
404
-		} catch (NotFoundException $e) {
405
-			return false;
406
-		} catch (ForbiddenException $e) {
407
-			return false;
408
-		} catch (ConnectException $e) {
409
-			throw new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e);
410
-		}
411
-	}
412
-
413
-	public function touch($path, $time = null) {
414
-		try {
415
-			if (!$this->file_exists($path)) {
416
-				$fh = $this->share->write($this->buildPath($path));
417
-				fclose($fh);
418
-				return true;
419
-			}
420
-			return false;
421
-		} catch (ConnectException $e) {
422
-			throw new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e);
423
-		}
424
-	}
425
-
426
-	public function opendir($path) {
427
-		try {
428
-			$files = $this->getFolderContents($path);
429
-		} catch (NotFoundException $e) {
430
-			return false;
431
-		} catch (ForbiddenException $e) {
432
-			return false;
433
-		}
434
-		$names = array_map(function ($info) {
435
-			/** @var \Icewind\SMB\IFileInfo $info */
436
-			return $info->getName();
437
-		}, $files);
438
-		return IteratorDirectory::wrap($names);
439
-	}
440
-
441
-	public function filetype($path) {
442
-		try {
443
-			return $this->getFileInfo($path)->isDirectory() ? 'dir' : 'file';
444
-		} catch (NotFoundException $e) {
445
-			return false;
446
-		} catch (ForbiddenException $e) {
447
-			return false;
448
-		}
449
-	}
450
-
451
-	public function mkdir($path) {
452
-		$path = $this->buildPath($path);
453
-		try {
454
-			$this->share->mkdir($path);
455
-			return true;
456
-		} catch (ConnectException $e) {
457
-			throw new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e);
458
-		} catch (Exception $e) {
459
-			return false;
460
-		}
461
-	}
462
-
463
-	public function file_exists($path) {
464
-		try {
465
-			$this->getFileInfo($path);
466
-			return true;
467
-		} catch (NotFoundException $e) {
468
-			return false;
469
-		} catch (ForbiddenException $e) {
470
-			return false;
471
-		} catch (ConnectException $e) {
472
-			throw new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e);
473
-		}
474
-	}
475
-
476
-	public function isReadable($path) {
477
-		try {
478
-			$info = $this->getFileInfo($path);
479
-			return !$info->isHidden();
480
-		} catch (NotFoundException $e) {
481
-			return false;
482
-		} catch (ForbiddenException $e) {
483
-			return false;
484
-		}
485
-	}
486
-
487
-	public function isUpdatable($path) {
488
-		try {
489
-			$info = $this->getFileInfo($path);
490
-			// following windows behaviour for read-only folders: they can be written into
491
-			// (https://support.microsoft.com/en-us/kb/326549 - "cause" section)
492
-			return !$info->isHidden() && (!$info->isReadOnly() || $this->is_dir($path));
493
-		} catch (NotFoundException $e) {
494
-			return false;
495
-		} catch (ForbiddenException $e) {
496
-			return false;
497
-		}
498
-	}
499
-
500
-	public function isDeletable($path) {
501
-		try {
502
-			$info = $this->getFileInfo($path);
503
-			return !$info->isHidden() && !$info->isReadOnly();
504
-		} catch (NotFoundException $e) {
505
-			return false;
506
-		} catch (ForbiddenException $e) {
507
-			return false;
508
-		}
509
-	}
510
-
511
-	/**
512
-	 * check if smbclient is installed
513
-	 */
514
-	public static function checkDependencies() {
515
-		return (
516
-			(bool)\OC_Helper::findBinaryPath('smbclient')
517
-			|| NativeServer::available(new System())
518
-		) ? true : ['smbclient'];
519
-	}
520
-
521
-	/**
522
-	 * Test a storage for availability
523
-	 *
524
-	 * @return bool
525
-	 */
526
-	public function test() {
527
-		try {
528
-			return parent::test();
529
-		} catch (Exception $e) {
530
-			return false;
531
-		}
532
-	}
533
-
534
-	public function listen($path, callable $callback) {
535
-		$this->notify($path)->listen(function (IChange $change) use ($callback) {
536
-			if ($change instanceof IRenameChange) {
537
-				return $callback($change->getType(), $change->getPath(), $change->getTargetPath());
538
-			} else {
539
-				return $callback($change->getType(), $change->getPath());
540
-			}
541
-		});
542
-	}
543
-
544
-	public function notify($path) {
545
-		$path = '/' . ltrim($path, '/');
546
-		$shareNotifyHandler = $this->share->notify($this->buildPath($path));
547
-		return new SMBNotifyHandler($shareNotifyHandler, $this->root);
548
-	}
60
+    /**
61
+     * @var \Icewind\SMB\IServer
62
+     */
63
+    protected $server;
64
+
65
+    /**
66
+     * @var \Icewind\SMB\IShare
67
+     */
68
+    protected $share;
69
+
70
+    /**
71
+     * @var string
72
+     */
73
+    protected $root;
74
+
75
+    /**
76
+     * @var \Icewind\SMB\IFileInfo[]
77
+     */
78
+    protected $statCache;
79
+
80
+    public function __construct($params) {
81
+        if (isset($params['host']) && isset($params['user']) && isset($params['password']) && isset($params['share'])) {
82
+            list($workgroup, $user) = $this->splitUser($params['user']);
83
+            $auth = new BasicAuth($user, $workgroup, $params['password']);
84
+            $serverFactory = new ServerFactory();
85
+            $this->server = $serverFactory->createServer($params['host'], $auth);
86
+            $this->share = $this->server->getShare(trim($params['share'], '/'));
87
+
88
+            $this->root = $params['root'] ?? '/';
89
+            $this->root = '/' . ltrim($this->root, '/');
90
+            $this->root = rtrim($this->root, '/') . '/';
91
+        } else {
92
+            throw new \Exception('Invalid configuration');
93
+        }
94
+        $this->statCache = new CappedMemoryCache();
95
+        parent::__construct($params);
96
+    }
97
+
98
+    private function splitUser($user) {
99
+        if (strpos($user, '/')) {
100
+            return explode('/', $user, 2);
101
+        } elseif (strpos($user, '\\')) {
102
+            return explode('\\', $user);
103
+        } else {
104
+            return [null, $user];
105
+        }
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
+        return Filesystem::normalizePath($this->root . '/' . $path, true, false, true);
124
+    }
125
+
126
+    protected function relativePath($fullPath) {
127
+        if ($fullPath === $this->root) {
128
+            return '';
129
+        } else if (substr($fullPath, 0, strlen($this->root)) === $this->root) {
130
+            return substr($fullPath, strlen($this->root));
131
+        } else {
132
+            return null;
133
+        }
134
+    }
135
+
136
+    /**
137
+     * @param string $path
138
+     * @return \Icewind\SMB\IFileInfo
139
+     * @throws StorageNotAvailableException
140
+     */
141
+    protected function getFileInfo($path) {
142
+        try {
143
+            $path = $this->buildPath($path);
144
+            if (!isset($this->statCache[$path])) {
145
+                $this->statCache[$path] = $this->share->stat($path);
146
+            }
147
+            return $this->statCache[$path];
148
+        } catch (ConnectException $e) {
149
+            throw new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e);
150
+        }
151
+    }
152
+
153
+    /**
154
+     * @param string $path
155
+     * @return \Icewind\SMB\IFileInfo[]
156
+     * @throws StorageNotAvailableException
157
+     */
158
+    protected function getFolderContents($path) {
159
+        try {
160
+            $path = $this->buildPath($path);
161
+            $files = $this->share->dir($path);
162
+            foreach ($files as $file) {
163
+                $this->statCache[$path . '/' . $file->getName()] = $file;
164
+            }
165
+            return array_filter($files, function (IFileInfo $file) {
166
+                try {
167
+                    return !$file->isHidden();
168
+                } catch (ForbiddenException $e) {
169
+                    return false;
170
+                } catch (NotFoundException $e) {
171
+                    return false;
172
+                }
173
+            });
174
+        } catch (ConnectException $e) {
175
+            throw new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e);
176
+        }
177
+    }
178
+
179
+    /**
180
+     * @param \Icewind\SMB\IFileInfo $info
181
+     * @return array
182
+     */
183
+    protected function formatInfo($info) {
184
+        $result = [
185
+            'size' => $info->getSize(),
186
+            'mtime' => $info->getMTime(),
187
+        ];
188
+        if ($info->isDirectory()) {
189
+            $result['type'] = 'dir';
190
+        } else {
191
+            $result['type'] = 'file';
192
+        }
193
+        return $result;
194
+    }
195
+
196
+    /**
197
+     * Rename the files. If the source or the target is the root, the rename won't happen.
198
+     *
199
+     * @param string $source the old name of the path
200
+     * @param string $target the new name of the path
201
+     * @return bool true if the rename is successful, false otherwise
202
+     */
203
+    public function rename($source, $target) {
204
+        if ($this->isRootDir($source) || $this->isRootDir($target)) {
205
+            return false;
206
+        }
207
+
208
+        $absoluteSource = $this->buildPath($source);
209
+        $absoluteTarget = $this->buildPath($target);
210
+        try {
211
+            $result = $this->share->rename($absoluteSource, $absoluteTarget);
212
+        } catch (AlreadyExistsException $e) {
213
+            $this->remove($target);
214
+            $result = $this->share->rename($absoluteSource, $absoluteTarget);
215
+        } catch (\Exception $e) {
216
+            \OC::$server->getLogger()->logException($e, ['level' => ILogger::WARN]);
217
+            return false;
218
+        }
219
+        unset($this->statCache[$absoluteSource], $this->statCache[$absoluteTarget]);
220
+        return $result;
221
+    }
222
+
223
+    public function stat($path) {
224
+        try {
225
+            $result = $this->formatInfo($this->getFileInfo($path));
226
+        } catch (ForbiddenException $e) {
227
+            return false;
228
+        } catch (NotFoundException $e) {
229
+            return false;
230
+        }
231
+        if ($this->remoteIsShare() && $this->isRootDir($path)) {
232
+            $result['mtime'] = $this->shareMTime();
233
+        }
234
+        return $result;
235
+    }
236
+
237
+    /**
238
+     * get the best guess for the modification time of the share
239
+     *
240
+     * @return int
241
+     */
242
+    private function shareMTime() {
243
+        $highestMTime = 0;
244
+        $files = $this->share->dir($this->root);
245
+        foreach ($files as $fileInfo) {
246
+            try {
247
+                if ($fileInfo->getMTime() > $highestMTime) {
248
+                    $highestMTime = $fileInfo->getMTime();
249
+                }
250
+            } catch (NotFoundException $e) {
251
+                // Ignore this, can happen on unavailable DFS shares
252
+            }
253
+        }
254
+        return $highestMTime;
255
+    }
256
+
257
+    /**
258
+     * Check if the path is our root dir (not the smb one)
259
+     *
260
+     * @param string $path the path
261
+     * @return bool
262
+     */
263
+    private function isRootDir($path) {
264
+        return $path === '' || $path === '/' || $path === '.';
265
+    }
266
+
267
+    /**
268
+     * Check if our root points to a smb share
269
+     *
270
+     * @return bool true if our root points to a share false otherwise
271
+     */
272
+    private function remoteIsShare() {
273
+        return $this->share->getName() && (!$this->root || $this->root === '/');
274
+    }
275
+
276
+    /**
277
+     * @param string $path
278
+     * @return bool
279
+     */
280
+    public function unlink($path) {
281
+        if ($this->isRootDir($path)) {
282
+            return false;
283
+        }
284
+
285
+        try {
286
+            if ($this->is_dir($path)) {
287
+                return $this->rmdir($path);
288
+            } else {
289
+                $path = $this->buildPath($path);
290
+                unset($this->statCache[$path]);
291
+                $this->share->del($path);
292
+                return true;
293
+            }
294
+        } catch (NotFoundException $e) {
295
+            return false;
296
+        } catch (ForbiddenException $e) {
297
+            return false;
298
+        } catch (ConnectException $e) {
299
+            throw new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e);
300
+        }
301
+    }
302
+
303
+    /**
304
+     * check if a file or folder has been updated since $time
305
+     *
306
+     * @param string $path
307
+     * @param int $time
308
+     * @return bool
309
+     */
310
+    public function hasUpdated($path, $time) {
311
+        if (!$path and $this->root === '/') {
312
+            // mtime doesn't work for shares, but giving the nature of the backend,
313
+            // doing a full update is still just fast enough
314
+            return true;
315
+        } else {
316
+            $actualTime = $this->filemtime($path);
317
+            return $actualTime > $time;
318
+        }
319
+    }
320
+
321
+    /**
322
+     * @param string $path
323
+     * @param string $mode
324
+     * @return resource|false
325
+     */
326
+    public function fopen($path, $mode) {
327
+        $fullPath = $this->buildPath($path);
328
+        try {
329
+            switch ($mode) {
330
+                case 'r':
331
+                case 'rb':
332
+                    if (!$this->file_exists($path)) {
333
+                        return false;
334
+                    }
335
+                    return $this->share->read($fullPath);
336
+                case 'w':
337
+                case 'wb':
338
+                    $source = $this->share->write($fullPath);
339
+                    return CallBackWrapper::wrap($source, null, null, function () use ($fullPath) {
340
+                        unset($this->statCache[$fullPath]);
341
+                    });
342
+                case 'a':
343
+                case 'ab':
344
+                case 'r+':
345
+                case 'w+':
346
+                case 'wb+':
347
+                case 'a+':
348
+                case 'x':
349
+                case 'x+':
350
+                case 'c':
351
+                case 'c+':
352
+                    //emulate these
353
+                    if (strrpos($path, '.') !== false) {
354
+                        $ext = substr($path, strrpos($path, '.'));
355
+                    } else {
356
+                        $ext = '';
357
+                    }
358
+                    if ($this->file_exists($path)) {
359
+                        if (!$this->isUpdatable($path)) {
360
+                            return false;
361
+                        }
362
+                        $tmpFile = $this->getCachedFile($path);
363
+                    } else {
364
+                        if (!$this->isCreatable(dirname($path))) {
365
+                            return false;
366
+                        }
367
+                        $tmpFile = \OC::$server->getTempManager()->getTemporaryFile($ext);
368
+                    }
369
+                    $source = fopen($tmpFile, $mode);
370
+                    $share = $this->share;
371
+                    return CallbackWrapper::wrap($source, null, null, function () use ($tmpFile, $fullPath, $share) {
372
+                        unset($this->statCache[$fullPath]);
373
+                        $share->put($tmpFile, $fullPath);
374
+                        unlink($tmpFile);
375
+                    });
376
+            }
377
+            return false;
378
+        } catch (NotFoundException $e) {
379
+            return false;
380
+        } catch (ForbiddenException $e) {
381
+            return false;
382
+        } catch (ConnectException $e) {
383
+            throw new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e);
384
+        }
385
+    }
386
+
387
+    public function rmdir($path) {
388
+        if ($this->isRootDir($path)) {
389
+            return false;
390
+        }
391
+
392
+        try {
393
+            $this->statCache = array();
394
+            $content = $this->share->dir($this->buildPath($path));
395
+            foreach ($content as $file) {
396
+                if ($file->isDirectory()) {
397
+                    $this->rmdir($path . '/' . $file->getName());
398
+                } else {
399
+                    $this->share->del($file->getPath());
400
+                }
401
+            }
402
+            $this->share->rmdir($this->buildPath($path));
403
+            return true;
404
+        } catch (NotFoundException $e) {
405
+            return false;
406
+        } catch (ForbiddenException $e) {
407
+            return false;
408
+        } catch (ConnectException $e) {
409
+            throw new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e);
410
+        }
411
+    }
412
+
413
+    public function touch($path, $time = null) {
414
+        try {
415
+            if (!$this->file_exists($path)) {
416
+                $fh = $this->share->write($this->buildPath($path));
417
+                fclose($fh);
418
+                return true;
419
+            }
420
+            return false;
421
+        } catch (ConnectException $e) {
422
+            throw new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e);
423
+        }
424
+    }
425
+
426
+    public function opendir($path) {
427
+        try {
428
+            $files = $this->getFolderContents($path);
429
+        } catch (NotFoundException $e) {
430
+            return false;
431
+        } catch (ForbiddenException $e) {
432
+            return false;
433
+        }
434
+        $names = array_map(function ($info) {
435
+            /** @var \Icewind\SMB\IFileInfo $info */
436
+            return $info->getName();
437
+        }, $files);
438
+        return IteratorDirectory::wrap($names);
439
+    }
440
+
441
+    public function filetype($path) {
442
+        try {
443
+            return $this->getFileInfo($path)->isDirectory() ? 'dir' : 'file';
444
+        } catch (NotFoundException $e) {
445
+            return false;
446
+        } catch (ForbiddenException $e) {
447
+            return false;
448
+        }
449
+    }
450
+
451
+    public function mkdir($path) {
452
+        $path = $this->buildPath($path);
453
+        try {
454
+            $this->share->mkdir($path);
455
+            return true;
456
+        } catch (ConnectException $e) {
457
+            throw new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e);
458
+        } catch (Exception $e) {
459
+            return false;
460
+        }
461
+    }
462
+
463
+    public function file_exists($path) {
464
+        try {
465
+            $this->getFileInfo($path);
466
+            return true;
467
+        } catch (NotFoundException $e) {
468
+            return false;
469
+        } catch (ForbiddenException $e) {
470
+            return false;
471
+        } catch (ConnectException $e) {
472
+            throw new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e);
473
+        }
474
+    }
475
+
476
+    public function isReadable($path) {
477
+        try {
478
+            $info = $this->getFileInfo($path);
479
+            return !$info->isHidden();
480
+        } catch (NotFoundException $e) {
481
+            return false;
482
+        } catch (ForbiddenException $e) {
483
+            return false;
484
+        }
485
+    }
486
+
487
+    public function isUpdatable($path) {
488
+        try {
489
+            $info = $this->getFileInfo($path);
490
+            // following windows behaviour for read-only folders: they can be written into
491
+            // (https://support.microsoft.com/en-us/kb/326549 - "cause" section)
492
+            return !$info->isHidden() && (!$info->isReadOnly() || $this->is_dir($path));
493
+        } catch (NotFoundException $e) {
494
+            return false;
495
+        } catch (ForbiddenException $e) {
496
+            return false;
497
+        }
498
+    }
499
+
500
+    public function isDeletable($path) {
501
+        try {
502
+            $info = $this->getFileInfo($path);
503
+            return !$info->isHidden() && !$info->isReadOnly();
504
+        } catch (NotFoundException $e) {
505
+            return false;
506
+        } catch (ForbiddenException $e) {
507
+            return false;
508
+        }
509
+    }
510
+
511
+    /**
512
+     * check if smbclient is installed
513
+     */
514
+    public static function checkDependencies() {
515
+        return (
516
+            (bool)\OC_Helper::findBinaryPath('smbclient')
517
+            || NativeServer::available(new System())
518
+        ) ? true : ['smbclient'];
519
+    }
520
+
521
+    /**
522
+     * Test a storage for availability
523
+     *
524
+     * @return bool
525
+     */
526
+    public function test() {
527
+        try {
528
+            return parent::test();
529
+        } catch (Exception $e) {
530
+            return false;
531
+        }
532
+    }
533
+
534
+    public function listen($path, callable $callback) {
535
+        $this->notify($path)->listen(function (IChange $change) use ($callback) {
536
+            if ($change instanceof IRenameChange) {
537
+                return $callback($change->getType(), $change->getPath(), $change->getTargetPath());
538
+            } else {
539
+                return $callback($change->getType(), $change->getPath());
540
+            }
541
+        });
542
+    }
543
+
544
+    public function notify($path) {
545
+        $path = '/' . ltrim($path, '/');
546
+        $shareNotifyHandler = $this->share->notify($this->buildPath($path));
547
+        return new SMBNotifyHandler($shareNotifyHandler, $this->root);
548
+    }
549 549
 }
Please login to merge, or discard this patch.