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