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