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