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