Completed
Push — master ( 3da92a...bdf411 )
by Robin
15:41
created
apps/files_external/lib/Lib/Storage/SMB.php 1 patch
Indentation   +472 added lines, -472 removed lines patch added patch discarded remove patch
@@ -55,476 +55,476 @@
 block discarded – undo
55 55
 use OCP\Util;
56 56
 
57 57
 class SMB extends Common implements INotifyStorage {
58
-	/**
59
-	 * @var \Icewind\SMB\Server
60
-	 */
61
-	protected $server;
62
-
63
-	/**
64
-	 * @var \Icewind\SMB\Share
65
-	 */
66
-	protected $share;
67
-
68
-	/**
69
-	 * @var string
70
-	 */
71
-	protected $root;
72
-
73
-	/**
74
-	 * @var \Icewind\SMB\FileInfo[]
75
-	 */
76
-	protected $statCache;
77
-
78
-	public function __construct($params) {
79
-		if (isset($params['host']) && isset($params['user']) && isset($params['password']) && isset($params['share'])) {
80
-			if (Server::NativeAvailable()) {
81
-				$this->server = new NativeServer($params['host'], $params['user'], $params['password']);
82
-			} else {
83
-				$this->server = new Server($params['host'], $params['user'], $params['password']);
84
-			}
85
-			$this->share = $this->server->getShare(trim($params['share'], '/'));
86
-
87
-			$this->root = isset($params['root']) ? $params['root'] : '/';
88
-			if (!$this->root || $this->root[0] !== '/') {
89
-				$this->root = '/' . $this->root;
90
-			}
91
-			if (substr($this->root, -1, 1) !== '/') {
92
-				$this->root .= '/';
93
-			}
94
-		} else {
95
-			throw new \Exception('Invalid configuration');
96
-		}
97
-		$this->statCache = new CappedMemoryCache();
98
-		parent::__construct($params);
99
-	}
100
-
101
-	/**
102
-	 * @return string
103
-	 */
104
-	public function getId() {
105
-		// FIXME: double slash to keep compatible with the old storage ids,
106
-		// failure to do so will lead to creation of a new storage id and
107
-		// loss of shares from the storage
108
-		return 'smb::' . $this->server->getUser() . '@' . $this->server->getHost() . '//' . $this->share->getName() . '/' . $this->root;
109
-	}
110
-
111
-	/**
112
-	 * @param string $path
113
-	 * @return string
114
-	 */
115
-	protected function buildPath($path) {
116
-		return Filesystem::normalizePath($this->root . '/' . $path, true, false, true);
117
-	}
118
-
119
-	protected function relativePath($fullPath) {
120
-		if ($fullPath === $this->root) {
121
-			return '';
122
-		} else if (substr($fullPath, 0, strlen($this->root)) === $this->root) {
123
-			return substr($fullPath, strlen($this->root));
124
-		} else {
125
-			return null;
126
-		}
127
-	}
128
-
129
-	/**
130
-	 * @param string $path
131
-	 * @return \Icewind\SMB\IFileInfo
132
-	 * @throws StorageNotAvailableException
133
-	 */
134
-	protected function getFileInfo($path) {
135
-		try {
136
-			$path = $this->buildPath($path);
137
-			if (!isset($this->statCache[$path])) {
138
-				$this->statCache[$path] = $this->share->stat($path);
139
-			}
140
-			return $this->statCache[$path];
141
-		} catch (ConnectException $e) {
142
-			throw new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e);
143
-		}
144
-	}
145
-
146
-	/**
147
-	 * @param string $path
148
-	 * @return \Icewind\SMB\IFileInfo[]
149
-	 * @throws StorageNotAvailableException
150
-	 */
151
-	protected function getFolderContents($path) {
152
-		try {
153
-			$path = $this->buildPath($path);
154
-			$files = $this->share->dir($path);
155
-			foreach ($files as $file) {
156
-				$this->statCache[$path . '/' . $file->getName()] = $file;
157
-			}
158
-			return array_filter($files, function (IFileInfo $file) {
159
-				return !$file->isHidden();
160
-			});
161
-		} catch (ConnectException $e) {
162
-			throw new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e);
163
-		}
164
-	}
165
-
166
-	/**
167
-	 * @param \Icewind\SMB\IFileInfo $info
168
-	 * @return array
169
-	 */
170
-	protected function formatInfo($info) {
171
-		$result = [
172
-			'size' => $info->getSize(),
173
-			'mtime' => $info->getMTime(),
174
-		];
175
-		if ($info->isDirectory()) {
176
-			$result['type'] = 'dir';
177
-		} else {
178
-			$result['type'] = 'file';
179
-		}
180
-		return $result;
181
-	}
182
-
183
-	/**
184
-	 * Rename the files. If the source or the target is the root, the rename won't happen.
185
-	 *
186
-	 * @param string $source the old name of the path
187
-	 * @param string $target the new name of the path
188
-	 * @return bool true if the rename is successful, false otherwise
189
-	 */
190
-	public function rename($source, $target) {
191
-		if ($this->isRootDir($source) || $this->isRootDir($target)) {
192
-			return false;
193
-		}
194
-
195
-		$absoluteSource = $this->buildPath($source);
196
-		$absoluteTarget = $this->buildPath($target);
197
-		try {
198
-			$result = $this->share->rename($absoluteSource, $absoluteTarget);
199
-		} catch (AlreadyExistsException $e) {
200
-			$this->remove($target);
201
-			$result = $this->share->rename($absoluteSource, $absoluteTarget);
202
-		} catch (\Exception $e) {
203
-			\OC::$server->getLogger()->logException($e, ['level' => Util::WARN]);
204
-			return false;
205
-		}
206
-		unset($this->statCache[$absoluteSource], $this->statCache[$absoluteTarget]);
207
-		return $result;
208
-	}
209
-
210
-	/**
211
-	 * @param string $path
212
-	 * @return array
213
-	 */
214
-	public function stat($path) {
215
-		$result = $this->formatInfo($this->getFileInfo($path));
216
-		if ($this->remoteIsShare() && $this->isRootDir($path)) {
217
-			$result['mtime'] = $this->shareMTime();
218
-		}
219
-		return $result;
220
-	}
221
-
222
-	/**
223
-	 * get the best guess for the modification time of the share
224
-	 *
225
-	 * @return int
226
-	 */
227
-	private function shareMTime() {
228
-		$highestMTime = 0;
229
-		$files = $this->share->dir($this->root);
230
-		foreach ($files as $fileInfo) {
231
-			if ($fileInfo->getMTime() > $highestMTime) {
232
-				$highestMTime = $fileInfo->getMTime();
233
-			}
234
-		}
235
-		return $highestMTime;
236
-	}
237
-
238
-	/**
239
-	 * Check if the path is our root dir (not the smb one)
240
-	 *
241
-	 * @param string $path the path
242
-	 * @return bool
243
-	 */
244
-	private function isRootDir($path) {
245
-		return $path === '' || $path === '/' || $path === '.';
246
-	}
247
-
248
-	/**
249
-	 * Check if our root points to a smb share
250
-	 *
251
-	 * @return bool true if our root points to a share false otherwise
252
-	 */
253
-	private function remoteIsShare() {
254
-		return $this->share->getName() && (!$this->root || $this->root === '/');
255
-	}
256
-
257
-	/**
258
-	 * @param string $path
259
-	 * @return bool
260
-	 */
261
-	public function unlink($path) {
262
-		if ($this->isRootDir($path)) {
263
-			return false;
264
-		}
265
-
266
-		try {
267
-			if ($this->is_dir($path)) {
268
-				return $this->rmdir($path);
269
-			} else {
270
-				$path = $this->buildPath($path);
271
-				unset($this->statCache[$path]);
272
-				$this->share->del($path);
273
-				return true;
274
-			}
275
-		} catch (NotFoundException $e) {
276
-			return false;
277
-		} catch (ForbiddenException $e) {
278
-			return false;
279
-		} catch (ConnectException $e) {
280
-			throw new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e);
281
-		}
282
-	}
283
-
284
-	/**
285
-	 * check if a file or folder has been updated since $time
286
-	 *
287
-	 * @param string $path
288
-	 * @param int $time
289
-	 * @return bool
290
-	 */
291
-	public function hasUpdated($path, $time) {
292
-		if (!$path and $this->root === '/') {
293
-			// mtime doesn't work for shares, but giving the nature of the backend,
294
-			// doing a full update is still just fast enough
295
-			return true;
296
-		} else {
297
-			$actualTime = $this->filemtime($path);
298
-			return $actualTime > $time;
299
-		}
300
-	}
301
-
302
-	/**
303
-	 * @param string $path
304
-	 * @param string $mode
305
-	 * @return resource|false
306
-	 */
307
-	public function fopen($path, $mode) {
308
-		$fullPath = $this->buildPath($path);
309
-		try {
310
-			switch ($mode) {
311
-				case 'r':
312
-				case 'rb':
313
-					if (!$this->file_exists($path)) {
314
-						return false;
315
-					}
316
-					return $this->share->read($fullPath);
317
-				case 'w':
318
-				case 'wb':
319
-					$source = $this->share->write($fullPath);
320
-					return CallBackWrapper::wrap($source, null, null, function () use ($fullPath) {
321
-						unset($this->statCache[$fullPath]);
322
-					});
323
-				case 'a':
324
-				case 'ab':
325
-				case 'r+':
326
-				case 'w+':
327
-				case 'wb+':
328
-				case 'a+':
329
-				case 'x':
330
-				case 'x+':
331
-				case 'c':
332
-				case 'c+':
333
-					//emulate these
334
-					if (strrpos($path, '.') !== false) {
335
-						$ext = substr($path, strrpos($path, '.'));
336
-					} else {
337
-						$ext = '';
338
-					}
339
-					if ($this->file_exists($path)) {
340
-						if (!$this->isUpdatable($path)) {
341
-							return false;
342
-						}
343
-						$tmpFile = $this->getCachedFile($path);
344
-					} else {
345
-						if (!$this->isCreatable(dirname($path))) {
346
-							return false;
347
-						}
348
-						$tmpFile = \OCP\Files::tmpFile($ext);
349
-					}
350
-					$source = fopen($tmpFile, $mode);
351
-					$share = $this->share;
352
-					return CallbackWrapper::wrap($source, null, null, function () use ($tmpFile, $fullPath, $share) {
353
-						unset($this->statCache[$fullPath]);
354
-						$share->put($tmpFile, $fullPath);
355
-						unlink($tmpFile);
356
-					});
357
-			}
358
-			return false;
359
-		} catch (NotFoundException $e) {
360
-			return false;
361
-		} catch (ForbiddenException $e) {
362
-			return false;
363
-		} catch (ConnectException $e) {
364
-			throw new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e);
365
-		}
366
-	}
367
-
368
-	public function rmdir($path) {
369
-		if ($this->isRootDir($path)) {
370
-			return false;
371
-		}
372
-
373
-		try {
374
-			$this->statCache = array();
375
-			$content = $this->share->dir($this->buildPath($path));
376
-			foreach ($content as $file) {
377
-				if ($file->isDirectory()) {
378
-					$this->rmdir($path . '/' . $file->getName());
379
-				} else {
380
-					$this->share->del($file->getPath());
381
-				}
382
-			}
383
-			$this->share->rmdir($this->buildPath($path));
384
-			return true;
385
-		} catch (NotFoundException $e) {
386
-			return false;
387
-		} catch (ForbiddenException $e) {
388
-			return false;
389
-		} catch (ConnectException $e) {
390
-			throw new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e);
391
-		}
392
-	}
393
-
394
-	public function touch($path, $time = null) {
395
-		try {
396
-			if (!$this->file_exists($path)) {
397
-				$fh = $this->share->write($this->buildPath($path));
398
-				fclose($fh);
399
-				return true;
400
-			}
401
-			return false;
402
-		} catch (ConnectException $e) {
403
-			throw new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e);
404
-		}
405
-	}
406
-
407
-	public function opendir($path) {
408
-		try {
409
-			$files = $this->getFolderContents($path);
410
-		} catch (NotFoundException $e) {
411
-			return false;
412
-		} catch (ForbiddenException $e) {
413
-			return false;
414
-		}
415
-		$names = array_map(function ($info) {
416
-			/** @var \Icewind\SMB\IFileInfo $info */
417
-			return $info->getName();
418
-		}, $files);
419
-		return IteratorDirectory::wrap($names);
420
-	}
421
-
422
-	public function filetype($path) {
423
-		try {
424
-			return $this->getFileInfo($path)->isDirectory() ? 'dir' : 'file';
425
-		} catch (NotFoundException $e) {
426
-			return false;
427
-		} catch (ForbiddenException $e) {
428
-			return false;
429
-		}
430
-	}
431
-
432
-	public function mkdir($path) {
433
-		$path = $this->buildPath($path);
434
-		try {
435
-			$this->share->mkdir($path);
436
-			return true;
437
-		} catch (ConnectException $e) {
438
-			throw new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e);
439
-		} catch (Exception $e) {
440
-			return false;
441
-		}
442
-	}
443
-
444
-	public function file_exists($path) {
445
-		try {
446
-			$this->getFileInfo($path);
447
-			return true;
448
-		} catch (NotFoundException $e) {
449
-			return false;
450
-		} catch (ForbiddenException $e) {
451
-			return false;
452
-		} catch (ConnectException $e) {
453
-			throw new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e);
454
-		}
455
-	}
456
-
457
-	public function isReadable($path) {
458
-		try {
459
-			$info = $this->getFileInfo($path);
460
-			return !$info->isHidden();
461
-		} catch (NotFoundException $e) {
462
-			return false;
463
-		} catch (ForbiddenException $e) {
464
-			return false;
465
-		}
466
-	}
467
-
468
-	public function isUpdatable($path) {
469
-		try {
470
-			$info = $this->getFileInfo($path);
471
-			// following windows behaviour for read-only folders: they can be written into
472
-			// (https://support.microsoft.com/en-us/kb/326549 - "cause" section)
473
-			return !$info->isHidden() && (!$info->isReadOnly() || $this->is_dir($path));
474
-		} catch (NotFoundException $e) {
475
-			return false;
476
-		} catch (ForbiddenException $e) {
477
-			return false;
478
-		}
479
-	}
480
-
481
-	public function isDeletable($path) {
482
-		try {
483
-			$info = $this->getFileInfo($path);
484
-			return !$info->isHidden() && !$info->isReadOnly();
485
-		} catch (NotFoundException $e) {
486
-			return false;
487
-		} catch (ForbiddenException $e) {
488
-			return false;
489
-		}
490
-	}
491
-
492
-	/**
493
-	 * check if smbclient is installed
494
-	 */
495
-	public static function checkDependencies() {
496
-		return (
497
-			(bool)\OC_Helper::findBinaryPath('smbclient')
498
-			|| Server::NativeAvailable()
499
-		) ? true : ['smbclient'];
500
-	}
501
-
502
-	/**
503
-	 * Test a storage for availability
504
-	 *
505
-	 * @return bool
506
-	 */
507
-	public function test() {
508
-		try {
509
-			return parent::test();
510
-		} catch (Exception $e) {
511
-			return false;
512
-		}
513
-	}
514
-
515
-	public function listen($path, callable $callback) {
516
-		$this->notify($path)->listen(function (IChange $change) use ($callback) {
517
-			if ($change instanceof IRenameChange) {
518
-				return $callback($change->getType(), $change->getPath(), $change->getTargetPath());
519
-			} else {
520
-				return $callback($change->getType(), $change->getPath());
521
-			}
522
-		});
523
-	}
524
-
525
-	public function notify($path) {
526
-		$path = '/' . ltrim($path, '/');
527
-		$shareNotifyHandler = $this->share->notify($this->buildPath($path));
528
-		return new SMBNotifyHandler($shareNotifyHandler, $this->root);
529
-	}
58
+    /**
59
+     * @var \Icewind\SMB\Server
60
+     */
61
+    protected $server;
62
+
63
+    /**
64
+     * @var \Icewind\SMB\Share
65
+     */
66
+    protected $share;
67
+
68
+    /**
69
+     * @var string
70
+     */
71
+    protected $root;
72
+
73
+    /**
74
+     * @var \Icewind\SMB\FileInfo[]
75
+     */
76
+    protected $statCache;
77
+
78
+    public function __construct($params) {
79
+        if (isset($params['host']) && isset($params['user']) && isset($params['password']) && isset($params['share'])) {
80
+            if (Server::NativeAvailable()) {
81
+                $this->server = new NativeServer($params['host'], $params['user'], $params['password']);
82
+            } else {
83
+                $this->server = new Server($params['host'], $params['user'], $params['password']);
84
+            }
85
+            $this->share = $this->server->getShare(trim($params['share'], '/'));
86
+
87
+            $this->root = isset($params['root']) ? $params['root'] : '/';
88
+            if (!$this->root || $this->root[0] !== '/') {
89
+                $this->root = '/' . $this->root;
90
+            }
91
+            if (substr($this->root, -1, 1) !== '/') {
92
+                $this->root .= '/';
93
+            }
94
+        } else {
95
+            throw new \Exception('Invalid configuration');
96
+        }
97
+        $this->statCache = new CappedMemoryCache();
98
+        parent::__construct($params);
99
+    }
100
+
101
+    /**
102
+     * @return string
103
+     */
104
+    public function getId() {
105
+        // FIXME: double slash to keep compatible with the old storage ids,
106
+        // failure to do so will lead to creation of a new storage id and
107
+        // loss of shares from the storage
108
+        return 'smb::' . $this->server->getUser() . '@' . $this->server->getHost() . '//' . $this->share->getName() . '/' . $this->root;
109
+    }
110
+
111
+    /**
112
+     * @param string $path
113
+     * @return string
114
+     */
115
+    protected function buildPath($path) {
116
+        return Filesystem::normalizePath($this->root . '/' . $path, true, false, true);
117
+    }
118
+
119
+    protected function relativePath($fullPath) {
120
+        if ($fullPath === $this->root) {
121
+            return '';
122
+        } else if (substr($fullPath, 0, strlen($this->root)) === $this->root) {
123
+            return substr($fullPath, strlen($this->root));
124
+        } else {
125
+            return null;
126
+        }
127
+    }
128
+
129
+    /**
130
+     * @param string $path
131
+     * @return \Icewind\SMB\IFileInfo
132
+     * @throws StorageNotAvailableException
133
+     */
134
+    protected function getFileInfo($path) {
135
+        try {
136
+            $path = $this->buildPath($path);
137
+            if (!isset($this->statCache[$path])) {
138
+                $this->statCache[$path] = $this->share->stat($path);
139
+            }
140
+            return $this->statCache[$path];
141
+        } catch (ConnectException $e) {
142
+            throw new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e);
143
+        }
144
+    }
145
+
146
+    /**
147
+     * @param string $path
148
+     * @return \Icewind\SMB\IFileInfo[]
149
+     * @throws StorageNotAvailableException
150
+     */
151
+    protected function getFolderContents($path) {
152
+        try {
153
+            $path = $this->buildPath($path);
154
+            $files = $this->share->dir($path);
155
+            foreach ($files as $file) {
156
+                $this->statCache[$path . '/' . $file->getName()] = $file;
157
+            }
158
+            return array_filter($files, function (IFileInfo $file) {
159
+                return !$file->isHidden();
160
+            });
161
+        } catch (ConnectException $e) {
162
+            throw new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e);
163
+        }
164
+    }
165
+
166
+    /**
167
+     * @param \Icewind\SMB\IFileInfo $info
168
+     * @return array
169
+     */
170
+    protected function formatInfo($info) {
171
+        $result = [
172
+            'size' => $info->getSize(),
173
+            'mtime' => $info->getMTime(),
174
+        ];
175
+        if ($info->isDirectory()) {
176
+            $result['type'] = 'dir';
177
+        } else {
178
+            $result['type'] = 'file';
179
+        }
180
+        return $result;
181
+    }
182
+
183
+    /**
184
+     * Rename the files. If the source or the target is the root, the rename won't happen.
185
+     *
186
+     * @param string $source the old name of the path
187
+     * @param string $target the new name of the path
188
+     * @return bool true if the rename is successful, false otherwise
189
+     */
190
+    public function rename($source, $target) {
191
+        if ($this->isRootDir($source) || $this->isRootDir($target)) {
192
+            return false;
193
+        }
194
+
195
+        $absoluteSource = $this->buildPath($source);
196
+        $absoluteTarget = $this->buildPath($target);
197
+        try {
198
+            $result = $this->share->rename($absoluteSource, $absoluteTarget);
199
+        } catch (AlreadyExistsException $e) {
200
+            $this->remove($target);
201
+            $result = $this->share->rename($absoluteSource, $absoluteTarget);
202
+        } catch (\Exception $e) {
203
+            \OC::$server->getLogger()->logException($e, ['level' => Util::WARN]);
204
+            return false;
205
+        }
206
+        unset($this->statCache[$absoluteSource], $this->statCache[$absoluteTarget]);
207
+        return $result;
208
+    }
209
+
210
+    /**
211
+     * @param string $path
212
+     * @return array
213
+     */
214
+    public function stat($path) {
215
+        $result = $this->formatInfo($this->getFileInfo($path));
216
+        if ($this->remoteIsShare() && $this->isRootDir($path)) {
217
+            $result['mtime'] = $this->shareMTime();
218
+        }
219
+        return $result;
220
+    }
221
+
222
+    /**
223
+     * get the best guess for the modification time of the share
224
+     *
225
+     * @return int
226
+     */
227
+    private function shareMTime() {
228
+        $highestMTime = 0;
229
+        $files = $this->share->dir($this->root);
230
+        foreach ($files as $fileInfo) {
231
+            if ($fileInfo->getMTime() > $highestMTime) {
232
+                $highestMTime = $fileInfo->getMTime();
233
+            }
234
+        }
235
+        return $highestMTime;
236
+    }
237
+
238
+    /**
239
+     * Check if the path is our root dir (not the smb one)
240
+     *
241
+     * @param string $path the path
242
+     * @return bool
243
+     */
244
+    private function isRootDir($path) {
245
+        return $path === '' || $path === '/' || $path === '.';
246
+    }
247
+
248
+    /**
249
+     * Check if our root points to a smb share
250
+     *
251
+     * @return bool true if our root points to a share false otherwise
252
+     */
253
+    private function remoteIsShare() {
254
+        return $this->share->getName() && (!$this->root || $this->root === '/');
255
+    }
256
+
257
+    /**
258
+     * @param string $path
259
+     * @return bool
260
+     */
261
+    public function unlink($path) {
262
+        if ($this->isRootDir($path)) {
263
+            return false;
264
+        }
265
+
266
+        try {
267
+            if ($this->is_dir($path)) {
268
+                return $this->rmdir($path);
269
+            } else {
270
+                $path = $this->buildPath($path);
271
+                unset($this->statCache[$path]);
272
+                $this->share->del($path);
273
+                return true;
274
+            }
275
+        } catch (NotFoundException $e) {
276
+            return false;
277
+        } catch (ForbiddenException $e) {
278
+            return false;
279
+        } catch (ConnectException $e) {
280
+            throw new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e);
281
+        }
282
+    }
283
+
284
+    /**
285
+     * check if a file or folder has been updated since $time
286
+     *
287
+     * @param string $path
288
+     * @param int $time
289
+     * @return bool
290
+     */
291
+    public function hasUpdated($path, $time) {
292
+        if (!$path and $this->root === '/') {
293
+            // mtime doesn't work for shares, but giving the nature of the backend,
294
+            // doing a full update is still just fast enough
295
+            return true;
296
+        } else {
297
+            $actualTime = $this->filemtime($path);
298
+            return $actualTime > $time;
299
+        }
300
+    }
301
+
302
+    /**
303
+     * @param string $path
304
+     * @param string $mode
305
+     * @return resource|false
306
+     */
307
+    public function fopen($path, $mode) {
308
+        $fullPath = $this->buildPath($path);
309
+        try {
310
+            switch ($mode) {
311
+                case 'r':
312
+                case 'rb':
313
+                    if (!$this->file_exists($path)) {
314
+                        return false;
315
+                    }
316
+                    return $this->share->read($fullPath);
317
+                case 'w':
318
+                case 'wb':
319
+                    $source = $this->share->write($fullPath);
320
+                    return CallBackWrapper::wrap($source, null, null, function () use ($fullPath) {
321
+                        unset($this->statCache[$fullPath]);
322
+                    });
323
+                case 'a':
324
+                case 'ab':
325
+                case 'r+':
326
+                case 'w+':
327
+                case 'wb+':
328
+                case 'a+':
329
+                case 'x':
330
+                case 'x+':
331
+                case 'c':
332
+                case 'c+':
333
+                    //emulate these
334
+                    if (strrpos($path, '.') !== false) {
335
+                        $ext = substr($path, strrpos($path, '.'));
336
+                    } else {
337
+                        $ext = '';
338
+                    }
339
+                    if ($this->file_exists($path)) {
340
+                        if (!$this->isUpdatable($path)) {
341
+                            return false;
342
+                        }
343
+                        $tmpFile = $this->getCachedFile($path);
344
+                    } else {
345
+                        if (!$this->isCreatable(dirname($path))) {
346
+                            return false;
347
+                        }
348
+                        $tmpFile = \OCP\Files::tmpFile($ext);
349
+                    }
350
+                    $source = fopen($tmpFile, $mode);
351
+                    $share = $this->share;
352
+                    return CallbackWrapper::wrap($source, null, null, function () use ($tmpFile, $fullPath, $share) {
353
+                        unset($this->statCache[$fullPath]);
354
+                        $share->put($tmpFile, $fullPath);
355
+                        unlink($tmpFile);
356
+                    });
357
+            }
358
+            return false;
359
+        } catch (NotFoundException $e) {
360
+            return false;
361
+        } catch (ForbiddenException $e) {
362
+            return false;
363
+        } catch (ConnectException $e) {
364
+            throw new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e);
365
+        }
366
+    }
367
+
368
+    public function rmdir($path) {
369
+        if ($this->isRootDir($path)) {
370
+            return false;
371
+        }
372
+
373
+        try {
374
+            $this->statCache = array();
375
+            $content = $this->share->dir($this->buildPath($path));
376
+            foreach ($content as $file) {
377
+                if ($file->isDirectory()) {
378
+                    $this->rmdir($path . '/' . $file->getName());
379
+                } else {
380
+                    $this->share->del($file->getPath());
381
+                }
382
+            }
383
+            $this->share->rmdir($this->buildPath($path));
384
+            return true;
385
+        } catch (NotFoundException $e) {
386
+            return false;
387
+        } catch (ForbiddenException $e) {
388
+            return false;
389
+        } catch (ConnectException $e) {
390
+            throw new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e);
391
+        }
392
+    }
393
+
394
+    public function touch($path, $time = null) {
395
+        try {
396
+            if (!$this->file_exists($path)) {
397
+                $fh = $this->share->write($this->buildPath($path));
398
+                fclose($fh);
399
+                return true;
400
+            }
401
+            return false;
402
+        } catch (ConnectException $e) {
403
+            throw new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e);
404
+        }
405
+    }
406
+
407
+    public function opendir($path) {
408
+        try {
409
+            $files = $this->getFolderContents($path);
410
+        } catch (NotFoundException $e) {
411
+            return false;
412
+        } catch (ForbiddenException $e) {
413
+            return false;
414
+        }
415
+        $names = array_map(function ($info) {
416
+            /** @var \Icewind\SMB\IFileInfo $info */
417
+            return $info->getName();
418
+        }, $files);
419
+        return IteratorDirectory::wrap($names);
420
+    }
421
+
422
+    public function filetype($path) {
423
+        try {
424
+            return $this->getFileInfo($path)->isDirectory() ? 'dir' : 'file';
425
+        } catch (NotFoundException $e) {
426
+            return false;
427
+        } catch (ForbiddenException $e) {
428
+            return false;
429
+        }
430
+    }
431
+
432
+    public function mkdir($path) {
433
+        $path = $this->buildPath($path);
434
+        try {
435
+            $this->share->mkdir($path);
436
+            return true;
437
+        } catch (ConnectException $e) {
438
+            throw new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e);
439
+        } catch (Exception $e) {
440
+            return false;
441
+        }
442
+    }
443
+
444
+    public function file_exists($path) {
445
+        try {
446
+            $this->getFileInfo($path);
447
+            return true;
448
+        } catch (NotFoundException $e) {
449
+            return false;
450
+        } catch (ForbiddenException $e) {
451
+            return false;
452
+        } catch (ConnectException $e) {
453
+            throw new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e);
454
+        }
455
+    }
456
+
457
+    public function isReadable($path) {
458
+        try {
459
+            $info = $this->getFileInfo($path);
460
+            return !$info->isHidden();
461
+        } catch (NotFoundException $e) {
462
+            return false;
463
+        } catch (ForbiddenException $e) {
464
+            return false;
465
+        }
466
+    }
467
+
468
+    public function isUpdatable($path) {
469
+        try {
470
+            $info = $this->getFileInfo($path);
471
+            // following windows behaviour for read-only folders: they can be written into
472
+            // (https://support.microsoft.com/en-us/kb/326549 - "cause" section)
473
+            return !$info->isHidden() && (!$info->isReadOnly() || $this->is_dir($path));
474
+        } catch (NotFoundException $e) {
475
+            return false;
476
+        } catch (ForbiddenException $e) {
477
+            return false;
478
+        }
479
+    }
480
+
481
+    public function isDeletable($path) {
482
+        try {
483
+            $info = $this->getFileInfo($path);
484
+            return !$info->isHidden() && !$info->isReadOnly();
485
+        } catch (NotFoundException $e) {
486
+            return false;
487
+        } catch (ForbiddenException $e) {
488
+            return false;
489
+        }
490
+    }
491
+
492
+    /**
493
+     * check if smbclient is installed
494
+     */
495
+    public static function checkDependencies() {
496
+        return (
497
+            (bool)\OC_Helper::findBinaryPath('smbclient')
498
+            || Server::NativeAvailable()
499
+        ) ? true : ['smbclient'];
500
+    }
501
+
502
+    /**
503
+     * Test a storage for availability
504
+     *
505
+     * @return bool
506
+     */
507
+    public function test() {
508
+        try {
509
+            return parent::test();
510
+        } catch (Exception $e) {
511
+            return false;
512
+        }
513
+    }
514
+
515
+    public function listen($path, callable $callback) {
516
+        $this->notify($path)->listen(function (IChange $change) use ($callback) {
517
+            if ($change instanceof IRenameChange) {
518
+                return $callback($change->getType(), $change->getPath(), $change->getTargetPath());
519
+            } else {
520
+                return $callback($change->getType(), $change->getPath());
521
+            }
522
+        });
523
+    }
524
+
525
+    public function notify($path) {
526
+        $path = '/' . ltrim($path, '/');
527
+        $shareNotifyHandler = $this->share->notify($this->buildPath($path));
528
+        return new SMBNotifyHandler($shareNotifyHandler, $this->root);
529
+    }
530 530
 }
