Completed
Pull Request — master (#9578)
by Robin
42:44 queued 19:09
created
apps/files_external/lib/Lib/Storage/SMB.php 2 patches
Spacing   +13 added lines, -13 removed lines patch added patch discarded remove patch
@@ -85,8 +85,8 @@  discard block
 block discarded – undo
85 85
 			$this->share = $this->server->getShare(trim($params['share'], '/'));
86 86
 
87 87
 			$this->root = $params['root'] ?? '/';
88
-			$this->root = '/' . ltrim($this->root, '/');
89
-			$this->root = rtrim($this->root, '/') . '/';
88
+			$this->root = '/'.ltrim($this->root, '/');
89
+			$this->root = rtrim($this->root, '/').'/';
90 90
 		} else {
91 91
 			throw new \Exception('Invalid configuration');
92 92
 		}
@@ -101,7 +101,7 @@  discard block
 block discarded – undo
101 101
 		// FIXME: double slash to keep compatible with the old storage ids,
102 102
 		// failure to do so will lead to creation of a new storage id and
103 103
 		// loss of shares from the storage
104
-		return 'smb::' . $this->server->getAuth()->getUsername() . '@' . $this->server->getHost() . '//' . $this->share->getName() . '/' . $this->root;
104
+		return 'smb::'.$this->server->getAuth()->getUsername().'@'.$this->server->getHost().'//'.$this->share->getName().'/'.$this->root;
105 105
 	}
106 106
 
107 107
 	/**
@@ -109,7 +109,7 @@  discard block
 block discarded – undo
109 109
 	 * @return string
110 110
 	 */
111 111
 	protected function buildPath($path) {
112
-		return Filesystem::normalizePath($this->root . '/' . $path, true, false, true);
112
+		return Filesystem::normalizePath($this->root.'/'.$path, true, false, true);
113 113
 	}
114 114
 
