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