Please login to merge, or discard this patch.
lib/private/Files/Storage/Common.php 1 patch
Indentation   +733 added lines, -733 removed lines patch added patch discarded remove patch
@@ -70,741 +70,741 @@
 block discarded – undo
70 70
  */
71 71
 abstract class Common implements Storage, ILockingStorage {
72 72
 
73
-	use LocalTempFileTrait;
74
-
75
-	protected $cache;
76
-	protected $scanner;
77
-	protected $watcher;
78
-	protected $propagator;
79
-	protected $storageCache;
80
-	protected $updater;
81
-
82
-	protected $mountOptions = [];
83
-	protected $owner = null;
84
-
85
-	private $shouldLogLocks = null;
86
-	private $logger;
87
-
88
-	public function __construct($parameters) {
89
-	}
90
-
91
-	/**
92
-	 * Remove a file or folder
93
-	 *
94
-	 * @param string $path
95
-	 * @return bool
96
-	 */
97
-	protected function remove($path) {
98
-		if ($this->is_dir($path)) {
99
-			return $this->rmdir($path);
100
-		} else if ($this->is_file($path)) {
101
-			return $this->unlink($path);
102
-		} else {
103
-			return false;
104
-		}
105
-	}
106
-
107
-	public function is_dir($path) {
108
-		return $this->filetype($path) === 'dir';
109
-	}
110
-
111
-	public function is_file($path) {
112
-		return $this->filetype($path) === 'file';
113
-	}
114
-
115
-	public function filesize($path) {
116
-		if ($this->is_dir($path)) {
117
-			return 0; //by definition
118
-		} else {
119
-			$stat = $this->stat($path);
120
-			if (isset($stat['size'])) {
121
-				return $stat['size'];
122
-			} else {
123
-				return 0;
124
-			}
125
-		}
126
-	}
127
-
128
-	public function isReadable($path) {
129
-		// at least check whether it exists
130
-		// subclasses might want to implement this more thoroughly
131
-		return $this->file_exists($path);
132
-	}
133
-
134
-	public function isUpdatable($path) {
135
-		// at least check whether it exists
136
-		// subclasses might want to implement this more thoroughly
137
-		// a non-existing file/folder isn't updatable
138
-		return $this->file_exists($path);
139
-	}
140
-
141
-	public function isCreatable($path) {
142
-		if ($this->is_dir($path) && $this->isUpdatable($path)) {
143
-			return true;
144
-		}
145
-		return false;
146
-	}
147
-
148
-	public function isDeletable($path) {
149
-		if ($path === '' || $path === '/') {
150
-			return false;
151
-		}
152
-		$parent = dirname($path);
153
-		return $this->isUpdatable($parent) && $this->isUpdatable($path);
154
-	}
155
-
156
-	public function isSharable($path) {
157
-		return $this->isReadable($path);
158
-	}
159
-
160
-	public function getPermissions($path) {
161
-		$permissions = 0;
162
-		if ($this->isCreatable($path)) {
163
-			$permissions |= \OCP\Constants::PERMISSION_CREATE;
164
-		}
165
-		if ($this->isReadable($path)) {
166
-			$permissions |= \OCP\Constants::PERMISSION_READ;
167
-		}
168
-		if ($this->isUpdatable($path)) {
169
-			$permissions |= \OCP\Constants::PERMISSION_UPDATE;
170
-		}
171
-		if ($this->isDeletable($path)) {
172
-			$permissions |= \OCP\Constants::PERMISSION_DELETE;
173
-		}
174
-		if ($this->isSharable($path)) {
175
-			$permissions |= \OCP\Constants::PERMISSION_SHARE;
176
-		}
177
-		return $permissions;
178
-	}
179
-
180
-	public function filemtime($path) {
181
-		$stat = $this->stat($path);
182
-		if (isset($stat['mtime']) && $stat['mtime'] > 0) {
183
-			return $stat['mtime'];
184
-		} else {
185
-			return 0;
186
-		}
187
-	}
188
-
189
-	public function file_get_contents($path) {
190
-		$handle = $this->fopen($path, "r");
191
-		if (!$handle) {
192
-			return false;
193
-		}
194
-		$data = stream_get_contents($handle);
195
-		fclose($handle);
196
-		return $data;
197
-	}
198
-
199
-	public function file_put_contents($path, $data) {
200
-		$handle = $this->fopen($path, "w");
201
-		$this->removeCachedFile($path);
202
-		$count = fwrite($handle, $data);
203
-		fclose($handle);
204
-		return $count;
205
-	}
206
-
207
-	public function rename($path1, $path2) {
208
-		$this->remove($path2);
209
-
210
-		$this->removeCachedFile($path1);
211
-		return $this->copy($path1, $path2) and $this->remove($path1);
212
-	}
213
-
214
-	public function copy($path1, $path2) {
215
-		if ($this->is_dir($path1)) {
216
-			$this->remove($path2);
217
-			$dir = $this->opendir($path1);
218
-			$this->mkdir($path2);
219
-			while ($file = readdir($dir)) {
220
-				if (!Filesystem::isIgnoredDir($file)) {
221
-					if (!$this->copy($path1 . '/' . $file, $path2 . '/' . $file)) {
222
-						return false;
223
-					}
224
-				}
225
-			}
226
-			closedir($dir);
227
-			return true;
228
-		} else {
229
-			$source = $this->fopen($path1, 'r');
230
-			$target = $this->fopen($path2, 'w');
231
-			list(, $result) = \OC_Helper::streamCopy($source, $target);
232
-			if (!$result) {
233
-				\OC::$server->getLogger()->warning("Failed to write data while copying $path1 to $path2");
234
-			}
235
-			$this->removeCachedFile($path2);
236
-			return $result;
237
-		}
238
-	}
239
-
240
-	public function getMimeType($path) {
241
-		if ($this->is_dir($path)) {
242
-			return 'httpd/unix-directory';
243
-		} elseif ($this->file_exists($path)) {
244
-			return \OC::$server->getMimeTypeDetector()->detectPath($path);
245
-		} else {
246
-			return false;
247
-		}
248
-	}
249
-
250
-	public function hash($type, $path, $raw = false) {
251
-		$fh = $this->fopen($path, 'rb');
252
-		$ctx = hash_init($type);
253
-		hash_update_stream($ctx, $fh);
254
-		fclose($fh);
255
-		return hash_final($ctx, $raw);
256
-	}
257
-
258
-	public function search($query) {
259
-		return $this->searchInDir($query);
260
-	}
261
-
262
-	public function getLocalFile($path) {
263
-		return $this->getCachedFile($path);
264
-	}
265
-
266
-	/**
267
-	 * @param string $path
268
-	 * @param string $target
269
-	 */
270
-	private function addLocalFolder($path, $target) {
271
-		$dh = $this->opendir($path);
272
-		if (is_resource($dh)) {
273
-			while (($file = readdir($dh)) !== false) {
274
-				if (!\OC\Files\Filesystem::isIgnoredDir($file)) {
275
-					if ($this->is_dir($path . '/' . $file)) {
276
-						mkdir($target . '/' . $file);
277
-						$this->addLocalFolder($path . '/' . $file, $target . '/' . $file);
278
-					} else {
279
-						$tmp = $this->toTmpFile($path . '/' . $file);
280
-						rename($tmp, $target . '/' . $file);
281
-					}
282
-				}
283
-			}
284
-		}
285
-	}
286
-
287
-	/**
288
-	 * @param string $query
289
-	 * @param string $dir
290
-	 * @return array
291
-	 */
292
-	protected function searchInDir($query, $dir = '') {
293
-		$files = array();
294
-		$dh = $this->opendir($dir);
295
-		if (is_resource($dh)) {
296
-			while (($item = readdir($dh)) !== false) {
297
-				if (\OC\Files\Filesystem::isIgnoredDir($item)) continue;
298
-				if (strstr(strtolower($item), strtolower($query)) !== false) {
299
-					$files[] = $dir . '/' . $item;
300
-				}
301
-				if ($this->is_dir($dir . '/' . $item)) {
302
-					$files = array_merge($files, $this->searchInDir($query, $dir . '/' . $item));
303
-				}
304
-			}
305
-		}
306
-		closedir($dh);
307
-		return $files;
308
-	}
309
-
310
-	/**
311
-	 * check if a file or folder has been updated since $time
312
-	 *
313
-	 * The method is only used to check if the cache needs to be updated. Storage backends that don't support checking
314
-	 * the mtime should always return false here. As a result storage implementations that always return false expect
315
-	 * exclusive access to the backend and will not pick up files that have been added in a way that circumvents
316
-	 * ownClouds filesystem.
317
-	 *
318
-	 * @param string $path
319
-	 * @param int $time
320
-	 * @return bool
321
-	 */
322
-	public function hasUpdated($path, $time) {
323
-		return $this->filemtime($path) > $time;
324
-	}
325
-
326
-	public function getCache($path = '', $storage = null) {
327
-		if (!$storage) {
328
-			$storage = $this;
329
-		}
330
-		if (!isset($storage->cache)) {
331
-			$storage->cache = new Cache($storage);
332
-		}
333
-		return $storage->cache;
334
-	}
335
-
336
-	public function getScanner($path = '', $storage = null) {
337
-		if (!$storage) {
338
-			$storage = $this;
339
-		}
340
-		if (!isset($storage->scanner)) {
341
-			$storage->scanner = new Scanner($storage);
342
-		}
343
-		return $storage->scanner;
344
-	}
345
-
346
-	public function getWatcher($path = '', $storage = null) {
347
-		if (!$storage) {
348
-			$storage = $this;
349
-		}
350
-		if (!isset($this->watcher)) {
351
-			$this->watcher = new Watcher($storage);
352
-			$globalPolicy = \OC::$server->getConfig()->getSystemValue('filesystem_check_changes', Watcher::CHECK_NEVER);
353
-			$this->watcher->setPolicy((int)$this->getMountOption('filesystem_check_changes', $globalPolicy));
354
-		}
355
-		return $this->watcher;
356
-	}
357
-
358
-	/**
359
-	 * get a propagator instance for the cache
360
-	 *
361
-	 * @param \OC\Files\Storage\Storage (optional) the storage to pass to the watcher
362
-	 * @return \OC\Files\Cache\Propagator
363
-	 */
364
-	public function getPropagator($storage = null) {
365
-		if (!$storage) {
366
-			$storage = $this;
367
-		}
368
-		if (!isset($storage->propagator)) {
369
-			$storage->propagator = new Propagator($storage, \OC::$server->getDatabaseConnection());
370
-		}
371
-		return $storage->propagator;
372
-	}
373
-
374
-	public function getUpdater($storage = null) {
375
-		if (!$storage) {
376
-			$storage = $this;
377
-		}
378
-		if (!isset($storage->updater)) {
379
-			$storage->updater = new Updater($storage);
380
-		}
381
-		return $storage->updater;
382
-	}
383
-
384
-	public function getStorageCache($storage = null) {
385
-		if (!$storage) {
386
-			$storage = $this;
387
-		}
388
-		if (!isset($this->storageCache)) {
389
-			$this->storageCache = new \OC\Files\Cache\Storage($storage);
390
-		}
391
-		return $this->storageCache;
392
-	}
393
-
394
-	/**
395
-	 * get the owner of a path
396
-	 *
397
-	 * @param string $path The path to get the owner
398
-	 * @return string|false uid or false
399
-	 */
400
-	public function getOwner($path) {
401
-		if ($this->owner === null) {
402
-			$this->owner = \OC_User::getUser();
403
-		}
404
-
405
-		return $this->owner;
406
-	}
407
-
408
-	/**
409
-	 * get the ETag for a file or folder
410
-	 *
411
-	 * @param string $path
412
-	 * @return string
413
-	 */
414
-	public function getETag($path) {
415
-		return uniqid();
416
-	}
417
-
418
-	/**
419
-	 * clean a path, i.e. remove all redundant '.' and '..'
420
-	 * making sure that it can't point to higher than '/'
421
-	 *
422
-	 * @param string $path The path to clean
423
-	 * @return string cleaned path
424
-	 */
425
-	public function cleanPath($path) {
426
-		if (strlen($path) == 0 or $path[0] != '/') {
427
-			$path = '/' . $path;
428
-		}
429
-
430
-		$output = array();
431
-		foreach (explode('/', $path) as $chunk) {
432
-			if ($chunk == '..') {
433
-				array_pop($output);
434
-			} else if ($chunk == '.') {
435
-			} else {
436
-				$output[] = $chunk;
437
-			}
438
-		}
439
-		return implode('/', $output);
440
-	}
441
-
442
-	/**
443
-	 * Test a storage for availability
444
-	 *
445
-	 * @return bool
446
-	 */
447
-	public function test() {
448
-		try {
449
-			if ($this->stat('')) {
450
-				return true;
451
-			}
452
-			return false;
453
-		} catch (\Exception $e) {
454
-			return false;
455
-		}
456
-	}
457
-
458
-	/**
459
-	 * get the free space in the storage
460
-	 *
461
-	 * @param string $path
462
-	 * @return int|false
463
-	 */
464
-	public function free_space($path) {
465
-		return \OCP\Files\FileInfo::SPACE_UNKNOWN;
466
-	}
467
-
468
-	/**
469
-	 * {@inheritdoc}
470
-	 */
471
-	public function isLocal() {
472
-		// the common implementation returns a temporary file by
473
-		// default, which is not local
474
-		return false;
475
-	}
476
-
477
-	/**
478
-	 * Check if the storage is an instance of $class or is a wrapper for a storage that is an instance of $class
479
-	 *
480
-	 * @param string $class
481
-	 * @return bool
482
-	 */
483
-	public function instanceOfStorage($class) {
484
-		if (ltrim($class, '\\') === 'OC\Files\Storage\Shared') {
485
-			// FIXME Temporary fix to keep existing checks working
486
-			$class = '\OCA\Files_Sharing\SharedStorage';
487
-		}
488
-		return is_a($this, $class);
489
-	}
490
-
491
-	/**
492
-	 * A custom storage implementation can return an url for direct download of a give file.
493
-	 *
494
-	 * For now the returned array can hold the parameter url - in future more attributes might follow.
495
-	 *
496
-	 * @param string $path
497
-	 * @return array|false
498
-	 */
499
-	public function getDirectDownload($path) {
500
-		return [];
501
-	}
502
-
503
-	/**
504
-	 * @inheritdoc
505
-	 * @throws InvalidPathException
506
-	 */
507
-	public function verifyPath($path, $fileName) {
508
-
509
-		// verify empty and dot files
510
-		$trimmed = trim($fileName);
511
-		if ($trimmed === '') {
512
-			throw new EmptyFileNameException();
513
-		}
514
-
515
-		if (\OC\Files\Filesystem::isIgnoredDir($trimmed)) {
516
-			throw new InvalidDirectoryException();
517
-		}
518
-
519
-		if (!\OC::$server->getDatabaseConnection()->supports4ByteText()) {
520
-			// verify database - e.g. mysql only 3-byte chars
521
-			if (preg_match('%(?:
73
+    use LocalTempFileTrait;
74
+
75
+    protected $cache;
76
+    protected $scanner;
77
+    protected $watcher;
78
+    protected $propagator;
79
+    protected $storageCache;
80
+    protected $updater;
81
+
82
+    protected $mountOptions = [];
83
+    protected $owner = null;
84
+
85
+    private $shouldLogLocks = null;
86
+    private $logger;
87
+
88
+    public function __construct($parameters) {
89
+    }
90
+
91
+    /**
92
+     * Remove a file or folder
93
+     *
94
+     * @param string $path
95
+     * @return bool
96
+     */
97
+    protected function remove($path) {
98
+        if ($this->is_dir($path)) {
99
+            return $this->rmdir($path);
100
+        } else if ($this->is_file($path)) {
101
+            return $this->unlink($path);
102
+        } else {
103
+            return false;
104
+        }
105
+    }
106
+
107
+    public function is_dir($path) {
108
+        return $this->filetype($path) === 'dir';
109
+    }
110
+
111
+    public function is_file($path) {
112
+        return $this->filetype($path) === 'file';
113
+    }
114
+
115
+    public function filesize($path) {
116
+        if ($this->is_dir($path)) {
117
+            return 0; //by definition
118
+        } else {
119
+            $stat = $this->stat($path);
120
+            if (isset($stat['size'])) {
121
+                return $stat['size'];
122
+            } else {
123
+                return 0;
124
+            }
125
+        }
126
+    }
127
+
128
+    public function isReadable($path) {
129
+        // at least check whether it exists
130
+        // subclasses might want to implement this more thoroughly
131
+        return $this->file_exists($path);
132
+    }
133
+
134
+    public function isUpdatable($path) {
135
+        // at least check whether it exists
136
+        // subclasses might want to implement this more thoroughly
137
+        // a non-existing file/folder isn't updatable
138
+        return $this->file_exists($path);
139
+    }
140
+
141
+    public function isCreatable($path) {
142
+        if ($this->is_dir($path) && $this->isUpdatable($path)) {
143
+            return true;
144
+        }
145
+        return false;
146
+    }
147
+
148
+    public function isDeletable($path) {
149
+        if ($path === '' || $path === '/') {
150
+            return false;
151
+        }
152
+        $parent = dirname($path);
153
+        return $this->isUpdatable($parent) && $this->isUpdatable($path);
154
+    }
155
+
156
+    public function isSharable($path) {
157
+        return $this->isReadable($path);
158
+    }
159
+
160
+    public function getPermissions($path) {
161
+        $permissions = 0;
162
+        if ($this->isCreatable($path)) {
163
+            $permissions |= \OCP\Constants::PERMISSION_CREATE;
164
+        }
165
+        if ($this->isReadable($path)) {
166
+            $permissions |= \OCP\Constants::PERMISSION_READ;
167
+        }
168
+        if ($this->isUpdatable($path)) {
169
+            $permissions |= \OCP\Constants::PERMISSION_UPDATE;
170
+        }
171
+        if ($this->isDeletable($path)) {
172
+            $permissions |= \OCP\Constants::PERMISSION_DELETE;
173
+        }
174
+        if ($this->isSharable($path)) {
175
+            $permissions |= \OCP\Constants::PERMISSION_SHARE;
176
+        }
177
+        return $permissions;
178
+    }
179
+
180
+    public function filemtime($path) {
181
+        $stat = $this->stat($path);
182
+        if (isset($stat['mtime']) && $stat['mtime'] > 0) {
183
+            return $stat['mtime'];
184
+        } else {
185
+            return 0;
186
+        }
187
+    }
188
+
189
+    public function file_get_contents($path) {
190
+        $handle = $this->fopen($path, "r");
191
+        if (!$handle) {
192
+            return false;
193
+        }
194
+        $data = stream_get_contents($handle);
195
+        fclose($handle);
196
+        return $data;
197
+    }
198
+
199
+    public function file_put_contents($path, $data) {
200
+        $handle = $this->fopen($path, "w");
201
+        $this->removeCachedFile($path);
202
+        $count = fwrite($handle, $data);
203
+        fclose($handle);
204
+        return $count;
205
+    }
206
+
207
+    public function rename($path1, $path2) {
208
+        $this->remove($path2);
209
+
210
+        $this->removeCachedFile($path1);
211
+        return $this->copy($path1, $path2) and $this->remove($path1);
212
+    }
213
+
214
+    public function copy($path1, $path2) {
215
+        if ($this->is_dir($path1)) {
216
+            $this->remove($path2);
217
+            $dir = $this->opendir($path1);
218
+            $this->mkdir($path2);
219
+            while ($file = readdir($dir)) {
220
+                if (!Filesystem::isIgnoredDir($file)) {
221
+                    if (!$this->copy($path1 . '/' . $file, $path2 . '/' . $file)) {
222
+                        return false;
223
+                    }
224
+                }
225
+            }
226
+            closedir($dir);
227
+            return true;
228
+        } else {
229
+            $source = $this->fopen($path1, 'r');
230
+            $target = $this->fopen($path2, 'w');
231
+            list(, $result) = \OC_Helper::streamCopy($source, $target);
232
+            if (!$result) {
233
+                \OC::$server->getLogger()->warning("Failed to write data while copying $path1 to $path2");
234
+            }
235
+            $this->removeCachedFile($path2);
236
+            return $result;
237
+        }
238
+    }
239
+
240
+    public function getMimeType($path) {
241
+        if ($this->is_dir($path)) {
242
+            return 'httpd/unix-directory';
243
+        } elseif ($this->file_exists($path)) {
244
+            return \OC::$server->getMimeTypeDetector()->detectPath($path);
245
+        } else {
246
+            return false;
247
+        }
248
+    }
249
+
250
+    public function hash($type, $path, $raw = false) {
251
+        $fh = $this->fopen($path, 'rb');
252
+        $ctx = hash_init($type);
253
+        hash_update_stream($ctx, $fh);
254
+        fclose($fh);
255
+        return hash_final($ctx, $raw);
256
+    }
257
+
258
+    public function search($query) {
259
+        return $this->searchInDir($query);
260
+    }
261
+
262
+    public function getLocalFile($path) {
263
+        return $this->getCachedFile($path);
264
+    }
265
+
266
+    /**
267
+     * @param string $path
268
+     * @param string $target
269
+     */
270
+    private function addLocalFolder($path, $target) {
271
+        $dh = $this->opendir($path);
272
+        if (is_resource($dh)) {
273
+            while (($file = readdir($dh)) !== false) {
274
+                if (!\OC\Files\Filesystem::isIgnoredDir($file)) {
275
+                    if ($this->is_dir($path . '/' . $file)) {
276
+                        mkdir($target . '/' . $file);
277
+                        $this->addLocalFolder($path . '/' . $file, $target . '/' . $file);
278
+                    } else {
279
+                        $tmp = $this->toTmpFile($path . '/' . $file);
280
+                        rename($tmp, $target . '/' . $file);
281
+                    }
282
+                }
283
+            }
284
+        }
285
+    }
286
+
287
+    /**
288
+     * @param string $query
289
+     * @param string $dir
290
+     * @return array
291
+     */
292
+    protected function searchInDir($query, $dir = '') {
293
+        $files = array();
294
+        $dh = $this->opendir($dir);
295
+        if (is_resource($dh)) {
296
+            while (($item = readdir($dh)) !== false) {
297
+                if (\OC\Files\Filesystem::isIgnoredDir($item)) continue;
298
+                if (strstr(strtolower($item), strtolower($query)) !== false) {
299
+                    $files[] = $dir . '/' . $item;
300
+                }
301
+                if ($this->is_dir($dir . '/' . $item)) {
302
+                    $files = array_merge($files, $this->searchInDir($query, $dir . '/' . $item));
303
+                }
304
+            }
305
+        }
306
+        closedir($dh);
307
+        return $files;
308
+    }
309
+
310
+    /**
311
+     * check if a file or folder has been updated since $time
312
+     *
313
+     * The method is only used to check if the cache needs to be updated. Storage backends that don't support checking
314
+     * the mtime should always return false here. As a result storage implementations that always return false expect
315
+     * exclusive access to the backend and will not pick up files that have been added in a way that circumvents
316
+     * ownClouds filesystem.
317
+     *
318
+     * @param string $path
319
+     * @param int $time
320
+     * @return bool
321
+     */
322
+    public function hasUpdated($path, $time) {
323
+        return $this->filemtime($path) > $time;
324
+    }
325
+
326
+    public function getCache($path = '', $storage = null) {
327
+        if (!$storage) {
328
+            $storage = $this;
329
+        }
330
+        if (!isset($storage->cache)) {
331
+            $storage->cache = new Cache($storage);
332
+        }
333
+        return $storage->cache;
334
+    }
335
+
336
+    public function getScanner($path = '', $storage = null) {
337
+        if (!$storage) {
338
+            $storage = $this;
339
+        }
340
+        if (!isset($storage->scanner)) {
341
+            $storage->scanner = new Scanner($storage);
342
+        }
343
+        return $storage->scanner;
344
+    }
345
+
346
+    public function getWatcher($path = '', $storage = null) {
347
+        if (!$storage) {
348
+            $storage = $this;
349
+        }
350
+        if (!isset($this->watcher)) {
351
+            $this->watcher = new Watcher($storage);
352
+            $globalPolicy = \OC::$server->getConfig()->getSystemValue('filesystem_check_changes', Watcher::CHECK_NEVER);
353
+            $this->watcher->setPolicy((int)$this->getMountOption('filesystem_check_changes', $globalPolicy));
354
+        }
355
+        return $this->watcher;
356
+    }
357
+
358
+    /**
359
+     * get a propagator instance for the cache
360
+     *
361
+     * @param \OC\Files\Storage\Storage (optional) the storage to pass to the watcher
362
+     * @return \OC\Files\Cache\Propagator
363
+     */
364
+    public function getPropagator($storage = null) {
365
+        if (!$storage) {
366
+            $storage = $this;
367
+        }
368
+        if (!isset($storage->propagator)) {
369
+            $storage->propagator = new Propagator($storage, \OC::$server->getDatabaseConnection());
370
+        }
371
+        return $storage->propagator;
372
+    }
373
+
374
+    public function getUpdater($storage = null) {
375
+        if (!$storage) {
376
+            $storage = $this;
377
+        }
378
+        if (!isset($storage->updater)) {
379
+            $storage->updater = new Updater($storage);
380
+        }
381
+        return $storage->updater;
382
+    }
383
+
384
+    public function getStorageCache($storage = null) {
385
+        if (!$storage) {
386
+            $storage = $this;
387
+        }
388
+        if (!isset($this->storageCache)) {
389
+            $this->storageCache = new \OC\Files\Cache\Storage($storage);
390
+        }
391
+        return $this->storageCache;
392
+    }
393
+
394
+    /**
395
+     * get the owner of a path
396
+     *
397
+     * @param string $path The path to get the owner
398
+     * @return string|false uid or false
399
+     */
400
+    public function getOwner($path) {
401
+        if ($this->owner === null) {
402
+            $this->owner = \OC_User::getUser();
403
+        }
404
+
405
+        return $this->owner;
406
+    }
407
+
408
+    /**
409
+     * get the ETag for a file or folder
410
+     *
411
+     * @param string $path
412
+     * @return string
413
+     */
414
+    public function getETag($path) {
415
+        return uniqid();
416
+    }
417
+
418
+    /**
419
+     * clean a path, i.e. remove all redundant '.' and '..'
420
+     * making sure that it can't point to higher than '/'
421
+     *
422
+     * @param string $path The path to clean
423
+     * @return string cleaned path
424
+     */
425
+    public function cleanPath($path) {
426
+        if (strlen($path) == 0 or $path[0] != '/') {
427
+            $path = '/' . $path;
428
+        }
429
+
430
+        $output = array();
431
+        foreach (explode('/', $path) as $chunk) {
432
+            if ($chunk == '..') {
433
+                array_pop($output);
434
+            } else if ($chunk == '.') {
435
+            } else {
436
+                $output[] = $chunk;
437
+            }
438
+        }
439
+        return implode('/', $output);
440
+    }
441
+
442
+    /**
443
+     * Test a storage for availability
444
+     *
445
+     * @return bool
446
+     */
447
+    public function test() {
448
+        try {
449
+            if ($this->stat('')) {
450
+                return true;
451
+            }
452
+            return false;
453
+        } catch (\Exception $e) {
454
+            return false;
455
+        }
456
+    }
457
+
458
+    /**
459
+     * get the free space in the storage
460
+     *
461
+     * @param string $path
462
+     * @return int|false
463
+     */
464
+    public function free_space($path) {
465
+        return \OCP\Files\FileInfo::SPACE_UNKNOWN;
466
+    }
467
+
468
+    /**
469
+     * {@inheritdoc}
470
+     */
471
+    public function isLocal() {
472
+        // the common implementation returns a temporary file by
473
+        // default, which is not local
474
+        return false;
475
+    }
476
+
477
+    /**
478
+     * Check if the storage is an instance of $class or is a wrapper for a storage that is an instance of $class
479
+     *
480
+     * @param string $class
481
+     * @return bool
482
+     */
483
+    public function instanceOfStorage($class) {
484
+        if (ltrim($class, '\\') === 'OC\Files\Storage\Shared') {
485
+            // FIXME Temporary fix to keep existing checks working
486
+            $class = '\OCA\Files_Sharing\SharedStorage';
487
+        }
488
+        return is_a($this, $class);
489
+    }
490
+
491
+    /**
492
+     * A custom storage implementation can return an url for direct download of a give file.
493
+     *
494
+     * For now the returned array can hold the parameter url - in future more attributes might follow.
495
+     *
496
+     * @param string $path
497
+     * @return array|false
498
+     */
499
+    public function getDirectDownload($path) {
500
+        return [];
501
+    }
502
+
503
+    /**
504
+     * @inheritdoc
505
+     * @throws InvalidPathException
506
+     */
507
+    public function verifyPath($path, $fileName) {
508
+
509
+        // verify empty and dot files
510
+        $trimmed = trim($fileName);
511
+        if ($trimmed === '') {
512
+            throw new EmptyFileNameException();
513
+        }
514
+
515
+        if (\OC\Files\Filesystem::isIgnoredDir($trimmed)) {
516
+            throw new InvalidDirectoryException();
517
+        }
518
+
519
+        if (!\OC::$server->getDatabaseConnection()->supports4ByteText()) {
520
+            // verify database - e.g. mysql only 3-byte chars
521
+            if (preg_match('%(?:
522 522
       \xF0[\x90-\xBF][\x80-\xBF]{2}      # planes 1-3
523 523
     | [\xF1-\xF3][\x80-\xBF]{3}          # planes 4-15
524 524
     | \xF4[\x80-\x8F][\x80-\xBF]{2}      # plane 16
525 525
 )%xs', $fileName)) {
526
-				throw new InvalidCharacterInPathException();
527
-			}
528
-		}
529
-
530
-		if (isset($fileName[255])) {
531
-			throw new FileNameTooLongException();
532
-		}
533
-
534
-		// NOTE: $path will remain unverified for now
535
-		$this->verifyPosixPath($fileName);
536
-	}
537
-
538
-	/**
539
-	 * @param string $fileName
540
-	 * @throws InvalidPathException
541
-	 */
542
-	protected function verifyPosixPath($fileName) {
543
-		$fileName = trim($fileName);
544
-		$this->scanForInvalidCharacters($fileName, "\\/");
545
-		$reservedNames = ['*'];
546
-		if (in_array($fileName, $reservedNames)) {
547
-			throw new ReservedWordException();
548
-		}
549
-	}
550
-
551
-	/**
552
-	 * @param string $fileName
553
-	 * @param string $invalidChars
554
-	 * @throws InvalidPathException
555
-	 */
556
-	private function scanForInvalidCharacters($fileName, $invalidChars) {
557
-		foreach (str_split($invalidChars) as $char) {
558
-			if (strpos($fileName, $char) !== false) {
559
-				throw new InvalidCharacterInPathException();
560
-			}
561
-		}
562
-
563
-		$sanitizedFileName = filter_var($fileName, FILTER_UNSAFE_RAW, FILTER_FLAG_STRIP_LOW);
564
-		if ($sanitizedFileName !== $fileName) {
565
-			throw new InvalidCharacterInPathException();
566
-		}
567
-	}
568
-
569
-	/**
570
-	 * @param array $options
571
-	 */
572
-	public function setMountOptions(array $options) {
573
-		$this->mountOptions = $options;
574
-	}
575
-
576
-	/**
577
-	 * @param string $name
578
-	 * @param mixed $default
579
-	 * @return mixed
580
-	 */
581
-	public function getMountOption($name, $default = null) {
582
-		return isset($this->mountOptions[$name]) ? $this->mountOptions[$name] : $default;
583
-	}
584
-
585
-	/**
586
-	 * @param IStorage $sourceStorage
587
-	 * @param string $sourceInternalPath
588
-	 * @param string $targetInternalPath
589
-	 * @param bool $preserveMtime
590
-	 * @return bool
591
-	 */
592
-	public function copyFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath, $preserveMtime = false) {
593
-		if ($sourceStorage === $this) {
594
-			return $this->copy($sourceInternalPath, $targetInternalPath);
595
-		}
596
-
597
-		if ($sourceStorage->is_dir($sourceInternalPath)) {
598
-			$dh = $sourceStorage->opendir($sourceInternalPath);
599
-			$result = $this->mkdir($targetInternalPath);
600
-			if (is_resource($dh)) {
601
-				while ($result and ($file = readdir($dh)) !== false) {
602
-					if (!Filesystem::isIgnoredDir($file)) {
603
-						$result &= $this->copyFromStorage($sourceStorage, $sourceInternalPath . '/' . $file, $targetInternalPath . '/' . $file);
604
-					}
605
-				}
606
-			}
607
-		} else {
608
-			$source = $sourceStorage->fopen($sourceInternalPath, 'r');
609
-			// TODO: call fopen in a way that we execute again all storage wrappers
610
-			// to avoid that we bypass storage wrappers which perform important actions
611
-			// for this operation. Same is true for all other operations which
612
-			// are not the same as the original one.Once this is fixed we also
613
-			// need to adjust the encryption wrapper.
614
-			$target = $this->fopen($targetInternalPath, 'w');
615
-			list(, $result) = \OC_Helper::streamCopy($source, $target);
616
-			if ($result and $preserveMtime) {
617
-				$this->touch($targetInternalPath, $sourceStorage->filemtime($sourceInternalPath));
618
-			}
619
-			fclose($source);
620
-			fclose($target);
621
-
622
-			if (!$result) {
623
-				// delete partially written target file
624
-				$this->unlink($targetInternalPath);
625
-				// delete cache entry that was created by fopen
626
-				$this->getCache()->remove($targetInternalPath);
627
-			}
628
-		}
629
-		return (bool)$result;
630
-	}
631
-
632
-	/**
633
-	 * @param IStorage $sourceStorage
634
-	 * @param string $sourceInternalPath
635
-	 * @param string $targetInternalPath
636
-	 * @return bool
637
-	 */
638
-	public function moveFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) {
639
-		if ($sourceStorage === $this) {
640
-			return $this->rename($sourceInternalPath, $targetInternalPath);
641
-		}
642
-
643
-		if (!$sourceStorage->isDeletable($sourceInternalPath)) {
644
-			return false;
645
-		}
646
-
647
-		$result = $this->copyFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath, true);
648
-		if ($result) {
649
-			if ($sourceStorage->is_dir($sourceInternalPath)) {
650
-				$result &= $sourceStorage->rmdir($sourceInternalPath);
651
-			} else {
652
-				$result &= $sourceStorage->unlink($sourceInternalPath);
653
-			}
654
-		}
655
-		return $result;
656
-	}
657
-
658
-	/**
659
-	 * @inheritdoc
660
-	 */
661
-	public function getMetaData($path) {
662
-		$permissions = $this->getPermissions($path);
663
-		if (!$permissions & \OCP\Constants::PERMISSION_READ) {
664
-			//can't read, nothing we can do
665
-			return null;
666
-		}
667
-
668
-		$data = [];
669
-		$data['mimetype'] = $this->getMimeType($path);
670
-		$data['mtime'] = $this->filemtime($path);
671
-		if ($data['mtime'] === false) {
672
-			$data['mtime'] = time();
673
-		}
674
-		if ($data['mimetype'] == 'httpd/unix-directory') {
675
-			$data['size'] = -1; //unknown
676
-		} else {
677
-			$data['size'] = $this->filesize($path);
678
-		}
679
-		$data['etag'] = $this->getETag($path);
680
-		$data['storage_mtime'] = $data['mtime'];
681
-		$data['permissions'] = $permissions;
682
-
683
-		return $data;
684
-	}
685
-
686
-	/**
687
-	 * @param string $path
688
-	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
689
-	 * @param \OCP\Lock\ILockingProvider $provider
690
-	 * @throws \OCP\Lock\LockedException
691
-	 */
692
-	public function acquireLock($path, $type, ILockingProvider $provider) {
693
-		$logger = $this->getLockLogger();
694
-		if ($logger) {
695
-			$typeString = ($type === ILockingProvider::LOCK_SHARED) ? 'shared' : 'exclusive';
696
-			$logger->info(
697
-				sprintf(
698
-					'acquire %s lock on "%s" on storage "%s"',
699
-					$typeString,
700
-					$path,
701
-					$this->getId()
702
-				),
703
-				[
704
-					'app' => 'locking',
705
-				]
706
-			);
707
-		}
708
-		try {
709
-			$provider->acquireLock('files/' . md5($this->getId() . '::' . trim($path, '/')), $type);
710
-		} catch (LockedException $e) {
711
-			if ($logger) {
712
-				$logger->logException($e);
713
-			}
714
-			throw $e;
715
-		}
716
-	}
717
-
718
-	/**
719
-	 * @param string $path
720
-	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
721
-	 * @param \OCP\Lock\ILockingProvider $provider
722
-	 * @throws \OCP\Lock\LockedException
723
-	 */
724
-	public function releaseLock($path, $type, ILockingProvider $provider) {
725
-		$logger = $this->getLockLogger();
726
-		if ($logger) {
727
-			$typeString = ($type === ILockingProvider::LOCK_SHARED) ? 'shared' : 'exclusive';
728
-			$logger->info(
729
-				sprintf(
730
-					'release %s lock on "%s" on storage "%s"',
731
-					$typeString,
732
-					$path,
733
-					$this->getId()
734
-				),
735
-				[
736
-					'app' => 'locking',
737
-				]
738
-			);
739
-		}
740
-		try {
741
-			$provider->releaseLock('files/' . md5($this->getId() . '::' . trim($path, '/')), $type);
742
-		} catch (LockedException $e) {
743
-			if ($logger) {
744
-				$logger->logException($e);
745
-			}
746
-			throw $e;
747
-		}
748
-	}
749
-
750
-	/**
751
-	 * @param string $path
752
-	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
753
-	 * @param \OCP\Lock\ILockingProvider $provider
754
-	 * @throws \OCP\Lock\LockedException
755
-	 */
756
-	public function changeLock($path, $type, ILockingProvider $provider) {
757
-		$logger = $this->getLockLogger();
758
-		if ($logger) {
759
-			$typeString = ($type === ILockingProvider::LOCK_SHARED) ? 'shared' : 'exclusive';
760
-			$logger->info(
761
-				sprintf(
762
-					'change lock on "%s" to %s on storage "%s"',
763
-					$path,
764
-					$typeString,
765
-					$this->getId()
766
-				),
767
-				[
768
-					'app' => 'locking',
769
-				]
770
-			);
771
-		}
772
-		try {
773
-			$provider->changeLock('files/' . md5($this->getId() . '::' . trim($path, '/')), $type);
774
-		} catch (LockedException $e) {
775
-			if ($logger) {
776
-				$logger->logException($e);
777
-			}
778
-			throw $e;
779
-		}
780
-	}
781
-
782
-	private function getLockLogger() {
783
-		if (is_null($this->shouldLogLocks)) {
784
-			$this->shouldLogLocks = \OC::$server->getConfig()->getSystemValue('filelocking.debug', false);
785
-			$this->logger = $this->shouldLogLocks ? \OC::$server->getLogger() : null;
786
-		}
787
-		return $this->logger;
788
-	}
789
-
790
-	/**
791
-	 * @return array [ available, last_checked ]
792
-	 */
793
-	public function getAvailability() {
794
-		return $this->getStorageCache()->getAvailability();
795
-	}
796
-
797
-	/**
798
-	 * @param bool $isAvailable
799
-	 */
800
-	public function setAvailability($isAvailable) {
801
-		$this->getStorageCache()->setAvailability($isAvailable);
802
-	}
803
-
804
-	/**
805
-	 * @return bool
806
-	 */
807
-	public function needsPartFile() {
808
-		return true;
809
-	}
526
+                throw new InvalidCharacterInPathException();
527
+            }
528
+        }
529
+
530
+        if (isset($fileName[255])) {
531
+            throw new FileNameTooLongException();
532
+        }
533
+
534
+        // NOTE: $path will remain unverified for now
535
+        $this->verifyPosixPath($fileName);
536
+    }
537
+
538
+    /**
539
+     * @param string $fileName
540
+     * @throws InvalidPathException
541
+     */
542
+    protected function verifyPosixPath($fileName) {
543
+        $fileName = trim($fileName);
544
+        $this->scanForInvalidCharacters($fileName, "\\/");
545
+        $reservedNames = ['*'];
546
+        if (in_array($fileName, $reservedNames)) {
547
+            throw new ReservedWordException();
548
+        }
549
+    }
550
+
551
+    /**
552
+     * @param string $fileName
553
+     * @param string $invalidChars
554
+     * @throws InvalidPathException
555
+     */
556
+    private function scanForInvalidCharacters($fileName, $invalidChars) {
557
+        foreach (str_split($invalidChars) as $char) {
558
+            if (strpos($fileName, $char) !== false) {
559
+                throw new InvalidCharacterInPathException();
560
+            }
561
+        }
562
+
563
+        $sanitizedFileName = filter_var($fileName, FILTER_UNSAFE_RAW, FILTER_FLAG_STRIP_LOW);
564
+        if ($sanitizedFileName !== $fileName) {
565
+            throw new InvalidCharacterInPathException();
566
+        }
567
+    }
568
+
569
+    /**
570
+     * @param array $options
571
+     */
572
+    public function setMountOptions(array $options) {
573
+        $this->mountOptions = $options;
574
+    }
575
+
576
+    /**
577
+     * @param string $name
578
+     * @param mixed $default
579
+     * @return mixed
580
+     */
581
+    public function getMountOption($name, $default = null) {
582
+        return isset($this->mountOptions[$name]) ? $this->mountOptions[$name] : $default;
583
+    }
584
+
585
+    /**
586
+     * @param IStorage $sourceStorage
587
+     * @param string $sourceInternalPath
588
+     * @param string $targetInternalPath
589
+     * @param bool $preserveMtime
590
+     * @return bool
591
+     */
592
+    public function copyFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath, $preserveMtime = false) {
593
+        if ($sourceStorage === $this) {
594
+            return $this->copy($sourceInternalPath, $targetInternalPath);
595
+        }
596
+
597
+        if ($sourceStorage->is_dir($sourceInternalPath)) {
598
+            $dh = $sourceStorage->opendir($sourceInternalPath);
599
+            $result = $this->mkdir($targetInternalPath);
600
+            if (is_resource($dh)) {
601
+                while ($result and ($file = readdir($dh)) !== false) {
602
+                    if (!Filesystem::isIgnoredDir($file)) {
603
+                        $result &= $this->copyFromStorage($sourceStorage, $sourceInternalPath . '/' . $file, $targetInternalPath . '/' . $file);
604
+                    }
605
+                }
606
+            }
607
+        } else {
608
+            $source = $sourceStorage->fopen($sourceInternalPath, 'r');
609
+            // TODO: call fopen in a way that we execute again all storage wrappers
610
+            // to avoid that we bypass storage wrappers which perform important actions
611
+            // for this operation. Same is true for all other operations which
612
+            // are not the same as the original one.Once this is fixed we also
613
+            // need to adjust the encryption wrapper.
614
+            $target = $this->fopen($targetInternalPath, 'w');
615
+            list(, $result) = \OC_Helper::streamCopy($source, $target);
616
+            if ($result and $preserveMtime) {
617
+                $this->touch($targetInternalPath, $sourceStorage->filemtime($sourceInternalPath));
618
+            }
619
+            fclose($source);
620
+            fclose($target);
621
+
622
+            if (!$result) {
623
+                // delete partially written target file
624
+                $this->unlink($targetInternalPath);
625
+                // delete cache entry that was created by fopen
626
+                $this->getCache()->remove($targetInternalPath);
627
+            }
628
+        }
629
+        return (bool)$result;
630
+    }
631
+
632
+    /**
633
+     * @param IStorage $sourceStorage
634
+     * @param string $sourceInternalPath
635
+     * @param string $targetInternalPath
636
+     * @return bool
637
+     */
638
+    public function moveFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) {
639
+        if ($sourceStorage === $this) {
640
+            return $this->rename($sourceInternalPath, $targetInternalPath);
641
+        }
642
+
643
+        if (!$sourceStorage->isDeletable($sourceInternalPath)) {
644
+            return false;
645
+        }
646
+
647
+        $result = $this->copyFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath, true);
648
+        if ($result) {
649
+            if ($sourceStorage->is_dir($sourceInternalPath)) {
650
+                $result &= $sourceStorage->rmdir($sourceInternalPath);
651
+            } else {
652
+                $result &= $sourceStorage->unlink($sourceInternalPath);
653
+            }
654
+        }
655
+        return $result;
656
+    }
657
+
658
+    /**
659
+     * @inheritdoc
660
+     */
661
+    public function getMetaData($path) {
662
+        $permissions = $this->getPermissions($path);
663
+        if (!$permissions & \OCP\Constants::PERMISSION_READ) {
664
+            //can't read, nothing we can do
665
+            return null;
666
+        }
667
+
668
+        $data = [];
669
+        $data['mimetype'] = $this->getMimeType($path);
670
+        $data['mtime'] = $this->filemtime($path);
671
+        if ($data['mtime'] === false) {
672
+            $data['mtime'] = time();
673
+        }
674
+        if ($data['mimetype'] == 'httpd/unix-directory') {
675
+            $data['size'] = -1; //unknown
676
+        } else {
677
+            $data['size'] = $this->filesize($path);
678
+        }
679
+        $data['etag'] = $this->getETag($path);
680
+        $data['storage_mtime'] = $data['mtime'];
681
+        $data['permissions'] = $permissions;
682
+
683
+        return $data;
684
+    }
685
+
686
+    /**
687
+     * @param string $path
688
+     * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
689
+     * @param \OCP\Lock\ILockingProvider $provider
690
+     * @throws \OCP\Lock\LockedException
691
+     */
692
+    public function acquireLock($path, $type, ILockingProvider $provider) {
693
+        $logger = $this->getLockLogger();
694
+        if ($logger) {
695
+            $typeString = ($type === ILockingProvider::LOCK_SHARED) ? 'shared' : 'exclusive';
696
+            $logger->info(
697
+                sprintf(
698
+                    'acquire %s lock on "%s" on storage "%s"',
699
+                    $typeString,
700
+                    $path,
701
+                    $this->getId()
702
+                ),
703
+                [
704
+                    'app' => 'locking',
705
+                ]
706
+            );
707
+        }
708
+        try {
709
+            $provider->acquireLock('files/' . md5($this->getId() . '::' . trim($path, '/')), $type);
710
+        } catch (LockedException $e) {
711
+            if ($logger) {
712
+                $logger->logException($e);
713
+            }
714
+            throw $e;
715
+        }
716
+    }
717
+
718
+    /**
719
+     * @param string $path
720
+     * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
721
+     * @param \OCP\Lock\ILockingProvider $provider
722
+     * @throws \OCP\Lock\LockedException
723
+     */
724
+    public function releaseLock($path, $type, ILockingProvider $provider) {
725
+        $logger = $this->getLockLogger();
726
+        if ($logger) {
727
+            $typeString = ($type === ILockingProvider::LOCK_SHARED) ? 'shared' : 'exclusive';
728
+            $logger->info(
729
+                sprintf(
730
+                    'release %s lock on "%s" on storage "%s"',
731
+                    $typeString,
732
+                    $path,
733
+                    $this->getId()
734
+                ),
735
+                [
736
+                    'app' => 'locking',
737
+                ]
738
+            );
739
+        }
740
+        try {
741
+            $provider->releaseLock('files/' . md5($this->getId() . '::' . trim($path, '/')), $type);
742
+        } catch (LockedException $e) {
743
+            if ($logger) {
744
+                $logger->logException($e);
745
+            }
746
+            throw $e;
747
+        }
748
+    }
749
+
750
+    /**
751
+     * @param string $path
752
+     * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
753
+     * @param \OCP\Lock\ILockingProvider $provider
754
+     * @throws \OCP\Lock\LockedException
755
+     */
756
+    public function changeLock($path, $type, ILockingProvider $provider) {
757
+        $logger = $this->getLockLogger();
758
+        if ($logger) {
759
+            $typeString = ($type === ILockingProvider::LOCK_SHARED) ? 'shared' : 'exclusive';
760
+            $logger->info(
761
+                sprintf(
762
+                    'change lock on "%s" to %s on storage "%s"',
763
+                    $path,
764
+                    $typeString,
765
+                    $this->getId()
766
+                ),
767
+                [
768
+                    'app' => 'locking',
769
+                ]
770
+            );
771
+        }
772
+        try {
773
+            $provider->changeLock('files/' . md5($this->getId() . '::' . trim($path, '/')), $type);
774
+        } catch (LockedException $e) {
775
+            if ($logger) {
776
+                $logger->logException($e);
777
+            }
778
+            throw $e;
779
+        }
780
+    }
781
+
782
+    private function getLockLogger() {
783
+        if (is_null($this->shouldLogLocks)) {
784
+            $this->shouldLogLocks = \OC::$server->getConfig()->getSystemValue('filelocking.debug', false);
785
+            $this->logger = $this->shouldLogLocks ? \OC::$server->getLogger() : null;
786
+        }
787
+        return $this->logger;
788
+    }
789
+
790
+    /**
791
+     * @return array [ available, last_checked ]
792
+     */
793
+    public function getAvailability() {
794
+        return $this->getStorageCache()->getAvailability();
795
+    }
796
+
797
+    /**
798
+     * @param bool $isAvailable
799
+     */
800
+    public function setAvailability($isAvailable) {
801
+        $this->getStorageCache()->setAvailability($isAvailable);
802
+    }
803
+
804
+    /**
805
+     * @return bool
806
+     */
807
+    public function needsPartFile() {
808
+        return true;
809
+    }
810 810
 }
Please login to merge, or discard this patch.