115 115
 	protected function relativePath($fullPath) {
@@ -149,9 +149,9 @@  discard block
 block discarded – undo
149 149
 			$path = $this->buildPath($path);
150 150
 			$files = $this->share->dir($path);
151 151
 			foreach ($files as $file) {
152
-				$this->statCache[$path . '/' . $file->getName()] = $file;
152
+				$this->statCache[$path.'/'.$file->getName()] = $file;
153 153
 			}
154
-			return array_filter($files, function (IFileInfo $file) {
154
+			return array_filter($files, function(IFileInfo $file) {
155 155
 				try {
156 156
 					return !$file->isHidden();
157 157
 				} catch (ForbiddenException $e) {
@@ -325,7 +325,7 @@  discard block
 block discarded – undo
325 325
 				case 'w':
326 326
 				case 'wb':
327 327
 					$source = $this->share->write($fullPath);
328
-					return CallBackWrapper::wrap($source, null, null, function () use ($fullPath) {
328
+					return CallBackWrapper::wrap($source, null, null, function() use ($fullPath) {
329 329
 						unset($this->statCache[$fullPath]);
330 330
 					});
331 331
 				case 'a':
@@ -357,7 +357,7 @@  discard block
 block discarded – undo
357 357
 					}
358 358
 					$source = fopen($tmpFile, $mode);
359 359
 					$share = $this->share;
360
-					return CallbackWrapper::wrap($source, null, null, function () use ($tmpFile, $fullPath, $share) {
360
+					return CallbackWrapper::wrap($source, null, null, function() use ($tmpFile, $fullPath, $share) {
361 361
 						unset($this->statCache[$fullPath]);
362 362
 						$share->put($tmpFile, $fullPath);
363 363
 						unlink($tmpFile);
@@ -383,7 +383,7 @@  discard block
 block discarded – undo
383 383
 			$content = $this->share->dir($this->buildPath($path));
384 384
 			foreach ($content as $file) {
385 385
 				if ($file->isDirectory()) {
386
-					$this->rmdir($path . '/' . $file->getName());
386
+					$this->rmdir($path.'/'.$file->getName());
387 387
 				} else {
388 388
 					$this->share->del($file->getPath());
389 389
 				}
@@ -420,7 +420,7 @@  discard block
 block discarded – undo
420 420
 		} catch (ForbiddenException $e) {
421 421
 			return false;
422 422
 		}
423
-		$names = array_map(function ($info) {
423
+		$names = array_map(function($info) {
424 424
 			/** @var \Icewind\SMB\IFileInfo $info */
425 425
 			return $info->getName();
426 426
 		}, $files);
@@ -502,7 +502,7 @@  discard block
 block discarded – undo
502 502
 	 */
503 503
 	public static function checkDependencies() {
504 504
 		return (
505
-			(bool)\OC_Helper::findBinaryPath('smbclient')
505
+			(bool) \OC_Helper::findBinaryPath('smbclient')
506 506
 			|| NativeServer::available(new System())
507 507
 		) ? true : ['smbclient'];
508 508
 	}
@@ -521,7 +521,7 @@  discard block
 block discarded – undo
521 521
 	}
522 522
 
523 523
 	public function listen($path, callable $callback) {
524
-		$this->notify($path)->listen(function (IChange $change) use ($callback) {
524
+		$this->notify($path)->listen(function(IChange $change) use ($callback) {
525 525
 			if ($change instanceof IRenameChange) {
526 526
 				return $callback($change->getType(), $change->getPath(), $change->getTargetPath());
527 527
 			} else {
@@ -531,7 +531,7 @@  discard block
 block discarded – undo
531 531
 	}
532 532
 
533 533
 	public function notify($path) {
534
-		$path = '/' . ltrim($path, '/');
534
+		$path = '/'.ltrim($path, '/');
535 535
 		$shareNotifyHandler = $this->share->notify($this->buildPath($path));
536 536
 		return new SMBNotifyHandler($shareNotifyHandler, $this->root);
537 537
 	}
Please login to merge, or discard this patch.
Indentation   +489 added lines, -489 removed lines patch added patch discarded remove patch
@@ -57,493 +57,493 @@
 block discarded – undo
57 57
 use OCP\ILogger;
58 58
 
59 59
 class SMB extends Common implements INotifyStorage {
60
-	/**
61
-	 * @var \Icewind\SMB\IServer
62
-	 */
63
-	protected $server;
64
-
65
-	/**
66
-	 * @var \Icewind\SMB\IShare
67
-	 */
68
-	protected $share;
69
-
70
-	/**
71
-	 * @var string
72
-	 */
73
-	protected $root;
74
-
75
-	/**
76
-	 * @var \Icewind\SMB\IFileInfo[]
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
-			list($workgroup, $user) = $this->splitUser($params['user']);
83
-			$auth = new BasicAuth($user, $workgroup, $params['password']);
84
-			$serverFactory = new ServerFactory();
85
-			$this->server = $serverFactory->createServer($params['host'], $auth);
86
-			$this->share = $this->server->getShare(trim($params['share'], '/'));
87
-
88
-			$this->root = $params['root'] ?? '/';
89
-			$this->root = '/' . ltrim($this->root, '/');
90
-			$this->root = rtrim($this->root, '/') . '/';
91
-		} else {
92
-			throw new \Exception('Invalid configuration');
93
-		}
94
-		$this->statCache = new CappedMemoryCache();
95
-		parent::__construct($params);
96
-	}
97
-
98
-	private function splitUser($user) {
99
-		if (strpos($user, '/')) {
100
-			return explode('/', $user, 2);
101
-		} elseif (strpos($user, '\\')) {
102
-			return explode('\\', $user);
103
-		} else {
104
-			return [null, $user];
105
-		}
106
-	}
107
-
108
-	/**
109
-	 * @return string
110
-	 */
111
-	public function getId() {
112
-		// FIXME: double slash to keep compatible with the old storage ids,
113
-		// failure to do so will lead to creation of a new storage id and
114
-		// loss of shares from the storage
115
-		return 'smb::' . $this->server->getAuth()->getUsername() . '@' . $this->server->getHost() . '//' . $this->share->getName() . '/' . $this->root;
116
-	}
117
-
118
-	/**
119
-	 * @param string $path
120
-	 * @return string
121
-	 */
122
-	protected function buildPath($path) {
123
-		return Filesystem::normalizePath($this->root . '/' . $path, true, false, true);
124
-	}
125
-
126
-	protected function relativePath($fullPath) {
127
-		if ($fullPath === $this->root) {
128
-			return '';
129
-		} else if (substr($fullPath, 0, strlen($this->root)) === $this->root) {
130
-			return substr($fullPath, strlen($this->root));
131
-		} else {
132
-			return null;
133
-		}
134
-	}
135
-
136
-	/**
137
-	 * @param string $path
138
-	 * @return \Icewind\SMB\IFileInfo
139
-	 * @throws StorageNotAvailableException
140
-	 */
141
-	protected function getFileInfo($path) {
142
-		try {
143
-			$path = $this->buildPath($path);
144
-			if (!isset($this->statCache[$path])) {
145
-				$this->statCache[$path] = $this->share->stat($path);
146
-			}
147
-			return $this->statCache[$path];
148
-		} catch (ConnectException $e) {
149
-			throw new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e);
150
-		}
151
-	}
152
-
153
-	/**
154
-	 * @param string $path
155
-	 * @return \Icewind\SMB\IFileInfo[]
156
-	 * @throws StorageNotAvailableException
157
-	 */
158
-	protected function getFolderContents($path) {
159
-		try {
160
-			$path = $this->buildPath($path);
161
-			$files = $this->share->dir($path);
162
-			foreach ($files as $file) {
163
-				$this->statCache[$path . '/' . $file->getName()] = $file;
164
-			}
165
-			return array_filter($files, function (IFileInfo $file) {
166
-				try {
167
-					return !$file->isHidden();
168
-				} catch (ForbiddenException $e) {
169
-					return false;
170
-				} catch (NotFoundException $e) {
171
-					return false;
172
-				}
173
-			});
174
-		} catch (ConnectException $e) {
175
-			throw new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e);
176
-		}
177
-	}
178
-
179
-	/**
180
-	 * @param \Icewind\SMB\IFileInfo $info
181
-	 * @return array
182
-	 */
183
-	protected function formatInfo($info) {
184
-		$result = [
185
-			'size' => $info->getSize(),
186
-			'mtime' => $info->getMTime(),
187
-		];
188
-		if ($info->isDirectory()) {
189
-			$result['type'] = 'dir';
190
-		} else {
191
-			$result['type'] = 'file';
192
-		}
193
-		return $result;
194
-	}
195
-
196
-	/**
197
-	 * Rename the files. If the source or the target is the root, the rename won't happen.
198
-	 *
199
-	 * @param string $source the old name of the path
200
-	 * @param string $target the new name of the path
201
-	 * @return bool true if the rename is successful, false otherwise
202
-	 */
203
-	public function rename($source, $target) {
204
-		if ($this->isRootDir($source) || $this->isRootDir($target)) {
205
-			return false;
206
-		}
207
-
208
-		$absoluteSource = $this->buildPath($source);
209
-		$absoluteTarget = $this->buildPath($target);
210
-		try {
211
-			$result = $this->share->rename($absoluteSource, $absoluteTarget);
212
-		} catch (AlreadyExistsException $e) {
213
-			$this->remove($target);
214
-			$result = $this->share->rename($absoluteSource, $absoluteTarget);
215
-		} catch (\Exception $e) {
216
-			\OC::$server->getLogger()->logException($e, ['level' => ILogger::WARN]);
217
-			return false;
218
-		}
219
-		unset($this->statCache[$absoluteSource], $this->statCache[$absoluteTarget]);
220
-		return $result;
221
-	}
222
-
223
-	public function stat($path) {
224
-		try {
225
-			$result = $this->formatInfo($this->getFileInfo($path));
226
-		} catch (ForbiddenException $e) {
227
-			return false;
228
-		} catch (NotFoundException $e) {
229
-			return false;
230
-		}
231
-		if ($this->remoteIsShare() && $this->isRootDir($path)) {
232
-			$result['mtime'] = $this->shareMTime();
233
-		}
234
-		return $result;
235
-	}
236
-
237
-	/**
238
-	 * get the best guess for the modification time of the share
239
-	 *
240
-	 * @return int
241
-	 */
242
-	private function shareMTime() {
243
-		$highestMTime = 0;
244
-		$files = $this->share->dir($this->root);
245
-		foreach ($files as $fileInfo) {
246
-			try {
247
-				if ($fileInfo->getMTime() > $highestMTime) {
248
-					$highestMTime = $fileInfo->getMTime();
249
-				}
250
-			} catch (NotFoundException $e) {
251
-				// Ignore this, can happen on unavailable DFS shares
252
-			}
253
-		}
254
-		return $highestMTime;
255
-	}
256
-
257
-	/**
258
-	 * Check if the path is our root dir (not the smb one)
259
-	 *
260
-	 * @param string $path the path
261
-	 * @return bool
262
-	 */
263
-	private function isRootDir($path) {
264
-		return $path === '' || $path === '/' || $path === '.';
265
-	}
266
-
267
-	/**
268
-	 * Check if our root points to a smb share
269
-	 *
270
-	 * @return bool true if our root points to a share false otherwise
271
-	 */
272
-	private function remoteIsShare() {
273
-		return $this->share->getName() && (!$this->root || $this->root === '/');
274
-	}
275
-
276
-	/**
277
-	 * @param string $path
278
-	 * @return bool
279
-	 */
280
-	public function unlink($path) {
281
-		if ($this->isRootDir($path)) {
282
-			return false;
283
-		}
284
-
285
-		try {
286
-			if ($this->is_dir($path)) {
287
-				return $this->rmdir($path);
288
-			} else {
289
-				$path = $this->buildPath($path);
290
-				unset($this->statCache[$path]);
291
-				$this->share->del($path);
292
-				return true;
293
-			}
294
-		} catch (NotFoundException $e) {
295
-			return false;
296
-		} catch (ForbiddenException $e) {
297
-			return false;
298
-		} catch (ConnectException $e) {
299
-			throw new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e);
300
-		}
301
-	}
302
-
303
-	/**
304
-	 * check if a file or folder has been updated since $time
305
-	 *
306
-	 * @param string $path
307
-	 * @param int $time
308
-	 * @return bool
309
-	 */
310
-	public function hasUpdated($path, $time) {
311
-		if (!$path and $this->root === '/') {
312
-			// mtime doesn't work for shares, but giving the nature of the backend,
313
-			// doing a full update is still just fast enough
314
-			return true;
315
-		} else {
316
-			$actualTime = $this->filemtime($path);
317
-			return $actualTime > $time;
318
-		}
319
-	}
320
-
321
-	/**
322
-	 * @param string $path
323
-	 * @param string $mode
324
-	 * @return resource|false
325
-	 */
326
-	public function fopen($path, $mode) {
327
-		$fullPath = $this->buildPath($path);
328
-		try {
329
-			switch ($mode) {
330
-				case 'r':
331
-				case 'rb':
332
-					if (!$this->file_exists($path)) {
333
-						return false;
334
-					}
335
-					return $this->share->read($fullPath);
336
-				case 'w':
337
-				case 'wb':
338
-					$source = $this->share->write($fullPath);
339
-					return CallBackWrapper::wrap($source, null, null, function () use ($fullPath) {
340
-						unset($this->statCache[$fullPath]);
341
-					});
342
-				case 'a':
343
-				case 'ab':
344
-				case 'r+':
345
-				case 'w+':
346
-				case 'wb+':
347
-				case 'a+':
348
-				case 'x':
349
-				case 'x+':
350
-				case 'c':
351
-				case 'c+':
352
-					//emulate these
353
-					if (strrpos($path, '.') !== false) {
354
-						$ext = substr($path, strrpos($path, '.'));
355
-					} else {
356
-						$ext = '';
357
-					}
358
-					if ($this->file_exists($path)) {
359
-						if (!$this->isUpdatable($path)) {
360
-							return false;
361
-						}
362
-						$tmpFile = $this->getCachedFile($path);
363
-					} else {
364
-						if (!$this->isCreatable(dirname($path))) {
365
-							return false;
366
-						}
367
-						$tmpFile = \OC::$server->getTempManager()->getTemporaryFile($ext);
368
-					}
369
-					$source = fopen($tmpFile, $mode);
370
-					$share = $this->share;
371
-					return CallbackWrapper::wrap($source, null, null, function () use ($tmpFile, $fullPath, $share) {
372
-						unset($this->statCache[$fullPath]);
373
-						$share->put($tmpFile, $fullPath);
374
-						unlink($tmpFile);
375
-					});
376
-			}
377
-			return false;
378
-		} catch (NotFoundException $e) {
379
-			return false;
380
-		} catch (ForbiddenException $e) {
381
-			return false;
382
-		} catch (ConnectException $e) {
383
-			throw new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e);
384
-		}
385
-	}
386
-
387
-	public function rmdir($path) {
388
-		if ($this->isRootDir($path)) {
389
-			return false;
390
-		}
391
-
392
-		try {
393
-			$this->statCache = array();
394
-			$content = $this->share->dir($this->buildPath($path));
395
-			foreach ($content as $file) {
396
-				if ($file->isDirectory()) {
397
-					$this->rmdir($path . '/' . $file->getName());
398
-				} else {
399
-					$this->share->del($file->getPath());
400
-				}
401
-			}
402
-			$this->share->rmdir($this->buildPath($path));
403
-			return true;
404
-		} catch (NotFoundException $e) {
405
-			return false;
406
-		} catch (ForbiddenException $e) {
407
-			return false;
408
-		} catch (ConnectException $e) {
409
-			throw new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e);
410
-		}
411
-	}
412
-
413
-	public function touch($path, $time = null) {
414
-		try {
415
-			if (!$this->file_exists($path)) {
416
-				$fh = $this->share->write($this->buildPath($path));
417
-				fclose($fh);
418
-				return true;
419
-			}
420
-			return false;
421
-		} catch (ConnectException $e) {
422
-			throw new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e);
423
-		}
424
-	}
425
-
426
-	public function opendir($path) {
427
-		try {
428
-			$files = $this->getFolderContents($path);
429
-		} catch (NotFoundException $e) {
430
-			return false;
431
-		} catch (ForbiddenException $e) {
432
-			return false;
433
-		}
434
-		$names = array_map(function ($info) {
435
-			/** @var \Icewind\SMB\IFileInfo $info */
436
-			return $info->getName();
437
-		}, $files);
438
-		return IteratorDirectory::wrap($names);
439
-	}
440
-
441
-	public function filetype($path) {
442
-		try {
443
-			return $this->getFileInfo($path)->isDirectory() ? 'dir' : 'file';
444
-		} catch (NotFoundException $e) {
445
-			return false;
446
-		} catch (ForbiddenException $e) {
447
-			return false;
448
-		}
449
-	}
450
-
451
-	public function mkdir($path) {
452
-		$path = $this->buildPath($path);
453
-		try {
454
-			$this->share->mkdir($path);
455
-			return true;
456
-		} catch (ConnectException $e) {
457
-			throw new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e);
458
-		} catch (Exception $e) {
459
-			return false;
460
-		}
461
-	}
462
-
463
-	public function file_exists($path) {
464
-		try {
465
-			$this->getFileInfo($path);
466
-			return true;
467
-		} catch (NotFoundException $e) {
468
-			return false;
469
-		} catch (ForbiddenException $e) {
470
-			return false;
471
-		} catch (ConnectException $e) {
472
-			throw new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e);
473
-		}
474
-	}
475
-
476
-	public function isReadable($path) {
477
-		try {
478
-			$info = $this->getFileInfo($path);
479
-			return !$info->isHidden();
480
-		} catch (NotFoundException $e) {
481
-			return false;
482
-		} catch (ForbiddenException $e) {
483
-			return false;
484
-		}
485
-	}
486
-
487
-	public function isUpdatable($path) {
488
-		try {
489
-			$info = $this->getFileInfo($path);
490
-			// following windows behaviour for read-only folders: they can be written into
491
-			// (https://support.microsoft.com/en-us/kb/326549 - "cause" section)
492
-			return !$info->isHidden() && (!$info->isReadOnly() || $this->is_dir($path));
493
-		} catch (NotFoundException $e) {
494
-			return false;
495
-		} catch (ForbiddenException $e) {
496
-			return false;
497
-		}
498
-	}
499
-
500
-	public function isDeletable($path) {
501
-		try {
502
-			$info = $this->getFileInfo($path);
503
-			return !$info->isHidden() && !$info->isReadOnly();
504
-		} catch (NotFoundException $e) {
505
-			return false;
506
-		} catch (ForbiddenException $e) {
507
-			return false;
508
-		}
509
-	}
510
-
511
-	/**
512
-	 * check if smbclient is installed
513
-	 */
514
-	public static function checkDependencies() {
515
-		return (
516
-			(bool)\OC_Helper::findBinaryPath('smbclient')
517
-			|| NativeServer::available(new System())
518
-		) ? true : ['smbclient'];
519
-	}
520
-
521
-	/**
522
-	 * Test a storage for availability
523
-	 *
524
-	 * @return bool
525
-	 */
526
-	public function test() {
527
-		try {
528
-			return parent::test();
529
-		} catch (Exception $e) {
530
-			return false;
531
-		}
532
-	}
533
-
534
-	public function listen($path, callable $callback) {
535
-		$this->notify($path)->listen(function (IChange $change) use ($callback) {
536
-			if ($change instanceof IRenameChange) {
537
-				return $callback($change->getType(), $change->getPath(), $change->getTargetPath());
538
-			} else {
539
-				return $callback($change->getType(), $change->getPath());
540
-			}
541
-		});
542
-	}
543
-
544
-	public function notify($path) {
545
-		$path = '/' . ltrim($path, '/');
546
-		$shareNotifyHandler = $this->share->notify($this->buildPath($path));
547
-		return new SMBNotifyHandler($shareNotifyHandler, $this->root);
548
-	}
60
+    /**
61
+     * @var \Icewind\SMB\IServer
62
+     */
63
+    protected $server;
64
+
65
+    /**
66
+     * @var \Icewind\SMB\IShare
67
+     */
68
+    protected $share;
69
+
70
+    /**
71
+     * @var string
72
+     */
73
+    protected $root;
74
+
75
+    /**
76
+     * @var \Icewind\SMB\IFileInfo[]
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
+            list($workgroup, $user) = $this->splitUser($params['user']);
83
+            $auth = new BasicAuth($user, $workgroup, $params['password']);
84
+            $serverFactory = new ServerFactory();
85
+            $this->server = $serverFactory->createServer($params['host'], $auth);
86
+            $this->share = $this->server->getShare(trim($params['share'], '/'));
87
+
88
+            $this->root = $params['root'] ?? '/';
89
+            $this->root = '/' . ltrim($this->root, '/');
90
+            $this->root = rtrim($this->root, '/') . '/';
91
+        } else {
92
+            throw new \Exception('Invalid configuration');
93
+        }
94
+        $this->statCache = new CappedMemoryCache();
95
+        parent::__construct($params);
96
+    }
97
+
98
+    private function splitUser($user) {
99
+        if (strpos($user, '/')) {
100
+            return explode('/', $user, 2);
101
+        } elseif (strpos($user, '\\')) {
102
+            return explode('\\', $user);
103
+        } else {
104
+            return [null, $user];
105
+        }
106
+    }
107
+
108
+    /**
109
+     * @return string
110
+     */
111
+    public function getId() {
112
+        // FIXME: double slash to keep compatible with the old storage ids,
113
+        // failure to do so will lead to creation of a new storage id and
114
+        // loss of shares from the storage
115
+        return 'smb::' . $this->server->getAuth()->getUsername() . '@' . $this->server->getHost() . '//' . $this->share->getName() . '/' . $this->root;
116
+    }
117
+
118
+    /**
119
+     * @param string $path
120
+     * @return string
121
+     */
122
+    protected function buildPath($path) {
123
+        return Filesystem::normalizePath($this->root . '/' . $path, true, false, true);
124
+    }
125
+
126
+    protected function relativePath($fullPath) {
127
+        if ($fullPath === $this->root) {
128
+            return '';
129
+        } else if (substr($fullPath, 0, strlen($this->root)) === $this->root) {
130
+            return substr($fullPath, strlen($this->root));
131
+        } else {
132
+            return null;
133
+        }
134
+    }
135
+
136
+    /**
137
+     * @param string $path
138
+     * @return \Icewind\SMB\IFileInfo
139
+     * @throws StorageNotAvailableException
140
+     */
141
+    protected function getFileInfo($path) {
142
+        try {
143
+            $path = $this->buildPath($path);
144
+            if (!isset($this->statCache[$path])) {
145
+                $this->statCache[$path] = $this->share->stat($path);
146
+            }
147
+            return $this->statCache[$path];
148
+        } catch (ConnectException $e) {
149
+            throw new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e);
150
+        }
151
+    }
152
+
153
+    /**
154
+     * @param string $path
155
+     * @return \Icewind\SMB\IFileInfo[]
156
+     * @throws StorageNotAvailableException
157
+     */
158
+    protected function getFolderContents($path) {
159
+        try {
160
+            $path = $this->buildPath($path);
161
+            $files = $this->share->dir($path);
162
+            foreach ($files as $file) {
163
+                $this->statCache[$path . '/' . $file->getName()] = $file;
164
+            }
165
+            return array_filter($files, function (IFileInfo $file) {
166
+                try {
167
+                    return !$file->isHidden();
168
+                } catch (ForbiddenException $e) {
169
+                    return false;
170
+                } catch (NotFoundException $e) {
171
+                    return false;
172
+                }
173
+            });
174
+        } catch (ConnectException $e) {
175
+            throw new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e);
176
+        }
177
+    }
178
+
179
+    /**
180
+     * @param \Icewind\SMB\IFileInfo $info
181
+     * @return array
182
+     */
183
+    protected function formatInfo($info) {
184
+        $result = [
185
+            'size' => $info->getSize(),
186
+            'mtime' => $info->getMTime(),
187
+        ];
188
+        if ($info->isDirectory()) {
189
+            $result['type'] = 'dir';
190
+        } else {
191
+            $result['type'] = 'file';
192
+        }
193
+        return $result;
194
+    }
195
+
196
+    /**
197
+     * Rename the files. If the source or the target is the root, the rename won't happen.
198
+     *
199
+     * @param string $source the old name of the path
200
+     * @param string $target the new name of the path
201
+     * @return bool true if the rename is successful, false otherwise
202
+     */
203
+    public function rename($source, $target) {
204
+        if ($this->isRootDir($source) || $this->isRootDir($target)) {
205
+            return false;
206
+        }
207
+
208
+        $absoluteSource = $this->buildPath($source);
209
+        $absoluteTarget = $this->buildPath($target);
210
+        try {
211
+            $result = $this->share->rename($absoluteSource, $absoluteTarget);
212
+        } catch (AlreadyExistsException $e) {
213
+            $this->remove($target);
214
+            $result = $this->share->rename($absoluteSource, $absoluteTarget);
215
+        } catch (\Exception $e) {
216
+            \OC::$server->getLogger()->logException($e, ['level' => ILogger::WARN]);
217
+            return false;
218
+        }
219
+        unset($this->statCache[$absoluteSource], $this->statCache[$absoluteTarget]);
220
+        return $result;
221
+    }
222
+
223
+    public function stat($path) {
224
+        try {
225
+            $result = $this->formatInfo($this->getFileInfo($path));
226
+        } catch (ForbiddenException $e) {
227
+            return false;
228
+        } catch (NotFoundException $e) {
229
+            return false;
230
+        }
231
+        if ($this->remoteIsShare() && $this->isRootDir($path)) {
232
+            $result['mtime'] = $this->shareMTime();
233
+        }
234
+        return $result;
235
+    }
236
+
237
+    /**
238
+     * get the best guess for the modification time of the share
239
+     *
240
+     * @return int
241
+     */
242
+    private function shareMTime() {
243
+        $highestMTime = 0;
244
+        $files = $this->share->dir($this->root);
245
+        foreach ($files as $fileInfo) {
246
+            try {
247
+                if ($fileInfo->getMTime() > $highestMTime) {
248
+                    $highestMTime = $fileInfo->getMTime();
249
+                }
250
+            } catch (NotFoundException $e) {
251
+                // Ignore this, can happen on unavailable DFS shares
252
+            }
253
+        }
254
+        return $highestMTime;
255
+    }
256
+
257
+    /**
258
+     * Check if the path is our root dir (not the smb one)
259
+     *
260
+     * @param string $path the path
261
+     * @return bool
262
+     */
263
+    private function isRootDir($path) {
264
+        return $path === '' || $path === '/' || $path === '.';
265
+    }
266
+
267
+    /**
268
+     * Check if our root points to a smb share
269
+     *
270
+     * @return bool true if our root points to a share false otherwise
271
+     */
272
+    private function remoteIsShare() {
273
+        return $this->share->getName() && (!$this->root || $this->root === '/');
274
+    }
275
+
276
+    /**
277
+     * @param string $path
278
+     * @return bool
279
+     */
280
+    public function unlink($path) {
281
+        if ($this->isRootDir($path)) {
282
+            return false;
283
+        }
284
+
285
+        try {
286
+            if ($this->is_dir($path)) {
287
+                return $this->rmdir($path);
288
+            } else {
289
+                $path = $this->buildPath($path);
290
+                unset($this->statCache[$path]);
291
+                $this->share->del($path);
292
+                return true;
293
+            }
294
+        } catch (NotFoundException $e) {
295
+            return false;
296
+        } catch (ForbiddenException $e) {
297
+            return false;
298
+        } catch (ConnectException $e) {
299
+            throw new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e);
300
+        }
301
+    }
302
+
303
+    /**
304
+     * check if a file or folder has been updated since $time
305
+     *
306
+     * @param string $path
307
+     * @param int $time
308
+     * @return bool
309
+     */
310
+    public function hasUpdated($path, $time) {
311
+        if (!$path and $this->root === '/') {
312
+            // mtime doesn't work for shares, but giving the nature of the backend,
313
+            // doing a full update is still just fast enough
314
+            return true;
315
+        } else {
316
+            $actualTime = $this->filemtime($path);
317
+            return $actualTime > $time;
318
+        }
319
+    }
320
+
321
+    /**
322
+     * @param string $path
323
+     * @param string $mode
324
+     * @return resource|false
325
+     */
326
+    public function fopen($path, $mode) {
327
+        $fullPath = $this->buildPath($path);
328
+        try {
329
+            switch ($mode) {
330
+                case 'r':
331
+                case 'rb':
332
+                    if (!$this->file_exists($path)) {
333
+                        return false;
334
+                    }
335
+                    return $this->share->read($fullPath);
336
+                case 'w':
337
+                case 'wb':
338
+                    $source = $this->share->write($fullPath);
339
+                    return CallBackWrapper::wrap($source, null, null, function () use ($fullPath) {
340
+                        unset($this->statCache[$fullPath]);
341
+                    });
342
+                case 'a':
343
+                case 'ab':
344
+                case 'r+':
345
+                case 'w+':
346
+                case 'wb+':
347
+                case 'a+':
348
+                case 'x':
349
+                case 'x+':
350
+                case 'c':
351
+                case 'c+':
352
+                    //emulate these
353
+                    if (strrpos($path, '.') !== false) {
354
+                        $ext = substr($path, strrpos($path, '.'));
355
+                    } else {
356
+                        $ext = '';
357
+                    }
358
+                    if ($this->file_exists($path)) {
359
+                        if (!$this->isUpdatable($path)) {
360
+                            return false;
361
+                        }
362
+                        $tmpFile = $this->getCachedFile($path);
363
+                    } else {
364
+                        if (!$this->isCreatable(dirname($path))) {
365
+                            return false;
366
+                        }
367
+                        $tmpFile = \OC::$server->getTempManager()->getTemporaryFile($ext);
368
+                    }
369
+                    $source = fopen($tmpFile, $mode);
370
+                    $share = $this->share;
371
+                    return CallbackWrapper::wrap($source, null, null, function () use ($tmpFile, $fullPath, $share) {
372
+                        unset($this->statCache[$fullPath]);
373
+                        $share->put($tmpFile, $fullPath);
374
+                        unlink($tmpFile);
375
+                    });
376
+            }
377
+            return false;
378
+        } catch (NotFoundException $e) {
379
+            return false;
380
+        } catch (ForbiddenException $e) {
381
+            return false;
382
+        } catch (ConnectException $e) {
383
+            throw new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e);
384
+        }
385
+    }
386
+
387
+    public function rmdir($path) {
388
+        if ($this->isRootDir($path)) {
389
+            return false;
390
+        }
391
+
392
+        try {
393
+            $this->statCache = array();
394
+            $content = $this->share->dir($this->buildPath($path));
395
+            foreach ($content as $file) {
396
+                if ($file->isDirectory()) {
397
+                    $this->rmdir($path . '/' . $file->getName());
398
+                } else {
399
+                    $this->share->del($file->getPath());
400
+                }
401
+            }
402
+            $this->share->rmdir($this->buildPath($path));
403
+            return true;
404
+        } catch (NotFoundException $e) {
405
+            return false;
406
+        } catch (ForbiddenException $e) {
407
+            return false;
408
+        } catch (ConnectException $e) {
409
+            throw new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e);
410
+        }
411
+    }
412
+
413
+    public function touch($path, $time = null) {
414
+        try {
415
+            if (!$this->file_exists($path)) {
416
+                $fh = $this->share->write($this->buildPath($path));
417
+                fclose($fh);
418
+                return true;
419
+            }
420
+            return false;
421
+        } catch (ConnectException $e) {
422
+            throw new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e);
423
+        }
424
+    }
425
+
426
+    public function opendir($path) {
427
+        try {
428
+            $files = $this->getFolderContents($path);
429
+        } catch (NotFoundException $e) {
430
+            return false;
431
+        } catch (ForbiddenException $e) {
432
+            return false;
433
+        }
434
+        $names = array_map(function ($info) {
435
+            /** @var \Icewind\SMB\IFileInfo $info */
436
+            return $info->getName();
437
+        }, $files);
438
+        return IteratorDirectory::wrap($names);
439
+    }
440
+
441
+    public function filetype($path) {
442
+        try {
443
+            return $this->getFileInfo($path)->isDirectory() ? 'dir' : 'file';
444
+        } catch (NotFoundException $e) {
445
+            return false;
446
+        } catch (ForbiddenException $e) {
447
+            return false;
448
+        }
449
+    }
450
+
451
+    public function mkdir($path) {
452
+        $path = $this->buildPath($path);
453
+        try {
454
+            $this->share->mkdir($path);
455
+            return true;
456
+        } catch (ConnectException $e) {
457
+            throw new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e);
458
+        } catch (Exception $e) {
459
+            return false;
460
+        }
461
+    }
462
+
463
+    public function file_exists($path) {
464
+        try {
465
+            $this->getFileInfo($path);
466
+            return true;
467
+        } catch (NotFoundException $e) {
468
+            return false;
469
+        } catch (ForbiddenException $e) {
470
+            return false;
471
+        } catch (ConnectException $e) {
472
+            throw new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e);
473
+        }
474
+    }
475
+
476
+    public function isReadable($path) {
477
+        try {
478
+            $info = $this->getFileInfo($path);
479
+            return !$info->isHidden();
480
+        } catch (NotFoundException $e) {
481
+            return false;
482
+        } catch (ForbiddenException $e) {
483
+            return false;
484
+        }
485
+    }
486
+
487
+    public function isUpdatable($path) {
488
+        try {
489
+            $info = $this->getFileInfo($path);
490
+            // following windows behaviour for read-only folders: they can be written into
491
+            // (https://support.microsoft.com/en-us/kb/326549 - "cause" section)
492
+            return !$info->isHidden() && (!$info->isReadOnly() || $this->is_dir($path));
493
+        } catch (NotFoundException $e) {
494
+            return false;
495
+        } catch (ForbiddenException $e) {
496
+            return false;
497
+        }
498
+    }
499
+
500
+    public function isDeletable($path) {
501
+        try {
502
+            $info = $this->getFileInfo($path);
503
+            return !$info->isHidden() && !$info->isReadOnly();
504
+        } catch (NotFoundException $e) {
505
+            return false;
506
+        } catch (ForbiddenException $e) {
507
+            return false;
508
+        }
509
+    }
510
+
511
+    /**
512
+     * check if smbclient is installed
513
+     */
514
+    public static function checkDependencies() {
515
+        return (
516
+            (bool)\OC_Helper::findBinaryPath('smbclient')
517
+            || NativeServer::available(new System())
518
+        ) ? true : ['smbclient'];
519
+    }
520
+
521
+    /**
522
+     * Test a storage for availability
523
+     *
524
+     * @return bool
525
+     */
526
+    public function test() {
527
+        try {
528
+            return parent::test();
529
+        } catch (Exception $e) {
530
+            return false;
531
+        }
532
+    }
533
+
534
+    public function listen($path, callable $callback) {
535
+        $this->notify($path)->listen(function (IChange $change) use ($callback) {
536
+            if ($change instanceof IRenameChange) {
537
+                return $callback($change->getType(), $change->getPath(), $change->getTargetPath());
538
+            } else {
539
+                return $callback($change->getType(), $change->getPath());
540
+            }
541
+        });
542
+    }
543
+
544
+    public function notify($path) {
545
+        $path = '/' . ltrim($path, '/');
546
+        $shareNotifyHandler = $this->share->notify($this->buildPath($path));
547
+        return new SMBNotifyHandler($shareNotifyHandler, $this->root);
548
+    }
549 549
 }
Please login to merge, or discard this patch.
apps/files_external/lib/Service/StoragesService.php 1 patch
Indentation   +490 added lines, -490 removed lines patch added patch discarded remove patch
@@ -44,494 +44,494 @@
 block discarded – undo
44 44
  */
45 45
 abstract class StoragesService {
46 46
 
47
-	/** @var BackendService */
48
-	protected $backendService;
49
-
50
-	/**
51
-	 * @var DBConfigService
52
-	 */
53
-	protected $dbConfig;
54
-
55
-	/**
56
-	 * @var IUserMountCache
57
-	 */
58
-	protected $userMountCache;
59
-
60
-	/**
61
-	 * @param BackendService $backendService
62
-	 * @param DBConfigService $dbConfigService
63
-	 * @param IUserMountCache $userMountCache
64
-	 */
65
-	public function __construct(BackendService $backendService, DBConfigService $dbConfigService, IUserMountCache $userMountCache) {
66
-		$this->backendService = $backendService;
67
-		$this->dbConfig = $dbConfigService;
68
-		$this->userMountCache = $userMountCache;
69
-	}
70
-
71
-	protected function readDBConfig() {
72
-		return $this->dbConfig->getAdminMounts();
73
-	}
74
-
75
-	protected function getStorageConfigFromDBMount(array $mount) {
76
-		$applicableUsers = array_filter($mount['applicable'], function ($applicable) {
77
-			return $applicable['type'] === DBConfigService::APPLICABLE_TYPE_USER;
78
-		});
79
-		$applicableUsers = array_map(function ($applicable) {
80
-			return $applicable['value'];
81
-		}, $applicableUsers);
82
-
83
-		$applicableGroups = array_filter($mount['applicable'], function ($applicable) {
84
-			return $applicable['type'] === DBConfigService::APPLICABLE_TYPE_GROUP;
85
-		});
86
-		$applicableGroups = array_map(function ($applicable) {
87
-			return $applicable['value'];
88
-		}, $applicableGroups);
89
-
90
-		try {
91
-			$config = $this->createStorage(
92
-				$mount['mount_point'],
93
-				$mount['storage_backend'],
94
-				$mount['auth_backend'],
95
-				$mount['config'],
96
-				$mount['options'],
97
-				array_values($applicableUsers),
98
-				array_values($applicableGroups),
99
-				$mount['priority']
100
-			);
101
-			$config->setType($mount['type']);
102
-			$config->setId((int)$mount['mount_id']);
103
-			return $config;
104
-		} catch (\UnexpectedValueException $e) {
105
-			// don't die if a storage backend doesn't exist
106
-			\OC::$server->getLogger()->logException($e, [
107
-				'message' => 'Could not load storage.',
108
-				'level' => ILogger::ERROR,
109
-				'app' => 'files_external',
110
-			]);
111
-			return null;
112
-		} catch (\InvalidArgumentException $e) {
113
-			\OC::$server->getLogger()->logException($e, [
114
-				'message' => 'Could not load storage.',
115
-				'level' => ILogger::ERROR,
116
-				'app' => 'files_external',
117
-			]);
118
-			return null;
119
-		}
120
-	}
121
-
122
-	/**
123
-	 * Read the external storages config
124
-	 *
125
-	 * @return array map of storage id to storage config
126
-	 */
127
-	protected function readConfig() {
128
-		$mounts = $this->readDBConfig();
129
-		$configs = array_map([$this, 'getStorageConfigFromDBMount'], $mounts);
130
-		$configs = array_filter($configs, function ($config) {
131
-			return $config instanceof StorageConfig;
132
-		});
133
-
134
-		$keys = array_map(function (StorageConfig $config) {
135
-			return $config->getId();
136
-		}, $configs);
137
-
138
-		return array_combine($keys, $configs);
139
-	}
140
-
141
-	/**
142
-	 * Get a storage with status
143
-	 *
144
-	 * @param int $id storage id
145
-	 *
146
-	 * @return StorageConfig
147
-	 * @throws NotFoundException if the storage with the given id was not found
148
-	 */
149
-	public function getStorage($id) {
150
-		$mount = $this->dbConfig->getMountById($id);
151
-
152
-		if (!is_array($mount)) {
153
-			throw new NotFoundException('Storage with ID "' . $id . '" not found');
154
-		}
155
-
156
-		$config = $this->getStorageConfigFromDBMount($mount);
157
-		if ($this->isApplicable($config)) {
158
-			return $config;
159
-		} else {
160
-			throw new NotFoundException('Storage with ID "' . $id . '" not found');
161
-		}
162
-	}
163
-
164
-	/**
165
-	 * Check whether this storage service should provide access to a storage
166
-	 *
167
-	 * @param StorageConfig $config
168
-	 * @return bool
169
-	 */
170
-	abstract protected function isApplicable(StorageConfig $config);
171
-
172
-	/**
173
-	 * Gets all storages, valid or not
174
-	 *
175
-	 * @return StorageConfig[] array of storage configs
176
-	 */
177
-	public function getAllStorages() {
178
-		return $this->readConfig();
179
-	}
180
-
181
-	/**
182
-	 * Gets all valid storages
183
-	 *
184
-	 * @return StorageConfig[]
185
-	 */
186
-	public function getStorages() {
187
-		return array_filter($this->getAllStorages(), [$this, 'validateStorage']);
188
-	}
189
-
190
-	/**
191
-	 * Validate storage
192
-	 * FIXME: De-duplicate with StoragesController::validate()
193
-	 *
194
-	 * @param StorageConfig $storage
195
-	 * @return bool
196
-	 */
197
-	protected function validateStorage(StorageConfig $storage) {
198
-		/** @var Backend */
199
-		$backend = $storage->getBackend();
200
-		/** @var AuthMechanism */
201
-		$authMechanism = $storage->getAuthMechanism();
202
-
203
-		if (!$backend->isVisibleFor($this->getVisibilityType())) {
204
-			// not permitted to use backend
205
-			return false;
206
-		}
207
-		if (!$authMechanism->isVisibleFor($this->getVisibilityType())) {
208
-			// not permitted to use auth mechanism
209
-			return false;
210
-		}
211
-
212
-		return true;
213
-	}
214
-
215
-	/**
216
-	 * Get the visibility type for this controller, used in validation
217
-	 *
218
-	 * @return string BackendService::VISIBILITY_* constants
219
-	 */
220
-	abstract public function getVisibilityType();
221
-
222
-	/**
223
-	 * @return integer
224
-	 */
225
-	protected function getType() {
226
-		return DBConfigService::MOUNT_TYPE_ADMIN;
227
-	}
228
-
229
-	/**
230
-	 * Add new storage to the configuration
231
-	 *
232
-	 * @param StorageConfig $newStorage storage attributes
233
-	 *
234
-	 * @return StorageConfig storage config, with added id
235
-	 */
236
-	public function addStorage(StorageConfig $newStorage) {
237
-		$allStorages = $this->readConfig();
238
-
239
-		$configId = $this->dbConfig->addMount(
240
-			$newStorage->getMountPoint(),
241
-			$newStorage->getBackend()->getIdentifier(),
242
-			$newStorage->getAuthMechanism()->getIdentifier(),
243
-			$newStorage->getPriority(),
244
-			$this->getType()
245
-		);
246
-
247
-		$newStorage->setId($configId);
248
-
249
-		foreach ($newStorage->getApplicableUsers() as $user) {
250
-			$this->dbConfig->addApplicable($configId, DBConfigService::APPLICABLE_TYPE_USER, $user);
251
-		}
252
-		foreach ($newStorage->getApplicableGroups() as $group) {
253
-			$this->dbConfig->addApplicable($configId, DBConfigService::APPLICABLE_TYPE_GROUP, $group);
254
-		}
255
-		foreach ($newStorage->getBackendOptions() as $key => $value) {
256
-			$this->dbConfig->setConfig($configId, $key, $value);
257
-		}
258
-		foreach ($newStorage->getMountOptions() as $key => $value) {
259
-			$this->dbConfig->setOption($configId, $key, $value);
260
-		}
261
-
262
-		if (count($newStorage->getApplicableUsers()) === 0 && count($newStorage->getApplicableGroups()) === 0) {
263
-			$this->dbConfig->addApplicable($configId, DBConfigService::APPLICABLE_TYPE_GLOBAL, null);
264
-		}
265
-
266
-		// add new storage
267
-		$allStorages[$configId] = $newStorage;
268
-
269
-		$this->triggerHooks($newStorage, Filesystem::signal_create_mount);
270
-
271
-		$newStorage->setStatus(StorageNotAvailableException::STATUS_SUCCESS);
272
-		return $newStorage;
273
-	}
274
-
275
-	/**
276
-	 * Create a storage from its parameters
277
-	 *
278
-	 * @param string $mountPoint storage mount point
279
-	 * @param string $backendIdentifier backend identifier
280
-	 * @param string $authMechanismIdentifier authentication mechanism identifier
281
-	 * @param array $backendOptions backend-specific options
282
-	 * @param array|null $mountOptions mount-specific options
283
-	 * @param array|null $applicableUsers users for which to mount the storage
284
-	 * @param array|null $applicableGroups groups for which to mount the storage
285
-	 * @param int|null $priority priority
286
-	 *
287
-	 * @return StorageConfig
288
-	 */
289
-	public function createStorage(
290
-		$mountPoint,
291
-		$backendIdentifier,
292
-		$authMechanismIdentifier,
293
-		$backendOptions,
294
-		$mountOptions = null,
295
-		$applicableUsers = null,
296
-		$applicableGroups = null,
297
-		$priority = null
298
-	) {
299
-		$backend = $this->backendService->getBackend($backendIdentifier);
300
-		if (!$backend) {
301
-			$backend = new InvalidBackend($backendIdentifier);
302
-		}
303
-		$authMechanism = $this->backendService->getAuthMechanism($authMechanismIdentifier);
304
-		if (!$authMechanism) {
305
-			$authMechanism = new InvalidAuth($authMechanismIdentifier);
306
-		}
307
-		$newStorage = new StorageConfig();
308
-		$newStorage->setMountPoint($mountPoint);
309
-		$newStorage->setBackend($backend);
310
-		$newStorage->setAuthMechanism($authMechanism);
311
-		$newStorage->setBackendOptions($backendOptions);
312
-		if (isset($mountOptions)) {
313
-			$newStorage->setMountOptions($mountOptions);
314
-		}
315
-		if (isset($applicableUsers)) {
316
-			$newStorage->setApplicableUsers($applicableUsers);
317
-		}
318
-		if (isset($applicableGroups)) {
319
-			$newStorage->setApplicableGroups($applicableGroups);
320
-		}
321
-		if (isset($priority)) {
322
-			$newStorage->setPriority($priority);
323
-		}
324
-
325
-		return $newStorage;
326
-	}
327
-
328
-	/**
329
-	 * Triggers the given hook signal for all the applicables given
330
-	 *
331
-	 * @param string $signal signal
332
-	 * @param string $mountPoint hook mount pount param
333
-	 * @param string $mountType hook mount type param
334
-	 * @param array $applicableArray array of applicable users/groups for which to trigger the hook
335
-	 */
336
-	protected function triggerApplicableHooks($signal, $mountPoint, $mountType, $applicableArray) {
337
-		foreach ($applicableArray as $applicable) {
338
-			\OCP\Util::emitHook(
339
-				Filesystem::CLASSNAME,
340
-				$signal,
341
-				[
342
-					Filesystem::signal_param_path => $mountPoint,
343
-					Filesystem::signal_param_mount_type => $mountType,
344
-					Filesystem::signal_param_users => $applicable,
345
-				]
346
-			);
347
-		}
348
-	}
349
-
350
-	/**
351
-	 * Triggers $signal for all applicable users of the given
352
-	 * storage
353
-	 *
354
-	 * @param StorageConfig $storage storage data
355
-	 * @param string $signal signal to trigger
356
-	 */
357
-	abstract protected function triggerHooks(StorageConfig $storage, $signal);
358
-
359
-	/**
360
-	 * Triggers signal_create_mount or signal_delete_mount to
361
-	 * accommodate for additions/deletions in applicableUsers
362
-	 * and applicableGroups fields.
363
-	 *
364
-	 * @param StorageConfig $oldStorage old storage data
365
-	 * @param StorageConfig $newStorage new storage data
366
-	 */
367
-	abstract protected function triggerChangeHooks(StorageConfig $oldStorage, StorageConfig $newStorage);
368
-
369
-	/**
370
-	 * Update storage to the configuration
371
-	 *
372
-	 * @param StorageConfig $updatedStorage storage attributes
373
-	 *
374
-	 * @return StorageConfig storage config
375
-	 * @throws NotFoundException if the given storage does not exist in the config
376
-	 */
377
-	public function updateStorage(StorageConfig $updatedStorage) {
378
-		$id = $updatedStorage->getId();
379
-
380
-		$existingMount = $this->dbConfig->getMountById($id);
381
-
382
-		if (!is_array($existingMount)) {
383
-			throw new NotFoundException('Storage with ID "' . $id . '" not found while updating storage');
384
-		}
385
-
386
-		$oldStorage = $this->getStorageConfigFromDBMount($existingMount);
387
-
388
-		if ($oldStorage->getBackend() instanceof InvalidBackend) {
389
-			throw new NotFoundException('Storage with id "' . $id . '" cannot be edited due to missing backend');
390
-		}
391
-
392
-		$removedUsers = array_diff($oldStorage->getApplicableUsers(), $updatedStorage->getApplicableUsers());
393
-		$removedGroups = array_diff($oldStorage->getApplicableGroups(), $updatedStorage->getApplicableGroups());
394
-		$addedUsers = array_diff($updatedStorage->getApplicableUsers(), $oldStorage->getApplicableUsers());
395
-		$addedGroups = array_diff($updatedStorage->getApplicableGroups(), $oldStorage->getApplicableGroups());
396
-
397
-		$oldUserCount = count($oldStorage->getApplicableUsers());
398
-		$oldGroupCount = count($oldStorage->getApplicableGroups());
399
-		$newUserCount = count($updatedStorage->getApplicableUsers());
400
-		$newGroupCount = count($updatedStorage->getApplicableGroups());
401
-		$wasGlobal = ($oldUserCount + $oldGroupCount) === 0;
402
-		$isGlobal = ($newUserCount + $newGroupCount) === 0;
403
-
404
-		foreach ($removedUsers as $user) {
405
-			$this->dbConfig->removeApplicable($id, DBConfigService::APPLICABLE_TYPE_USER, $user);
406
-		}
407
-		foreach ($removedGroups as $group) {
408
-			$this->dbConfig->removeApplicable($id, DBConfigService::APPLICABLE_TYPE_GROUP, $group);
409
-		}
410
-		foreach ($addedUsers as $user) {
411
-			$this->dbConfig->addApplicable($id, DBConfigService::APPLICABLE_TYPE_USER, $user);
412
-		}
413
-		foreach ($addedGroups as $group) {
414
-			$this->dbConfig->addApplicable($id, DBConfigService::APPLICABLE_TYPE_GROUP, $group);
415
-		}
416
-
417
-		if ($wasGlobal && !$isGlobal) {
418
-			$this->dbConfig->removeApplicable($id, DBConfigService::APPLICABLE_TYPE_GLOBAL, null);
419
-		} else if (!$wasGlobal && $isGlobal) {
420
-			$this->dbConfig->addApplicable($id, DBConfigService::APPLICABLE_TYPE_GLOBAL, null);
421
-		}
422
-
423
-		$changedConfig = array_diff_assoc($updatedStorage->getBackendOptions(), $oldStorage->getBackendOptions());
424
-		$changedOptions = array_diff_assoc($updatedStorage->getMountOptions(), $oldStorage->getMountOptions());
425
-
426
-		foreach ($changedConfig as $key => $value) {
427
-			$this->dbConfig->setConfig($id, $key, $value);
428
-		}
429
-		foreach ($changedOptions as $key => $value) {
430
-			$this->dbConfig->setOption($id, $key, $value);
431
-		}
432
-
433
-		if ($updatedStorage->getMountPoint() !== $oldStorage->getMountPoint()) {
434
-			$this->dbConfig->setMountPoint($id, $updatedStorage->getMountPoint());
435
-		}
436
-
437
-		if ($updatedStorage->getAuthMechanism()->getIdentifier() !== $oldStorage->getAuthMechanism()->getIdentifier()) {
438
-			$this->dbConfig->setAuthBackend($id, $updatedStorage->getAuthMechanism()->getIdentifier());
439
-		}
440
-
441
-		$this->triggerChangeHooks($oldStorage, $updatedStorage);
442
-
443
-		if (($wasGlobal && !$isGlobal) || count($removedGroups) > 0) { // to expensive to properly handle these on the fly
444
-			$this->userMountCache->remoteStorageMounts($this->getStorageId($updatedStorage));
445
-		} else {
446
-			$storageId = $this->getStorageId($updatedStorage);
447
-			foreach ($removedUsers as $userId) {
448
-				$this->userMountCache->removeUserStorageMount($storageId, $userId);
449
-			}
450
-		}
451
-
452
-		return $this->getStorage($id);
453
-	}
454
-
455
-	/**
456
-	 * Delete the storage with the given id.
457
-	 *
458
-	 * @param int $id storage id
459
-	 *
460
-	 * @throws NotFoundException if no storage was found with the given id
461
-	 */
462
-	public function removeStorage($id) {
463
-		$existingMount = $this->dbConfig->getMountById($id);
464
-
465
-		if (!is_array($existingMount)) {
466
-			throw new NotFoundException('Storage with ID "' . $id . '" not found');
467
-		}
468
-
469
-		$this->dbConfig->removeMount($id);
470
-
471
-		$deletedStorage = $this->getStorageConfigFromDBMount($existingMount);
472
-		$this->triggerHooks($deletedStorage, Filesystem::signal_delete_mount);
473
-
474
-		// delete oc_storages entries and oc_filecache
475
-		try {
476
-			$rustyStorageId = $this->getRustyStorageIdFromConfig($deletedStorage);
477
-			var_dump($rustyStorageId);
478
-			\OC\Files\Cache\Storage::remove($rustyStorageId);
479
-		} catch (\Exception $e) {
480
-			// can happen either for invalid configs where the storage could not
481
-			// be instantiated or whenever $user vars where used, in which case
482
-			// the storage id could not be computed
483
-			\OC::$server->getLogger()->logException($e, [
484
-				'level' => ILogger::ERROR,
485
-				'app' => 'files_external',
486
-			]);
487
-		}
488
-	}
489
-
490
-	/**
491
-	 * Returns the rusty storage id from oc_storages from the given storage config.
492
-	 *
493
-	 * @param StorageConfig $storageConfig
494
-	 * @return string rusty storage id
495
-	 */
496
-	private function getRustyStorageIdFromConfig(StorageConfig $storageConfig) {
497
-		// if any of the storage options contains $user, it is not possible
498
-		// to compute the possible storage id as we don't know which users
499
-		// mounted it already (and we certainly don't want to iterate over ALL users)
500
-		foreach ($storageConfig->getBackendOptions() as $value) {
501
-			if (strpos($value, '$user') !== false) {
502
-				throw new \Exception('Cannot compute storage id for deletion due to $user vars in the configuration');
503
-			}
504
-		}
505
-
506
-		// note: similar to ConfigAdapter->prepateStorageConfig()
507
-		$storageConfig->getAuthMechanism()->manipulateStorageConfig($storageConfig);
508
-		$storageConfig->getBackend()->manipulateStorageConfig($storageConfig);
509
-
510
-		$class = $storageConfig->getBackend()->getStorageClass();
511
-		$storageImpl = new $class($storageConfig->getBackendOptions());
512
-
513
-		return $storageImpl->getId();
514
-	}
515
-
516
-	/**
517
-	 * Construct the storage implementation
518
-	 *
519
-	 * @param StorageConfig $storageConfig
520
-	 * @return int
521
-	 */
522
-	private function getStorageId(StorageConfig $storageConfig) {
523
-		try {
524
-			$class = $storageConfig->getBackend()->getStorageClass();
525
-			/** @var \OC\Files\Storage\Storage $storage */
526
-			$storage = new $class($storageConfig->getBackendOptions());
527
-
528
-			// auth mechanism should fire first
529
-			$storage = $storageConfig->getBackend()->wrapStorage($storage);
530
-			$storage = $storageConfig->getAuthMechanism()->wrapStorage($storage);
531
-
532
-			return $storage->getStorageCache()->getNumericId();
533
-		} catch (\Exception $e) {
534
-			return -1;
535
-		}
536
-	}
47
+    /** @var BackendService */
48
+    protected $backendService;
49
+
50
+    /**
51
+     * @var DBConfigService
52
+     */
53
+    protected $dbConfig;
54
+
55
+    /**
56
+     * @var IUserMountCache
57
+     */
58
+    protected $userMountCache;
59
+
60
+    /**
61
+     * @param BackendService $backendService
62
+     * @param DBConfigService $dbConfigService
63
+     * @param IUserMountCache $userMountCache
64
+     */
65
+    public function __construct(BackendService $backendService, DBConfigService $dbConfigService, IUserMountCache $userMountCache) {
66
+        $this->backendService = $backendService;
67
+        $this->dbConfig = $dbConfigService;
68
+        $this->userMountCache = $userMountCache;
69
+    }
70
+
71
+    protected function readDBConfig() {
72
+        return $this->dbConfig->getAdminMounts();
73
+    }
74
+
75
+    protected function getStorageConfigFromDBMount(array $mount) {
76
+        $applicableUsers = array_filter($mount['applicable'], function ($applicable) {
77
+            return $applicable['type'] === DBConfigService::APPLICABLE_TYPE_USER;
78
+        });
79
+        $applicableUsers = array_map(function ($applicable) {
80
+            return $applicable['value'];
81
+        }, $applicableUsers);
82
+
83
+        $applicableGroups = array_filter($mount['applicable'], function ($applicable) {
84
+            return $applicable['type'] === DBConfigService::APPLICABLE_TYPE_GROUP;
85
+        });
86
+        $applicableGroups = array_map(function ($applicable) {
87
+            return $applicable['value'];
88
+        }, $applicableGroups);
89
+
90
+        try {
91
+            $config = $this->createStorage(
92
+                $mount['mount_point'],
93
+                $mount['storage_backend'],
94
+                $mount['auth_backend'],
95
+                $mount['config'],
96
+                $mount['options'],
97
+                array_values($applicableUsers),
98
+                array_values($applicableGroups),
99
+                $mount['priority']
100
+            );
101
+            $config->setType($mount['type']);
102
+            $config->setId((int)$mount['mount_id']);
103
+            return $config;
104
+        } catch (\UnexpectedValueException $e) {
105
+            // don't die if a storage backend doesn't exist
106
+            \OC::$server->getLogger()->logException($e, [
107
+                'message' => 'Could not load storage.',
108
+                'level' => ILogger::ERROR,
109
+                'app' => 'files_external',
110
+            ]);
111
+            return null;
112
+        } catch (\InvalidArgumentException $e) {
113
+            \OC::$server->getLogger()->logException($e, [
114
+                'message' => 'Could not load storage.',
115
+                'level' => ILogger::ERROR,
116
+                'app' => 'files_external',
117
+            ]);
118
+            return null;
119
+        }
120
+    }
121
+
122
+    /**
123
+     * Read the external storages config
124
+     *
125
+     * @return array map of storage id to storage config
126
+     */
127
+    protected function readConfig() {
128
+        $mounts = $this->readDBConfig();
129
+        $configs = array_map([$this, 'getStorageConfigFromDBMount'], $mounts);
130
+        $configs = array_filter($configs, function ($config) {
131
+            return $config instanceof StorageConfig;
132
+        });
133
+
134
+        $keys = array_map(function (StorageConfig $config) {
135
+            return $config->getId();
136
+        }, $configs);
137
+
138
+        return array_combine($keys, $configs);
139
+    }
140
+
141
+    /**
142
+     * Get a storage with status
143
+     *
144
+     * @param int $id storage id
145
+     *
146
+     * @return StorageConfig
147
+     * @throws NotFoundException if the storage with the given id was not found
148
+     */
149
+    public function getStorage($id) {
150
+        $mount = $this->dbConfig->getMountById($id);
151
+
152
+        if (!is_array($mount)) {
153
+            throw new NotFoundException('Storage with ID "' . $id . '" not found');
154
+        }
155
+
156
+        $config = $this->getStorageConfigFromDBMount($mount);
157
+        if ($this->isApplicable($config)) {
158
+            return $config;
159
+        } else {
160
+            throw new NotFoundException('Storage with ID "' . $id . '" not found');
161
+        }
162
+    }
163
+
164
+    /**
165
+     * Check whether this storage service should provide access to a storage
166
+     *
167
+     * @param StorageConfig $config
168
+     * @return bool
169
+     */
170
+    abstract protected function isApplicable(StorageConfig $config);
171
+
172
+    /**
173
+     * Gets all storages, valid or not
174
+     *
175
+     * @return StorageConfig[] array of storage configs
176
+     */
177
+    public function getAllStorages() {
178
+        return $this->readConfig();
179
+    }
180
+
181
+    /**
182
+     * Gets all valid storages
183
+     *
184
+     * @return StorageConfig[]
185
+     */
186
+    public function getStorages() {
187
+        return array_filter($this->getAllStorages(), [$this, 'validateStorage']);
188
+    }
189
+
190
+    /**
191
+     * Validate storage
192
+     * FIXME: De-duplicate with StoragesController::validate()
193
+     *
194
+     * @param StorageConfig $storage
195
+     * @return bool
196
+     */
197
+    protected function validateStorage(StorageConfig $storage) {
198
+        /** @var Backend */
199
+        $backend = $storage->getBackend();
200
+        /** @var AuthMechanism */
201
+        $authMechanism = $storage->getAuthMechanism();
202
+
203
+        if (!$backend->isVisibleFor($this->getVisibilityType())) {
204
+            // not permitted to use backend
205
+            return false;
206
+        }
207
+        if (!$authMechanism->isVisibleFor($this->getVisibilityType())) {
208
+            // not permitted to use auth mechanism
209
+            return false;
210
+        }
211
+
212
+        return true;
213
+    }
214
+
215
+    /**
216
+     * Get the visibility type for this controller, used in validation
217
+     *
218
+     * @return string BackendService::VISIBILITY_* constants
219
+     */
220
+    abstract public function getVisibilityType();
221
+
222
+    /**
223
+     * @return integer
224
+     */
225
+    protected function getType() {
226
+        return DBConfigService::MOUNT_TYPE_ADMIN;
227
+    }
228
+
229
+    /**
230
+     * Add new storage to the configuration
231
+     *
232
+     * @param StorageConfig $newStorage storage attributes
233
+     *
234
+     * @return StorageConfig storage config, with added id
235
+     */
236
+    public function addStorage(StorageConfig $newStorage) {
237
+        $allStorages = $this->readConfig();
238
+
239
+        $configId = $this->dbConfig->addMount(
240
+            $newStorage->getMountPoint(),
241
+            $newStorage->getBackend()->getIdentifier(),
242
+            $newStorage->getAuthMechanism()->getIdentifier(),
243
+            $newStorage->getPriority(),
244
+            $this->getType()
245
+        );
246
+
247
+        $newStorage->setId($configId);
248
+
249
+        foreach ($newStorage->getApplicableUsers() as $user) {
250
+            $this->dbConfig->addApplicable($configId, DBConfigService::APPLICABLE_TYPE_USER, $user);
251
+        }
252
+        foreach ($newStorage->getApplicableGroups() as $group) {
253
+            $this->dbConfig->addApplicable($configId, DBConfigService::APPLICABLE_TYPE_GROUP, $group);
254
+        }
255
+        foreach ($newStorage->getBackendOptions() as $key => $value) {
256
+            $this->dbConfig->setConfig($configId, $key, $value);
257
+        }
258
+        foreach ($newStorage->getMountOptions() as $key => $value) {
259
+            $this->dbConfig->setOption($configId, $key, $value);
260
+        }
261
+
262
+        if (count($newStorage->getApplicableUsers()) === 0 && count($newStorage->getApplicableGroups()) === 0) {
263
+            $this->dbConfig->addApplicable($configId, DBConfigService::APPLICABLE_TYPE_GLOBAL, null);
264
+        }
265
+
266
+        // add new storage
267
+        $allStorages[$configId] = $newStorage;
268
+
269
+        $this->triggerHooks($newStorage, Filesystem::signal_create_mount);
270
+
271
+        $newStorage->setStatus(StorageNotAvailableException::STATUS_SUCCESS);
272
+        return $newStorage;
273
+    }
274
+
275
+    /**
276
+     * Create a storage from its parameters
277
+     *
278
+     * @param string $mountPoint storage mount point
279
+     * @param string $backendIdentifier backend identifier
280
+     * @param string $authMechanismIdentifier authentication mechanism identifier
281
+     * @param array $backendOptions backend-specific options
282
+     * @param array|null $mountOptions mount-specific options
283
+     * @param array|null $applicableUsers users for which to mount the storage
284
+     * @param array|null $applicableGroups groups for which to mount the storage
285
+     * @param int|null $priority priority
286
+     *
287
+     * @return StorageConfig
288
+     */
289
+    public function createStorage(
290
+        $mountPoint,
291
+        $backendIdentifier,
292
+        $authMechanismIdentifier,
293
+        $backendOptions,
294
+        $mountOptions = null,
295
+        $applicableUsers = null,
296
+        $applicableGroups = null,
297
+        $priority = null
298
+    ) {
299
+        $backend = $this->backendService->getBackend($backendIdentifier);
300
+        if (!$backend) {
301
+            $backend = new InvalidBackend($backendIdentifier);
302
+        }
303
+        $authMechanism = $this->backendService->getAuthMechanism($authMechanismIdentifier);
304
+        if (!$authMechanism) {
305
+            $authMechanism = new InvalidAuth($authMechanismIdentifier);
306
+        }
307
+        $newStorage = new StorageConfig();
308
+        $newStorage->setMountPoint($mountPoint);
309
+        $newStorage->setBackend($backend);
310
+        $newStorage->setAuthMechanism($authMechanism);
311
+        $newStorage->setBackendOptions($backendOptions);
312
+        if (isset($mountOptions)) {
313
+            $newStorage->setMountOptions($mountOptions);
314
+        }
315
+        if (isset($applicableUsers)) {
316
+            $newStorage->setApplicableUsers($applicableUsers);
317
+        }
318
+        if (isset($applicableGroups)) {
319
+            $newStorage->setApplicableGroups($applicableGroups);
320
+        }
321
+        if (isset($priority)) {
322
+            $newStorage->setPriority($priority);
323
+        }
324
+
325
+        return $newStorage;
326
+    }
327
+
328
+    /**
329
+     * Triggers the given hook signal for all the applicables given
330
+     *
331
+     * @param string $signal signal
332
+     * @param string $mountPoint hook mount pount param
333
+     * @param string $mountType hook mount type param
334
+     * @param array $applicableArray array of applicable users/groups for which to trigger the hook
335
+     */
336
+    protected function triggerApplicableHooks($signal, $mountPoint, $mountType, $applicableArray) {
337
+        foreach ($applicableArray as $applicable) {
338
+            \OCP\Util::emitHook(
339
+                Filesystem::CLASSNAME,
340
+                $signal,
341
+                [
342
+                    Filesystem::signal_param_path => $mountPoint,
343
+                    Filesystem::signal_param_mount_type => $mountType,
344
+                    Filesystem::signal_param_users => $applicable,
345
+                ]
346
+            );
347
+        }
348
+    }
349
+
350
+    /**
351
+     * Triggers $signal for all applicable users of the given
352
+     * storage
353
+     *
354
+     * @param StorageConfig $storage storage data
355
+     * @param string $signal signal to trigger
356
+     */
357
+    abstract protected function triggerHooks(StorageConfig $storage, $signal);
358
+
359
+    /**
360
+     * Triggers signal_create_mount or signal_delete_mount to
361
+     * accommodate for additions/deletions in applicableUsers
362
+     * and applicableGroups fields.
363
+     *
364
+     * @param StorageConfig $oldStorage old storage data
365
+     * @param StorageConfig $newStorage new storage data
366
+     */
367
+    abstract protected function triggerChangeHooks(StorageConfig $oldStorage, StorageConfig $newStorage);
368
+
369
+    /**
370
+     * Update storage to the configuration
371
+     *
372
+     * @param StorageConfig $updatedStorage storage attributes
373
+     *
374
+     * @return StorageConfig storage config
375
+     * @throws NotFoundException if the given storage does not exist in the config
376
+     */
377
+    public function updateStorage(StorageConfig $updatedStorage) {
378
+        $id = $updatedStorage->getId();
379
+
380
+        $existingMount = $this->dbConfig->getMountById($id);
381
+
382
+        if (!is_array($existingMount)) {
383
+            throw new NotFoundException('Storage with ID "' . $id . '" not found while updating storage');
384
+        }
385
+
386
+        $oldStorage = $this->getStorageConfigFromDBMount($existingMount);
387
+
388
+        if ($oldStorage->getBackend() instanceof InvalidBackend) {
389
+            throw new NotFoundException('Storage with id "' . $id . '" cannot be edited due to missing backend');
390
+        }
391
+
392
+        $removedUsers = array_diff($oldStorage->getApplicableUsers(), $updatedStorage->getApplicableUsers());
393
+        $removedGroups = array_diff($oldStorage->getApplicableGroups(), $updatedStorage->getApplicableGroups());
394
+        $addedUsers = array_diff($updatedStorage->getApplicableUsers(), $oldStorage->getApplicableUsers());
395
+        $addedGroups = array_diff($updatedStorage->getApplicableGroups(), $oldStorage->getApplicableGroups());
396
+
397
+        $oldUserCount = count($oldStorage->getApplicableUsers());
398
+        $oldGroupCount = count($oldStorage->getApplicableGroups());
399
+        $newUserCount = count($updatedStorage->getApplicableUsers());
400
+        $newGroupCount = count($updatedStorage->getApplicableGroups());
401
+        $wasGlobal = ($oldUserCount + $oldGroupCount) === 0;
402
+        $isGlobal = ($newUserCount + $newGroupCount) === 0;
403
+
404
+        foreach ($removedUsers as $user) {
405
+            $this->dbConfig->removeApplicable($id, DBConfigService::APPLICABLE_TYPE_USER, $user);
406
+        }
407
+        foreach ($removedGroups as $group) {
408
+            $this->dbConfig->removeApplicable($id, DBConfigService::APPLICABLE_TYPE_GROUP, $group);
409
+        }
410
+        foreach ($addedUsers as $user) {
411
+            $this->dbConfig->addApplicable($id, DBConfigService::APPLICABLE_TYPE_USER, $user);
412
+        }
413
+        foreach ($addedGroups as $group) {
414
+            $this->dbConfig->addApplicable($id, DBConfigService::APPLICABLE_TYPE_GROUP, $group);
415
+        }
416
+
417
+        if ($wasGlobal && !$isGlobal) {
418
+            $this->dbConfig->removeApplicable($id, DBConfigService::APPLICABLE_TYPE_GLOBAL, null);
419
+        } else if (!$wasGlobal && $isGlobal) {
420
+            $this->dbConfig->addApplicable($id, DBConfigService::APPLICABLE_TYPE_GLOBAL, null);
421
+        }
422
+
423
+        $changedConfig = array_diff_assoc($updatedStorage->getBackendOptions(), $oldStorage->getBackendOptions());
424
+        $changedOptions = array_diff_assoc($updatedStorage->getMountOptions(), $oldStorage->getMountOptions());
425
+
426
+        foreach ($changedConfig as $key => $value) {
427
+            $this->dbConfig->setConfig($id, $key, $value);
428
+        }
429
+        foreach ($changedOptions as $key => $value) {
430
+            $this->dbConfig->setOption($id, $key, $value);
431
+        }
432
+
433
+        if ($updatedStorage->getMountPoint() !== $oldStorage->getMountPoint()) {
434
+            $this->dbConfig->setMountPoint($id, $updatedStorage->getMountPoint());
435
+        }
436
+
437
+        if ($updatedStorage->getAuthMechanism()->getIdentifier() !== $oldStorage->getAuthMechanism()->getIdentifier()) {
438
+            $this->dbConfig->setAuthBackend($id, $updatedStorage->getAuthMechanism()->getIdentifier());
439
+        }
440
+
441
+        $this->triggerChangeHooks($oldStorage, $updatedStorage);
442
+
443
+        if (($wasGlobal && !$isGlobal) || count($removedGroups) > 0) { // to expensive to properly handle these on the fly
444
+            $this->userMountCache->remoteStorageMounts($this->getStorageId($updatedStorage));
445
+        } else {
446
+            $storageId = $this->getStorageId($updatedStorage);
447
+            foreach ($removedUsers as $userId) {
448
+                $this->userMountCache->removeUserStorageMount($storageId, $userId);
449
+            }
450
+        }
451
+
452
+        return $this->getStorage($id);
453
+    }
454
+
455
+    /**
456
+     * Delete the storage with the given id.
457
+     *
458
+     * @param int $id storage id
459
+     *
460
+     * @throws NotFoundException if no storage was found with the given id
461
+     */
462
+    public function removeStorage($id) {
463
+        $existingMount = $this->dbConfig->getMountById($id);
464
+
465
+        if (!is_array($existingMount)) {
466
+            throw new NotFoundException('Storage with ID "' . $id . '" not found');
467
+        }
468
+
469
+        $this->dbConfig->removeMount($id);
470
+
471
+        $deletedStorage = $this->getStorageConfigFromDBMount($existingMount);
472
+        $this->triggerHooks($deletedStorage, Filesystem::signal_delete_mount);
473
+
474
+        // delete oc_storages entries and oc_filecache
475
+        try {
476
+            $rustyStorageId = $this->getRustyStorageIdFromConfig($deletedStorage);
477
+            var_dump($rustyStorageId);
478
+            \OC\Files\Cache\Storage::remove($rustyStorageId);
479
+        } catch (\Exception $e) {
480
+            // can happen either for invalid configs where the storage could not
481
+            // be instantiated or whenever $user vars where used, in which case
482
+            // the storage id could not be computed
483
+            \OC::$server->getLogger()->logException($e, [
484
+                'level' => ILogger::ERROR,
485
+                'app' => 'files_external',
486
+            ]);
487
+        }
488
+    }
489
+
490
+    /**
491
+     * Returns the rusty storage id from oc_storages from the given storage config.
492
+     *
493
+     * @param StorageConfig $storageConfig
494
+     * @return string rusty storage id
495
+     */
496
+    private function getRustyStorageIdFromConfig(StorageConfig $storageConfig) {
497
+        // if any of the storage options contains $user, it is not possible
498
+        // to compute the possible storage id as we don't know which users
499
+        // mounted it already (and we certainly don't want to iterate over ALL users)
500
+        foreach ($storageConfig->getBackendOptions() as $value) {
501
+            if (strpos($value, '$user') !== false) {
502
+                throw new \Exception('Cannot compute storage id for deletion due to $user vars in the configuration');
503
+            }
504
+        }
505
+
506
+        // note: similar to ConfigAdapter->prepateStorageConfig()
507
+        $storageConfig->getAuthMechanism()->manipulateStorageConfig($storageConfig);
508
+        $storageConfig->getBackend()->manipulateStorageConfig($storageConfig);
509
+
510
+        $class = $storageConfig->getBackend()->getStorageClass();
511
+        $storageImpl = new $class($storageConfig->getBackendOptions());
512
+
513
+        return $storageImpl->getId();
514
+    }
515
+
516
+    /**
517
+     * Construct the storage implementation
518
+     *
519
+     * @param StorageConfig $storageConfig
520
+     * @return int
521
+     */
522
+    private function getStorageId(StorageConfig $storageConfig) {
523
+        try {
524
+            $class = $storageConfig->getBackend()->getStorageClass();
525
+            /** @var \OC\Files\Storage\Storage $storage */
526
+            $storage = new $class($storageConfig->getBackendOptions());
527
+
528
+            // auth mechanism should fire first
529
+            $storage = $storageConfig->getBackend()->wrapStorage($storage);
530
+            $storage = $storageConfig->getAuthMechanism()->wrapStorage($storage);
531
+
532
+            return $storage->getStorageCache()->getNumericId();
533
+        } catch (\Exception $e) {
534
+            return -1;
535
+        }
536
+    }
537 537
 }
Please login to merge, or discard this patch.