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