Passed
Push — master ( 7ba632...8cab1d )
by Julius
27:04 queued 12:29
created
lib/private/Files/Storage/Common.php 2 patches
Indentation   +826 added lines, -826 removed lines patch added patch discarded remove patch
@@ -77,834 +77,834 @@
 block discarded – undo
77 77
  * in classes which extend it, e.g. $this->stat() .
78 78
  */
79 79
 abstract class Common implements Storage, ILockingStorage, IWriteStreamStorage {
80
-	use LocalTempFileTrait;
81
-
82
-	protected $cache;
83
-	protected $scanner;
84
-	protected $watcher;
85
-	protected $propagator;
86
-	protected $storageCache;
87
-	protected $updater;
88
-
89
-	protected $mountOptions = [];
90
-	protected $owner = null;
91
-
92
-	/** @var ?bool */
93
-	private $shouldLogLocks = null;
94
-	/** @var ?LoggerInterface */
95
-	private $logger;
96
-
97
-	public function __construct($parameters) {
98
-	}
99
-
100
-	/**
101
-	 * Remove a file or folder
102
-	 *
103
-	 * @param string $path
104
-	 * @return bool
105
-	 */
106
-	protected function remove($path) {
107
-		if ($this->is_dir($path)) {
108
-			return $this->rmdir($path);
109
-		} elseif ($this->is_file($path)) {
110
-			return $this->unlink($path);
111
-		} else {
112
-			return false;
113
-		}
114
-	}
115
-
116
-	public function is_dir($path) {
117
-		return $this->filetype($path) === 'dir';
118
-	}
119
-
120
-	public function is_file($path) {
121
-		return $this->filetype($path) === 'file';
122
-	}
123
-
124
-	public function filesize($path): false|int|float {
125
-		if ($this->is_dir($path)) {
126
-			return 0; //by definition
127
-		} else {
128
-			$stat = $this->stat($path);
129
-			if (isset($stat['size'])) {
130
-				return $stat['size'];
131
-			} else {
132
-				return 0;
133
-			}
134
-		}
135
-	}
136
-
137
-	public function isReadable($path) {
138
-		// at least check whether it exists
139
-		// subclasses might want to implement this more thoroughly
140
-		return $this->file_exists($path);
141
-	}
142
-
143
-	public function isUpdatable($path) {
144
-		// at least check whether it exists
145
-		// subclasses might want to implement this more thoroughly
146
-		// a non-existing file/folder isn't updatable
147
-		return $this->file_exists($path);
148
-	}
149
-
150
-	public function isCreatable($path) {
151
-		if ($this->is_dir($path) && $this->isUpdatable($path)) {
152
-			return true;
153
-		}
154
-		return false;
155
-	}
156
-
157
-	public function isDeletable($path) {
158
-		if ($path === '' || $path === '/') {
159
-			return $this->isUpdatable($path);
160
-		}
161
-		$parent = dirname($path);
162
-		return $this->isUpdatable($parent) && $this->isUpdatable($path);
163
-	}
164
-
165
-	public function isSharable($path) {
166
-		return $this->isReadable($path);
167
-	}
168
-
169
-	public function getPermissions($path) {
170
-		$permissions = 0;
171
-		if ($this->isCreatable($path)) {
172
-			$permissions |= \OCP\Constants::PERMISSION_CREATE;
173
-		}
174
-		if ($this->isReadable($path)) {
175
-			$permissions |= \OCP\Constants::PERMISSION_READ;
176
-		}
177
-		if ($this->isUpdatable($path)) {
178
-			$permissions |= \OCP\Constants::PERMISSION_UPDATE;
179
-		}
180
-		if ($this->isDeletable($path)) {
181
-			$permissions |= \OCP\Constants::PERMISSION_DELETE;
182
-		}
183
-		if ($this->isSharable($path)) {
184
-			$permissions |= \OCP\Constants::PERMISSION_SHARE;
185
-		}
186
-		return $permissions;
187
-	}
188
-
189
-	public function filemtime($path) {
190
-		$stat = $this->stat($path);
191
-		if (isset($stat['mtime']) && $stat['mtime'] > 0) {
192
-			return $stat['mtime'];
193
-		} else {
194
-			return 0;
195
-		}
196
-	}
197
-
198
-	public function file_get_contents($path) {
199
-		$handle = $this->fopen($path, "r");
200
-		if (!$handle) {
201
-			return false;
202
-		}
203
-		$data = stream_get_contents($handle);
204
-		fclose($handle);
205
-		return $data;
206
-	}
207
-
208
-	public function file_put_contents($path, $data) {
209
-		$handle = $this->fopen($path, "w");
210
-		if (!$handle) {
211
-			return false;
212
-		}
213
-		$this->removeCachedFile($path);
214
-		$count = fwrite($handle, $data);
215
-		fclose($handle);
216
-		return $count;
217
-	}
218
-
219
-	public function rename($source, $target) {
220
-		$this->remove($target);
221
-
222
-		$this->removeCachedFile($source);
223
-		return $this->copy($source, $target) and $this->remove($source);
224
-	}
225
-
226
-	public function copy($source, $target) {
227
-		if ($this->is_dir($source)) {
228
-			$this->remove($target);
229
-			$dir = $this->opendir($source);
230
-			$this->mkdir($target);
231
-			while ($file = readdir($dir)) {
232
-				if (!Filesystem::isIgnoredDir($file)) {
233
-					if (!$this->copy($source . '/' . $file, $target . '/' . $file)) {
234
-						closedir($dir);
235
-						return false;
236
-					}
237
-				}
238
-			}
239
-			closedir($dir);
240
-			return true;
241
-		} else {
242
-			$sourceStream = $this->fopen($source, 'r');
243
-			$targetStream = $this->fopen($target, 'w');
244
-			[, $result] = \OC_Helper::streamCopy($sourceStream, $targetStream);
245
-			if (!$result) {
246
-				\OCP\Server::get(LoggerInterface::class)->warning("Failed to write data while copying $source to $target");
247
-			}
248
-			$this->removeCachedFile($target);
249
-			return $result;
250
-		}
251
-	}
252
-
253
-	public function getMimeType($path) {
254
-		if ($this->is_dir($path)) {
255
-			return 'httpd/unix-directory';
256
-		} elseif ($this->file_exists($path)) {
257
-			return \OC::$server->getMimeTypeDetector()->detectPath($path);
258
-		} else {
259
-			return false;
260
-		}
261
-	}
262
-
263
-	public function hash($type, $path, $raw = false) {
264
-		$fh = $this->fopen($path, 'rb');
265
-		$ctx = hash_init($type);
266
-		hash_update_stream($ctx, $fh);
267
-		fclose($fh);
268
-		return hash_final($ctx, $raw);
269
-	}
270
-
271
-	public function search($query) {
272
-		return $this->searchInDir($query);
273
-	}
274
-
275
-	public function getLocalFile($path) {
276
-		return $this->getCachedFile($path);
277
-	}
278
-
279
-	/**
280
-	 * @param string $path
281
-	 * @param string $target
282
-	 */
283
-	private function addLocalFolder($path, $target) {
284
-		$dh = $this->opendir($path);
285
-		if (is_resource($dh)) {
286
-			while (($file = readdir($dh)) !== false) {
287
-				if (!\OC\Files\Filesystem::isIgnoredDir($file)) {
288
-					if ($this->is_dir($path . '/' . $file)) {
289
-						mkdir($target . '/' . $file);
290
-						$this->addLocalFolder($path . '/' . $file, $target . '/' . $file);
291
-					} else {
292
-						$tmp = $this->toTmpFile($path . '/' . $file);
293
-						rename($tmp, $target . '/' . $file);
294
-					}
295
-				}
296
-			}
297
-		}
298
-	}
299
-
300
-	/**
301
-	 * @param string $query
302
-	 * @param string $dir
303
-	 * @return array
304
-	 */
305
-	protected function searchInDir($query, $dir = '') {
306
-		$files = [];
307
-		$dh = $this->opendir($dir);
308
-		if (is_resource($dh)) {
309
-			while (($item = readdir($dh)) !== false) {
310
-				if (\OC\Files\Filesystem::isIgnoredDir($item)) {
311
-					continue;
312
-				}
313
-				if (strstr(strtolower($item), strtolower($query)) !== false) {
314
-					$files[] = $dir . '/' . $item;
315
-				}
316
-				if ($this->is_dir($dir . '/' . $item)) {
317
-					$files = array_merge($files, $this->searchInDir($query, $dir . '/' . $item));
318
-				}
319
-			}
320
-		}
321
-		closedir($dh);
322
-		return $files;
323
-	}
324
-
325
-	/**
326
-	 * Check if a file or folder has been updated since $time
327
-	 *
328
-	 * The method is only used to check if the cache needs to be updated. Storage backends that don't support checking
329
-	 * the mtime should always return false here. As a result storage implementations that always return false expect
330
-	 * exclusive access to the backend and will not pick up files that have been added in a way that circumvents
331
-	 * Nextcloud filesystem.
332
-	 *
333
-	 * @param string $path
334
-	 * @param int $time
335
-	 * @return bool
336
-	 */
337
-	public function hasUpdated($path, $time) {
338
-		return $this->filemtime($path) > $time;
339
-	}
340
-
341
-	public function getCache($path = '', $storage = null) {
342
-		if (!$storage) {
343
-			$storage = $this;
344
-		}
345
-		if (!isset($storage->cache)) {
346
-			$storage->cache = new Cache($storage);
347
-		}
348
-		return $storage->cache;
349
-	}
350
-
351
-	public function getScanner($path = '', $storage = null) {
352
-		if (!$storage) {
353
-			$storage = $this;
354
-		}
355
-		if (!isset($storage->scanner)) {
356
-			$storage->scanner = new Scanner($storage);
357
-		}
358
-		return $storage->scanner;
359
-	}
360
-
361
-	public function getWatcher($path = '', $storage = null) {
362
-		if (!$storage) {
363
-			$storage = $this;
364
-		}
365
-		if (!isset($this->watcher)) {
366
-			$this->watcher = new Watcher($storage);
367
-			$globalPolicy = \OC::$server->getConfig()->getSystemValue('filesystem_check_changes', Watcher::CHECK_NEVER);
368
-			$this->watcher->setPolicy((int)$this->getMountOption('filesystem_check_changes', $globalPolicy));
369
-		}
370
-		return $this->watcher;
371
-	}
372
-
373
-	/**
374
-	 * get a propagator instance for the cache
375
-	 *
376
-	 * @param \OC\Files\Storage\Storage (optional) the storage to pass to the watcher
377
-	 * @return \OC\Files\Cache\Propagator
378
-	 */
379
-	public function getPropagator($storage = null) {
380
-		if (!$storage) {
381
-			$storage = $this;
382
-		}
383
-		if (!isset($storage->propagator)) {
384
-			$config = \OC::$server->getSystemConfig();
385
-			$storage->propagator = new Propagator($storage, \OC::$server->getDatabaseConnection(), ['appdata_' . $config->getValue('instanceid')]);
386
-		}
387
-		return $storage->propagator;
388
-	}
389
-
390
-	public function getUpdater($storage = null) {
391
-		if (!$storage) {
392
-			$storage = $this;
393
-		}
394
-		if (!isset($storage->updater)) {
395
-			$storage->updater = new Updater($storage);
396
-		}
397
-		return $storage->updater;
398
-	}
399
-
400
-	public function getStorageCache($storage = null) {
401
-		if (!$storage) {
402
-			$storage = $this;
403
-		}
404
-		if (!isset($this->storageCache)) {
405
-			$this->storageCache = new \OC\Files\Cache\Storage($storage);
406
-		}
407
-		return $this->storageCache;
408
-	}
409
-
410
-	/**
411
-	 * get the owner of a path
412
-	 *
413
-	 * @param string $path The path to get the owner
414
-	 * @return string|false uid or false
415
-	 */
416
-	public function getOwner($path) {
417
-		if ($this->owner === null) {
418
-			$this->owner = \OC_User::getUser();
419
-		}
420
-
421
-		return $this->owner;
422
-	}
423
-
424
-	/**
425
-	 * get the ETag for a file or folder
426
-	 *
427
-	 * @param string $path
428
-	 * @return string
429
-	 */
430
-	public function getETag($path) {
431
-		return uniqid();
432
-	}
433
-
434
-	/**
435
-	 * clean a path, i.e. remove all redundant '.' and '..'
436
-	 * making sure that it can't point to higher than '/'
437
-	 *
438
-	 * @param string $path The path to clean
439
-	 * @return string cleaned path
440
-	 */
441
-	public function cleanPath($path) {
442
-		if (strlen($path) == 0 or $path[0] != '/') {
443
-			$path = '/' . $path;
444
-		}
445
-
446
-		$output = [];
447
-		foreach (explode('/', $path) as $chunk) {
448
-			if ($chunk == '..') {
449
-				array_pop($output);
450
-			} elseif ($chunk == '.') {
451
-			} else {
452
-				$output[] = $chunk;
453
-			}
454
-		}
455
-		return implode('/', $output);
456
-	}
457
-
458
-	/**
459
-	 * Test a storage for availability
460
-	 *
461
-	 * @return bool
462
-	 */
463
-	public function test() {
464
-		try {
465
-			if ($this->stat('')) {
466
-				return true;
467
-			}
468
-			\OC::$server->get(LoggerInterface::class)->info("External storage not available: stat() failed");
469
-			return false;
470
-		} catch (\Exception $e) {
471
-			\OC::$server->get(LoggerInterface::class)->warning(
472
-				"External storage not available: " . $e->getMessage(),
473
-				['exception' => $e]
474
-			);
475
-			return false;
476
-		}
477
-	}
478
-
479
-	/**
480
-	 * get the free space in the storage
481
-	 *
482
-	 * @param string $path
483
-	 * @return int|float|false
484
-	 */
485
-	public function free_space($path) {
486
-		return \OCP\Files\FileInfo::SPACE_UNKNOWN;
487
-	}
488
-
489
-	/**
490
-	 * {@inheritdoc}
491
-	 */
492
-	public function isLocal() {
493
-		// the common implementation returns a temporary file by
494
-		// default, which is not local
495
-		return false;
496
-	}
497
-
498
-	/**
499
-	 * Check if the storage is an instance of $class or is a wrapper for a storage that is an instance of $class
500
-	 *
501
-	 * @param string $class
502
-	 * @return bool
503
-	 */
504
-	public function instanceOfStorage($class) {
505
-		if (ltrim($class, '\\') === 'OC\Files\Storage\Shared') {
506
-			// FIXME Temporary fix to keep existing checks working
507
-			$class = '\OCA\Files_Sharing\SharedStorage';
508
-		}
509
-		return is_a($this, $class);
510
-	}
511
-
512
-	/**
513
-	 * A custom storage implementation can return an url for direct download of a give file.
514
-	 *
515
-	 * For now the returned array can hold the parameter url - in future more attributes might follow.
516
-	 *
517
-	 * @param string $path
518
-	 * @return array|false
519
-	 */
520
-	public function getDirectDownload($path) {
521
-		return [];
522
-	}
523
-
524
-	/**
525
-	 * @inheritdoc
526
-	 * @throws InvalidPathException
527
-	 */
528
-	public function verifyPath($path, $fileName) {
529
-		// verify empty and dot files
530
-		$trimmed = trim($fileName);
531
-		if ($trimmed === '') {
532
-			throw new EmptyFileNameException();
533
-		}
534
-
535
-		if (\OC\Files\Filesystem::isIgnoredDir($trimmed)) {
536
-			throw new InvalidDirectoryException();
537
-		}
538
-
539
-		if (!\OC::$server->getDatabaseConnection()->supports4ByteText()) {
540
-			// verify database - e.g. mysql only 3-byte chars
541
-			if (preg_match('%(?:
80
+    use LocalTempFileTrait;
81
+
82
+    protected $cache;
83
+    protected $scanner;
84
+    protected $watcher;
85
+    protected $propagator;
86
+    protected $storageCache;
87
+    protected $updater;
88
+
89
+    protected $mountOptions = [];
90
+    protected $owner = null;
91
+
92
+    /** @var ?bool */
93
+    private $shouldLogLocks = null;
94
+    /** @var ?LoggerInterface */
95
+    private $logger;
96
+
97
+    public function __construct($parameters) {
98
+    }
99
+
100
+    /**
101
+     * Remove a file or folder
102
+     *
103
+     * @param string $path
104
+     * @return bool
105
+     */
106
+    protected function remove($path) {
107
+        if ($this->is_dir($path)) {
108
+            return $this->rmdir($path);
109
+        } elseif ($this->is_file($path)) {
110
+            return $this->unlink($path);
111
+        } else {
112
+            return false;
113
+        }
114
+    }
115
+
116
+    public function is_dir($path) {
117
+        return $this->filetype($path) === 'dir';
118
+    }
119
+
120
+    public function is_file($path) {
121
+        return $this->filetype($path) === 'file';
122
+    }
123
+
124
+    public function filesize($path): false|int|float {
125
+        if ($this->is_dir($path)) {
126
+            return 0; //by definition
127
+        } else {
128
+            $stat = $this->stat($path);
129
+            if (isset($stat['size'])) {
130
+                return $stat['size'];
131
+            } else {
132
+                return 0;
133
+            }
134
+        }
135
+    }
136
+
137
+    public function isReadable($path) {
138
+        // at least check whether it exists
139
+        // subclasses might want to implement this more thoroughly
140
+        return $this->file_exists($path);
141
+    }
142
+
143
+    public function isUpdatable($path) {
144
+        // at least check whether it exists
145
+        // subclasses might want to implement this more thoroughly
146
+        // a non-existing file/folder isn't updatable
147
+        return $this->file_exists($path);
148
+    }
149
+
150
+    public function isCreatable($path) {
151
+        if ($this->is_dir($path) && $this->isUpdatable($path)) {
152
+            return true;
153
+        }
154
+        return false;
155
+    }
156
+
157
+    public function isDeletable($path) {
158
+        if ($path === '' || $path === '/') {
159
+            return $this->isUpdatable($path);
160
+        }
161
+        $parent = dirname($path);
162
+        return $this->isUpdatable($parent) && $this->isUpdatable($path);
163
+    }
164
+
165
+    public function isSharable($path) {
166
+        return $this->isReadable($path);
167
+    }
168
+
169
+    public function getPermissions($path) {
170
+        $permissions = 0;
171
+        if ($this->isCreatable($path)) {
172
+            $permissions |= \OCP\Constants::PERMISSION_CREATE;
173
+        }
174
+        if ($this->isReadable($path)) {
175
+            $permissions |= \OCP\Constants::PERMISSION_READ;
176
+        }
177
+        if ($this->isUpdatable($path)) {
178
+            $permissions |= \OCP\Constants::PERMISSION_UPDATE;
179
+        }
180
+        if ($this->isDeletable($path)) {
181
+            $permissions |= \OCP\Constants::PERMISSION_DELETE;
182
+        }
183
+        if ($this->isSharable($path)) {
184
+            $permissions |= \OCP\Constants::PERMISSION_SHARE;
185
+        }
186
+        return $permissions;
187
+    }
188
+
189
+    public function filemtime($path) {
190
+        $stat = $this->stat($path);
191
+        if (isset($stat['mtime']) && $stat['mtime'] > 0) {
192
+            return $stat['mtime'];
193
+        } else {
194
+            return 0;
195
+        }
196
+    }
197
+
198
+    public function file_get_contents($path) {
199
+        $handle = $this->fopen($path, "r");
200
+        if (!$handle) {
201
+            return false;
202
+        }
203
+        $data = stream_get_contents($handle);
204
+        fclose($handle);
205
+        return $data;
206
+    }
207
+
208
+    public function file_put_contents($path, $data) {
209
+        $handle = $this->fopen($path, "w");
210
+        if (!$handle) {
211
+            return false;
212
+        }
213
+        $this->removeCachedFile($path);
214
+        $count = fwrite($handle, $data);
215
+        fclose($handle);
216
+        return $count;
217
+    }
218
+
219
+    public function rename($source, $target) {
220
+        $this->remove($target);
221
+
222
+        $this->removeCachedFile($source);
223
+        return $this->copy($source, $target) and $this->remove($source);
224
+    }
225
+
226
+    public function copy($source, $target) {
227
+        if ($this->is_dir($source)) {
228
+            $this->remove($target);
229
+            $dir = $this->opendir($source);
230
+            $this->mkdir($target);
231
+            while ($file = readdir($dir)) {
232
+                if (!Filesystem::isIgnoredDir($file)) {
233
+                    if (!$this->copy($source . '/' . $file, $target . '/' . $file)) {
234
+                        closedir($dir);
235
+                        return false;
236
+                    }
237
+                }
238
+            }
239
+            closedir($dir);
240
+            return true;
241
+        } else {
242
+            $sourceStream = $this->fopen($source, 'r');
243
+            $targetStream = $this->fopen($target, 'w');
244
+            [, $result] = \OC_Helper::streamCopy($sourceStream, $targetStream);
245
+            if (!$result) {
246
+                \OCP\Server::get(LoggerInterface::class)->warning("Failed to write data while copying $source to $target");
247
+            }
248
+            $this->removeCachedFile($target);
249
+            return $result;
250
+        }
251
+    }
252
+
253
+    public function getMimeType($path) {
254
+        if ($this->is_dir($path)) {
255
+            return 'httpd/unix-directory';
256
+        } elseif ($this->file_exists($path)) {
257
+            return \OC::$server->getMimeTypeDetector()->detectPath($path);
258
+        } else {
259
+            return false;
260
+        }
261
+    }
262
+
263
+    public function hash($type, $path, $raw = false) {
264
+        $fh = $this->fopen($path, 'rb');
265
+        $ctx = hash_init($type);
266
+        hash_update_stream($ctx, $fh);
267
+        fclose($fh);
268
+        return hash_final($ctx, $raw);
269
+    }
270
+
271
+    public function search($query) {
272
+        return $this->searchInDir($query);
273
+    }
274
+
275
+    public function getLocalFile($path) {
276
+        return $this->getCachedFile($path);
277
+    }
278
+
279
+    /**
280
+     * @param string $path
281
+     * @param string $target
282
+     */
283
+    private function addLocalFolder($path, $target) {
284
+        $dh = $this->opendir($path);
285
+        if (is_resource($dh)) {
286
+            while (($file = readdir($dh)) !== false) {
287
+                if (!\OC\Files\Filesystem::isIgnoredDir($file)) {
288
+                    if ($this->is_dir($path . '/' . $file)) {
289
+                        mkdir($target . '/' . $file);
290
+                        $this->addLocalFolder($path . '/' . $file, $target . '/' . $file);
291
+                    } else {
292
+                        $tmp = $this->toTmpFile($path . '/' . $file);
293
+                        rename($tmp, $target . '/' . $file);
294
+                    }
295
+                }
296
+            }
297
+        }
298
+    }
299
+
300
+    /**
301
+     * @param string $query
302
+     * @param string $dir
303
+     * @return array
304
+     */
305
+    protected function searchInDir($query, $dir = '') {
306
+        $files = [];
307
+        $dh = $this->opendir($dir);
308
+        if (is_resource($dh)) {
309
+            while (($item = readdir($dh)) !== false) {
310
+                if (\OC\Files\Filesystem::isIgnoredDir($item)) {
311
+                    continue;
312
+                }
313
+                if (strstr(strtolower($item), strtolower($query)) !== false) {
314
+                    $files[] = $dir . '/' . $item;
315
+                }
316
+                if ($this->is_dir($dir . '/' . $item)) {
317
+                    $files = array_merge($files, $this->searchInDir($query, $dir . '/' . $item));
318
+                }
319
+            }
320
+        }
321
+        closedir($dh);
322
+        return $files;
323
+    }
324
+
325
+    /**
326
+     * Check if a file or folder has been updated since $time
327
+     *
328
+     * The method is only used to check if the cache needs to be updated. Storage backends that don't support checking
329
+     * the mtime should always return false here. As a result storage implementations that always return false expect
330
+     * exclusive access to the backend and will not pick up files that have been added in a way that circumvents
331
+     * Nextcloud filesystem.
332
+     *
333
+     * @param string $path
334
+     * @param int $time
335
+     * @return bool
336
+     */
337
+    public function hasUpdated($path, $time) {
338
+        return $this->filemtime($path) > $time;
339
+    }
340
+
341
+    public function getCache($path = '', $storage = null) {
342
+        if (!$storage) {
343
+            $storage = $this;
344
+        }
345
+        if (!isset($storage->cache)) {
346
+            $storage->cache = new Cache($storage);
347
+        }
348
+        return $storage->cache;
349
+    }
350
+
351
+    public function getScanner($path = '', $storage = null) {
352
+        if (!$storage) {
353
+            $storage = $this;
354
+        }
355
+        if (!isset($storage->scanner)) {
356
+            $storage->scanner = new Scanner($storage);
357
+        }
358
+        return $storage->scanner;
359
+    }
360
+
361
+    public function getWatcher($path = '', $storage = null) {
362
+        if (!$storage) {
363
+            $storage = $this;
364
+        }
365
+        if (!isset($this->watcher)) {
366
+            $this->watcher = new Watcher($storage);
367
+            $globalPolicy = \OC::$server->getConfig()->getSystemValue('filesystem_check_changes', Watcher::CHECK_NEVER);
368
+            $this->watcher->setPolicy((int)$this->getMountOption('filesystem_check_changes', $globalPolicy));
369
+        }
370
+        return $this->watcher;
371
+    }
372
+
373
+    /**
374
+     * get a propagator instance for the cache
375
+     *
376
+     * @param \OC\Files\Storage\Storage (optional) the storage to pass to the watcher
377
+     * @return \OC\Files\Cache\Propagator
378
+     */
379
+    public function getPropagator($storage = null) {
380
+        if (!$storage) {
381
+            $storage = $this;
382
+        }
383
+        if (!isset($storage->propagator)) {
384
+            $config = \OC::$server->getSystemConfig();
385
+            $storage->propagator = new Propagator($storage, \OC::$server->getDatabaseConnection(), ['appdata_' . $config->getValue('instanceid')]);
386
+        }
387
+        return $storage->propagator;
388
+    }
389
+
390
+    public function getUpdater($storage = null) {
391
+        if (!$storage) {
392
+            $storage = $this;
393
+        }
394
+        if (!isset($storage->updater)) {
395
+            $storage->updater = new Updater($storage);
396
+        }
397
+        return $storage->updater;
398
+    }
399
+
400
+    public function getStorageCache($storage = null) {
401
+        if (!$storage) {
402
+            $storage = $this;
403
+        }
404
+        if (!isset($this->storageCache)) {
405
+            $this->storageCache = new \OC\Files\Cache\Storage($storage);
406
+        }
407
+        return $this->storageCache;
408
+    }
409
+
410
+    /**
411
+     * get the owner of a path
412
+     *
413
+     * @param string $path The path to get the owner
414
+     * @return string|false uid or false
415
+     */
416
+    public function getOwner($path) {
417
+        if ($this->owner === null) {
418
+            $this->owner = \OC_User::getUser();
419
+        }
420
+
421
+        return $this->owner;
422
+    }
423
+
424
+    /**
425
+     * get the ETag for a file or folder
426
+     *
427
+     * @param string $path
428
+     * @return string
429
+     */
430
+    public function getETag($path) {
431
+        return uniqid();
432
+    }
433
+
434
+    /**
435
+     * clean a path, i.e. remove all redundant '.' and '..'
436
+     * making sure that it can't point to higher than '/'
437
+     *
438
+     * @param string $path The path to clean
439
+     * @return string cleaned path
440
+     */
441
+    public function cleanPath($path) {
442
+        if (strlen($path) == 0 or $path[0] != '/') {
443
+            $path = '/' . $path;
444
+        }
445
+
446
+        $output = [];
447
+        foreach (explode('/', $path) as $chunk) {
448
+            if ($chunk == '..') {
449
+                array_pop($output);
450
+            } elseif ($chunk == '.') {
451
+            } else {
452
+                $output[] = $chunk;
453
+            }
454
+        }
455
+        return implode('/', $output);
456
+    }
457
+
458
+    /**
459
+     * Test a storage for availability
460
+     *
461
+     * @return bool
462
+     */
463
+    public function test() {
464
+        try {
465
+            if ($this->stat('')) {
466
+                return true;
467
+            }
468
+            \OC::$server->get(LoggerInterface::class)->info("External storage not available: stat() failed");
469
+            return false;
470
+        } catch (\Exception $e) {
471
+            \OC::$server->get(LoggerInterface::class)->warning(
472
+                "External storage not available: " . $e->getMessage(),
473
+                ['exception' => $e]
474
+            );
475
+            return false;
476
+        }
477
+    }
478
+
479
+    /**
480
+     * get the free space in the storage
481
+     *
482
+     * @param string $path
483
+     * @return int|float|false
484
+     */
485
+    public function free_space($path) {
486
+        return \OCP\Files\FileInfo::SPACE_UNKNOWN;
487
+    }
488
+
489
+    /**
490
+     * {@inheritdoc}
491
+     */
492
+    public function isLocal() {
493
+        // the common implementation returns a temporary file by
494
+        // default, which is not local
495
+        return false;
496
+    }
497
+
498
+    /**
499
+     * Check if the storage is an instance of $class or is a wrapper for a storage that is an instance of $class
500
+     *
501
+     * @param string $class
502
+     * @return bool
503
+     */
504
+    public function instanceOfStorage($class) {
505
+        if (ltrim($class, '\\') === 'OC\Files\Storage\Shared') {
506
+            // FIXME Temporary fix to keep existing checks working
507
+            $class = '\OCA\Files_Sharing\SharedStorage';
508
+        }
509
+        return is_a($this, $class);
510
+    }
511
+
512
+    /**
513
+     * A custom storage implementation can return an url for direct download of a give file.
514
+     *
515
+     * For now the returned array can hold the parameter url - in future more attributes might follow.
516
+     *
517
+     * @param string $path
518
+     * @return array|false
519
+     */
520
+    public function getDirectDownload($path) {
521
+        return [];
522
+    }
523
+
524
+    /**
525
+     * @inheritdoc
526
+     * @throws InvalidPathException
527
+     */
528
+    public function verifyPath($path, $fileName) {
529
+        // verify empty and dot files
530
+        $trimmed = trim($fileName);
531
+        if ($trimmed === '') {
532
+            throw new EmptyFileNameException();
533
+        }
534
+
535
+        if (\OC\Files\Filesystem::isIgnoredDir($trimmed)) {
536
+            throw new InvalidDirectoryException();
537
+        }
538
+
539
+        if (!\OC::$server->getDatabaseConnection()->supports4ByteText()) {
540
+            // verify database - e.g. mysql only 3-byte chars
541
+            if (preg_match('%(?:
542 542
       \xF0[\x90-\xBF][\x80-\xBF]{2}      # planes 1-3
543 543
     | [\xF1-\xF3][\x80-\xBF]{3}          # planes 4-15
544 544
     | \xF4[\x80-\x8F][\x80-\xBF]{2}      # plane 16
545 545
 )%xs', $fileName)) {
546
-				throw new InvalidCharacterInPathException();
547
-			}
548
-		}
549
-
550
-		// 255 characters is the limit on common file systems (ext/xfs)
551
-		// oc_filecache has a 250 char length limit for the filename
552
-		if (isset($fileName[250])) {
553
-			throw new FileNameTooLongException();
554
-		}
555
-
556
-		// NOTE: $path will remain unverified for now
557
-		$this->verifyPosixPath($fileName);
558
-	}
559
-
560
-	/**
561
-	 * @param string $fileName
562
-	 * @throws InvalidPathException
563
-	 */
564
-	protected function verifyPosixPath($fileName) {
565
-		$this->scanForInvalidCharacters($fileName, "\\/");
566
-		$fileName = trim($fileName);
567
-		$reservedNames = ['*'];
568
-		if (in_array($fileName, $reservedNames)) {
569
-			throw new ReservedWordException();
570
-		}
571
-	}
572
-
573
-	/**
574
-	 * @param string $fileName
575
-	 * @param string $invalidChars
576
-	 * @throws InvalidPathException
577
-	 */
578
-	private function scanForInvalidCharacters($fileName, $invalidChars) {
579
-		foreach (str_split($invalidChars) as $char) {
580
-			if (strpos($fileName, $char) !== false) {
581
-				throw new InvalidCharacterInPathException();
582
-			}
583
-		}
584
-
585
-		$sanitizedFileName = filter_var($fileName, FILTER_UNSAFE_RAW, FILTER_FLAG_STRIP_LOW);
586
-		if ($sanitizedFileName !== $fileName) {
587
-			throw new InvalidCharacterInPathException();
588
-		}
589
-	}
590
-
591
-	/**
592
-	 * @param array $options
593
-	 */
594
-	public function setMountOptions(array $options) {
595
-		$this->mountOptions = $options;
596
-	}
597
-
598
-	/**
599
-	 * @param string $name
600
-	 * @param mixed $default
601
-	 * @return mixed
602
-	 */
603
-	public function getMountOption($name, $default = null) {
604
-		return isset($this->mountOptions[$name]) ? $this->mountOptions[$name] : $default;
605
-	}
606
-
607
-	/**
608
-	 * @param IStorage $sourceStorage
609
-	 * @param string $sourceInternalPath
610
-	 * @param string $targetInternalPath
611
-	 * @param bool $preserveMtime
612
-	 * @return bool
613
-	 */
614
-	public function copyFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath, $preserveMtime = false) {
615
-		if ($sourceStorage === $this) {
616
-			return $this->copy($sourceInternalPath, $targetInternalPath);
617
-		}
618
-
619
-		if ($sourceStorage->is_dir($sourceInternalPath)) {
620
-			$dh = $sourceStorage->opendir($sourceInternalPath);
621
-			$result = $this->mkdir($targetInternalPath);
622
-			if (is_resource($dh)) {
623
-				$result = true;
624
-				while ($result and ($file = readdir($dh)) !== false) {
625
-					if (!Filesystem::isIgnoredDir($file)) {
626
-						$result &= $this->copyFromStorage($sourceStorage, $sourceInternalPath . '/' . $file, $targetInternalPath . '/' . $file);
627
-					}
628
-				}
629
-			}
630
-		} else {
631
-			$source = $sourceStorage->fopen($sourceInternalPath, 'r');
632
-			$result = false;
633
-			if ($source) {
634
-				try {
635
-					$this->writeStream($targetInternalPath, $source);
636
-					$result = true;
637
-				} catch (\Exception $e) {
638
-					\OC::$server->get(LoggerInterface::class)->warning('Failed to copy stream to storage', ['exception' => $e]);
639
-				}
640
-			}
641
-
642
-			if ($result && $preserveMtime) {
643
-				$mtime = $sourceStorage->filemtime($sourceInternalPath);
644
-				$this->touch($targetInternalPath, is_int($mtime) ? $mtime : null);
645
-			}
646
-
647
-			if (!$result) {
648
-				// delete partially written target file
649
-				$this->unlink($targetInternalPath);
650
-				// delete cache entry that was created by fopen
651
-				$this->getCache()->remove($targetInternalPath);
652
-			}
653
-		}
654
-		return (bool)$result;
655
-	}
656
-
657
-	/**
658
-	 * Check if a storage is the same as the current one, including wrapped storages
659
-	 *
660
-	 * @param IStorage $storage
661
-	 * @return bool
662
-	 */
663
-	private function isSameStorage(IStorage $storage): bool {
664
-		while ($storage->instanceOfStorage(Wrapper::class)) {
665
-			/**
666
-			 * @var Wrapper $sourceStorage
667
-			 */
668
-			$storage = $storage->getWrapperStorage();
669
-		}
670
-
671
-		return $storage === $this;
672
-	}
673
-
674
-	/**
675
-	 * @param IStorage $sourceStorage
676
-	 * @param string $sourceInternalPath
677
-	 * @param string $targetInternalPath
678
-	 * @return bool
679
-	 */
680
-	public function moveFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) {
681
-		if ($this->isSameStorage($sourceStorage)) {
682
-			// resolve any jailed paths
683
-			while ($sourceStorage->instanceOfStorage(Jail::class)) {
684
-				/**
685
-				 * @var Jail $sourceStorage
686
-				 */
687
-				$sourceInternalPath = $sourceStorage->getUnjailedPath($sourceInternalPath);
688
-				$sourceStorage = $sourceStorage->getUnjailedStorage();
689
-			}
690
-
691
-			return $this->rename($sourceInternalPath, $targetInternalPath);
692
-		}
693
-
694
-		if (!$sourceStorage->isDeletable($sourceInternalPath)) {
695
-			return false;
696
-		}
697
-
698
-		$result = $this->copyFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath, true);
699
-		if ($result) {
700
-			if ($sourceStorage->is_dir($sourceInternalPath)) {
701
-				$result = $sourceStorage->rmdir($sourceInternalPath);
702
-			} else {
703
-				$result = $sourceStorage->unlink($sourceInternalPath);
704
-			}
705
-		}
706
-		return $result;
707
-	}
708
-
709
-	/**
710
-	 * @inheritdoc
711
-	 */
712
-	public function getMetaData($path) {
713
-		if (Filesystem::isFileBlacklisted($path)) {
714
-			throw new ForbiddenException('Invalid path: ' . $path, false);
715
-		}
716
-
717
-		$permissions = $this->getPermissions($path);
718
-		if (!$permissions & \OCP\Constants::PERMISSION_READ) {
719
-			//can't read, nothing we can do
720
-			return null;
721
-		}
722
-
723
-		$data = [];
724
-		$data['mimetype'] = $this->getMimeType($path);
725
-		$data['mtime'] = $this->filemtime($path);
726
-		if ($data['mtime'] === false) {
727
-			$data['mtime'] = time();
728
-		}
729
-		if ($data['mimetype'] == 'httpd/unix-directory') {
730
-			$data['size'] = -1; //unknown
731
-		} else {
732
-			$data['size'] = $this->filesize($path);
733
-		}
734
-		$data['etag'] = $this->getETag($path);
735
-		$data['storage_mtime'] = $data['mtime'];
736
-		$data['permissions'] = $permissions;
737
-		$data['name'] = basename($path);
738
-
739
-		return $data;
740
-	}
741
-
742
-	/**
743
-	 * @param string $path
744
-	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
745
-	 * @param \OCP\Lock\ILockingProvider $provider
746
-	 * @throws \OCP\Lock\LockedException
747
-	 */
748
-	public function acquireLock($path, $type, ILockingProvider $provider) {
749
-		$logger = $this->getLockLogger();
750
-		if ($logger) {
751
-			$typeString = ($type === ILockingProvider::LOCK_SHARED) ? 'shared' : 'exclusive';
752
-			$logger->info(
753
-				sprintf(
754
-					'acquire %s lock on "%s" on storage "%s"',
755
-					$typeString,
756
-					$path,
757
-					$this->getId()
758
-				),
759
-				[
760
-					'app' => 'locking',
761
-				]
762
-			);
763
-		}
764
-		try {
765
-			$provider->acquireLock('files/' . md5($this->getId() . '::' . trim($path, '/')), $type, $this->getId() . '::' . $path);
766
-		} catch (LockedException $e) {
767
-			$e = new LockedException($e->getPath(), $e, $e->getExistingLock(), $path);
768
-			if ($logger) {
769
-				$logger->info($e->getMessage(), ['exception' => $e]);
770
-			}
771
-			throw $e;
772
-		}
773
-	}
774
-
775
-	/**
776
-	 * @param string $path
777
-	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
778
-	 * @param \OCP\Lock\ILockingProvider $provider
779
-	 * @throws \OCP\Lock\LockedException
780
-	 */
781
-	public function releaseLock($path, $type, ILockingProvider $provider) {
782
-		$logger = $this->getLockLogger();
783
-		if ($logger) {
784
-			$typeString = ($type === ILockingProvider::LOCK_SHARED) ? 'shared' : 'exclusive';
785
-			$logger->info(
786
-				sprintf(
787
-					'release %s lock on "%s" on storage "%s"',
788
-					$typeString,
789
-					$path,
790
-					$this->getId()
791
-				),
792
-				[
793
-					'app' => 'locking',
794
-				]
795
-			);
796
-		}
797
-		try {
798
-			$provider->releaseLock('files/' . md5($this->getId() . '::' . trim($path, '/')), $type);
799
-		} catch (LockedException $e) {
800
-			$e = new LockedException($e->getPath(), $e, $e->getExistingLock(), $path);
801
-			if ($logger) {
802
-				$logger->info($e->getMessage(), ['exception' => $e]);
803
-			}
804
-			throw $e;
805
-		}
806
-	}
807
-
808
-	/**
809
-	 * @param string $path
810
-	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
811
-	 * @param \OCP\Lock\ILockingProvider $provider
812
-	 * @throws \OCP\Lock\LockedException
813
-	 */
814
-	public function changeLock($path, $type, ILockingProvider $provider) {
815
-		$logger = $this->getLockLogger();
816
-		if ($logger) {
817
-			$typeString = ($type === ILockingProvider::LOCK_SHARED) ? 'shared' : 'exclusive';
818
-			$logger->info(
819
-				sprintf(
820
-					'change lock on "%s" to %s on storage "%s"',
821
-					$path,
822
-					$typeString,
823
-					$this->getId()
824
-				),
825
-				[
826
-					'app' => 'locking',
827
-				]
828
-			);
829
-		}
830
-		try {
831
-			$provider->changeLock('files/' . md5($this->getId() . '::' . trim($path, '/')), $type);
832
-		} catch (LockedException $e) {
833
-			$e = new LockedException($e->getPath(), $e, $e->getExistingLock(), $path);
834
-			if ($logger) {
835
-				$logger->info($e->getMessage(), ['exception' => $e]);
836
-			}
837
-			throw $e;
838
-		}
839
-	}
840
-
841
-	private function getLockLogger(): ?LoggerInterface {
842
-		if (is_null($this->shouldLogLocks)) {
843
-			$this->shouldLogLocks = \OC::$server->getConfig()->getSystemValueBool('filelocking.debug', false);
844
-			$this->logger = $this->shouldLogLocks ? \OC::$server->get(LoggerInterface::class) : null;
845
-		}
846
-		return $this->logger;
847
-	}
848
-
849
-	/**
850
-	 * @return array [ available, last_checked ]
851
-	 */
852
-	public function getAvailability() {
853
-		return $this->getStorageCache()->getAvailability();
854
-	}
855
-
856
-	/**
857
-	 * @param bool $isAvailable
858
-	 */
859
-	public function setAvailability($isAvailable) {
860
-		$this->getStorageCache()->setAvailability($isAvailable);
861
-	}
862
-
863
-	/**
864
-	 * @return bool
865
-	 */
866
-	public function needsPartFile() {
867
-		return true;
868
-	}
869
-
870
-	/**
871
-	 * fallback implementation
872
-	 *
873
-	 * @param string $path
874
-	 * @param resource $stream
875
-	 * @param int $size
876
-	 * @return int
877
-	 */
878
-	public function writeStream(string $path, $stream, int $size = null): int {
879
-		$target = $this->fopen($path, 'w');
880
-		if (!$target) {
881
-			throw new GenericFileException("Failed to open $path for writing");
882
-		}
883
-		try {
884
-			[$count, $result] = \OC_Helper::streamCopy($stream, $target);
885
-			if (!$result) {
886
-				throw new GenericFileException("Failed to copy stream");
887
-			}
888
-		} finally {
889
-			fclose($target);
890
-			fclose($stream);
891
-		}
892
-		return $count;
893
-	}
894
-
895
-	public function getDirectoryContent($directory): \Traversable {
896
-		$dh = $this->opendir($directory);
897
-		if (is_resource($dh)) {
898
-			$basePath = rtrim($directory, '/');
899
-			while (($file = readdir($dh)) !== false) {
900
-				if (!Filesystem::isIgnoredDir($file)) {
901
-					$childPath = $basePath . '/' . trim($file, '/');
902
-					$metadata = $this->getMetaData($childPath);
903
-					if ($metadata !== null) {
904
-						yield $metadata;
905
-					}
906
-				}
907
-			}
908
-		}
909
-	}
546
+                throw new InvalidCharacterInPathException();
547
+            }
548
+        }
549
+
550
+        // 255 characters is the limit on common file systems (ext/xfs)
551
+        // oc_filecache has a 250 char length limit for the filename
552
+        if (isset($fileName[250])) {
553
+            throw new FileNameTooLongException();
554
+        }
555
+
556
+        // NOTE: $path will remain unverified for now
557
+        $this->verifyPosixPath($fileName);
558
+    }
559
+
560
+    /**
561
+     * @param string $fileName
562
+     * @throws InvalidPathException
563
+     */
564
+    protected function verifyPosixPath($fileName) {
565
+        $this->scanForInvalidCharacters($fileName, "\\/");
566
+        $fileName = trim($fileName);
567
+        $reservedNames = ['*'];
568
+        if (in_array($fileName, $reservedNames)) {
569
+            throw new ReservedWordException();
570
+        }
571
+    }
572
+
573
+    /**
574
+     * @param string $fileName
575
+     * @param string $invalidChars
576
+     * @throws InvalidPathException
577
+     */
578
+    private function scanForInvalidCharacters($fileName, $invalidChars) {
579
+        foreach (str_split($invalidChars) as $char) {
580
+            if (strpos($fileName, $char) !== false) {
581
+                throw new InvalidCharacterInPathException();
582
+            }
583
+        }
584
+
585
+        $sanitizedFileName = filter_var($fileName, FILTER_UNSAFE_RAW, FILTER_FLAG_STRIP_LOW);
586
+        if ($sanitizedFileName !== $fileName) {
587
+            throw new InvalidCharacterInPathException();
588
+        }
589
+    }
590
+
591
+    /**
592
+     * @param array $options
593
+     */
594
+    public function setMountOptions(array $options) {
595
+        $this->mountOptions = $options;
596
+    }
597
+
598
+    /**
599
+     * @param string $name
600
+     * @param mixed $default
601
+     * @return mixed
602
+     */
603
+    public function getMountOption($name, $default = null) {
604
+        return isset($this->mountOptions[$name]) ? $this->mountOptions[$name] : $default;
605
+    }
606
+
607
+    /**
608
+     * @param IStorage $sourceStorage
609
+     * @param string $sourceInternalPath
610
+     * @param string $targetInternalPath
611
+     * @param bool $preserveMtime
612
+     * @return bool
613
+     */
614
+    public function copyFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath, $preserveMtime = false) {
615
+        if ($sourceStorage === $this) {
616
+            return $this->copy($sourceInternalPath, $targetInternalPath);
617
+        }
618
+
619
+        if ($sourceStorage->is_dir($sourceInternalPath)) {
620
+            $dh = $sourceStorage->opendir($sourceInternalPath);
621
+            $result = $this->mkdir($targetInternalPath);
622
+            if (is_resource($dh)) {
623
+                $result = true;
624
+                while ($result and ($file = readdir($dh)) !== false) {
625
+                    if (!Filesystem::isIgnoredDir($file)) {
626
+                        $result &= $this->copyFromStorage($sourceStorage, $sourceInternalPath . '/' . $file, $targetInternalPath . '/' . $file);
627
+                    }
628
+                }
629
+            }
630
+        } else {
631
+            $source = $sourceStorage->fopen($sourceInternalPath, 'r');
632
+            $result = false;
633
+            if ($source) {
634
+                try {
635
+                    $this->writeStream($targetInternalPath, $source);
636
+                    $result = true;
637
+                } catch (\Exception $e) {
638
+                    \OC::$server->get(LoggerInterface::class)->warning('Failed to copy stream to storage', ['exception' => $e]);
639
+                }
640
+            }
641
+
642
+            if ($result && $preserveMtime) {
643
+                $mtime = $sourceStorage->filemtime($sourceInternalPath);
644
+                $this->touch($targetInternalPath, is_int($mtime) ? $mtime : null);
645
+            }
646
+
647
+            if (!$result) {
648
+                // delete partially written target file
649
+                $this->unlink($targetInternalPath);
650
+                // delete cache entry that was created by fopen
651
+                $this->getCache()->remove($targetInternalPath);
652
+            }
653
+        }
654
+        return (bool)$result;
655
+    }
656
+
657
+    /**
658
+     * Check if a storage is the same as the current one, including wrapped storages
659
+     *
660
+     * @param IStorage $storage
661
+     * @return bool
662
+     */
663
+    private function isSameStorage(IStorage $storage): bool {
664
+        while ($storage->instanceOfStorage(Wrapper::class)) {
665
+            /**
666
+             * @var Wrapper $sourceStorage
667
+             */
668
+            $storage = $storage->getWrapperStorage();
669
+        }
670
+
671
+        return $storage === $this;
672
+    }
673
+
674
+    /**
675
+     * @param IStorage $sourceStorage
676
+     * @param string $sourceInternalPath
677
+     * @param string $targetInternalPath
678
+     * @return bool
679
+     */
680
+    public function moveFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) {
681
+        if ($this->isSameStorage($sourceStorage)) {
682
+            // resolve any jailed paths
683
+            while ($sourceStorage->instanceOfStorage(Jail::class)) {
684
+                /**
685
+                 * @var Jail $sourceStorage
686
+                 */
687
+                $sourceInternalPath = $sourceStorage->getUnjailedPath($sourceInternalPath);
688
+                $sourceStorage = $sourceStorage->getUnjailedStorage();
689
+            }
690
+
691
+            return $this->rename($sourceInternalPath, $targetInternalPath);
692
+        }
693
+
694
+        if (!$sourceStorage->isDeletable($sourceInternalPath)) {
695
+            return false;
696
+        }
697
+
698
+        $result = $this->copyFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath, true);
699
+        if ($result) {
700
+            if ($sourceStorage->is_dir($sourceInternalPath)) {
701
+                $result = $sourceStorage->rmdir($sourceInternalPath);
702
+            } else {
703
+                $result = $sourceStorage->unlink($sourceInternalPath);
704
+            }
705
+        }
706
+        return $result;
707
+    }
708
+
709
+    /**
710
+     * @inheritdoc
711
+     */
712
+    public function getMetaData($path) {
713
+        if (Filesystem::isFileBlacklisted($path)) {
714
+            throw new ForbiddenException('Invalid path: ' . $path, false);
715
+        }
716
+
717
+        $permissions = $this->getPermissions($path);
718
+        if (!$permissions & \OCP\Constants::PERMISSION_READ) {
719
+            //can't read, nothing we can do
720
+            return null;
721
+        }
722
+
723
+        $data = [];
724
+        $data['mimetype'] = $this->getMimeType($path);
725
+        $data['mtime'] = $this->filemtime($path);
726
+        if ($data['mtime'] === false) {
727
+            $data['mtime'] = time();
728
+        }
729
+        if ($data['mimetype'] == 'httpd/unix-directory') {
730
+            $data['size'] = -1; //unknown
731
+        } else {
732
+            $data['size'] = $this->filesize($path);
733
+        }
734
+        $data['etag'] = $this->getETag($path);
735
+        $data['storage_mtime'] = $data['mtime'];
736
+        $data['permissions'] = $permissions;
737
+        $data['name'] = basename($path);
738
+
739
+        return $data;
740
+    }
741
+
742
+    /**
743
+     * @param string $path
744
+     * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
745
+     * @param \OCP\Lock\ILockingProvider $provider
746
+     * @throws \OCP\Lock\LockedException
747
+     */
748
+    public function acquireLock($path, $type, ILockingProvider $provider) {
749
+        $logger = $this->getLockLogger();
750
+        if ($logger) {
751
+            $typeString = ($type === ILockingProvider::LOCK_SHARED) ? 'shared' : 'exclusive';
752
+            $logger->info(
753
+                sprintf(
754
+                    'acquire %s lock on "%s" on storage "%s"',
755
+                    $typeString,
756
+                    $path,
757
+                    $this->getId()
758
+                ),
759
+                [
760
+                    'app' => 'locking',
761
+                ]
762
+            );
763
+        }
764
+        try {
765
+            $provider->acquireLock('files/' . md5($this->getId() . '::' . trim($path, '/')), $type, $this->getId() . '::' . $path);
766
+        } catch (LockedException $e) {
767
+            $e = new LockedException($e->getPath(), $e, $e->getExistingLock(), $path);
768
+            if ($logger) {
769
+                $logger->info($e->getMessage(), ['exception' => $e]);
770
+            }
771
+            throw $e;
772
+        }
773
+    }
774
+
775
+    /**
776
+     * @param string $path
777
+     * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
778
+     * @param \OCP\Lock\ILockingProvider $provider
779
+     * @throws \OCP\Lock\LockedException
780
+     */
781
+    public function releaseLock($path, $type, ILockingProvider $provider) {
782
+        $logger = $this->getLockLogger();
783
+        if ($logger) {
784
+            $typeString = ($type === ILockingProvider::LOCK_SHARED) ? 'shared' : 'exclusive';
785
+            $logger->info(
786
+                sprintf(
787
+                    'release %s lock on "%s" on storage "%s"',
788
+                    $typeString,
789
+                    $path,
790
+                    $this->getId()
791
+                ),
792
+                [
793
+                    'app' => 'locking',
794
+                ]
795
+            );
796
+        }
797
+        try {
798
+            $provider->releaseLock('files/' . md5($this->getId() . '::' . trim($path, '/')), $type);
799
+        } catch (LockedException $e) {
800
+            $e = new LockedException($e->getPath(), $e, $e->getExistingLock(), $path);
801
+            if ($logger) {
802
+                $logger->info($e->getMessage(), ['exception' => $e]);
803
+            }
804
+            throw $e;
805
+        }
806
+    }
807
+
808
+    /**
809
+     * @param string $path
810
+     * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
811
+     * @param \OCP\Lock\ILockingProvider $provider
812
+     * @throws \OCP\Lock\LockedException
813
+     */
814
+    public function changeLock($path, $type, ILockingProvider $provider) {
815
+        $logger = $this->getLockLogger();
816
+        if ($logger) {
817
+            $typeString = ($type === ILockingProvider::LOCK_SHARED) ? 'shared' : 'exclusive';
818
+            $logger->info(
819
+                sprintf(
820
+                    'change lock on "%s" to %s on storage "%s"',
821
+                    $path,
822
+                    $typeString,
823
+                    $this->getId()
824
+                ),
825
+                [
826
+                    'app' => 'locking',
827
+                ]
828
+            );
829
+        }
830
+        try {
831
+            $provider->changeLock('files/' . md5($this->getId() . '::' . trim($path, '/')), $type);
832
+        } catch (LockedException $e) {
833
+            $e = new LockedException($e->getPath(), $e, $e->getExistingLock(), $path);
834
+            if ($logger) {
835
+                $logger->info($e->getMessage(), ['exception' => $e]);
836
+            }
837
+            throw $e;
838
+        }
839
+    }
840
+
841
+    private function getLockLogger(): ?LoggerInterface {
842
+        if (is_null($this->shouldLogLocks)) {
843
+            $this->shouldLogLocks = \OC::$server->getConfig()->getSystemValueBool('filelocking.debug', false);
844
+            $this->logger = $this->shouldLogLocks ? \OC::$server->get(LoggerInterface::class) : null;
845
+        }
846
+        return $this->logger;
847
+    }
848
+
849
+    /**
850
+     * @return array [ available, last_checked ]
851
+     */
852
+    public function getAvailability() {
853
+        return $this->getStorageCache()->getAvailability();
854
+    }
855
+
856
+    /**
857
+     * @param bool $isAvailable
858
+     */
859
+    public function setAvailability($isAvailable) {
860
+        $this->getStorageCache()->setAvailability($isAvailable);
861
+    }
862
+
863
+    /**
864
+     * @return bool
865
+     */
866
+    public function needsPartFile() {
867
+        return true;
868
+    }
869
+
870
+    /**
871
+     * fallback implementation
872
+     *
873
+     * @param string $path
874
+     * @param resource $stream
875
+     * @param int $size
876
+     * @return int
877
+     */
878
+    public function writeStream(string $path, $stream, int $size = null): int {
879
+        $target = $this->fopen($path, 'w');
880
+        if (!$target) {
881
+            throw new GenericFileException("Failed to open $path for writing");
882
+        }
883
+        try {
884
+            [$count, $result] = \OC_Helper::streamCopy($stream, $target);
885
+            if (!$result) {
886
+                throw new GenericFileException("Failed to copy stream");
887
+            }
888
+        } finally {
889
+            fclose($target);
890
+            fclose($stream);
891
+        }
892
+        return $count;
893
+    }
894
+
895
+    public function getDirectoryContent($directory): \Traversable {
896
+        $dh = $this->opendir($directory);
897
+        if (is_resource($dh)) {
898
+            $basePath = rtrim($directory, '/');
899
+            while (($file = readdir($dh)) !== false) {
900
+                if (!Filesystem::isIgnoredDir($file)) {
901
+                    $childPath = $basePath . '/' . trim($file, '/');
902
+                    $metadata = $this->getMetaData($childPath);
903
+                    if ($metadata !== null) {
904
+                        yield $metadata;
905
+                    }
906
+                }
907
+            }
908
+        }
909
+    }
910 910
 }
Please login to merge, or discard this patch.
Spacing   +21 added lines, -21 removed lines patch added patch discarded remove patch
@@ -121,7 +121,7 @@  discard block
 block discarded – undo
121 121
 		return $this->filetype($path) === 'file';
122 122
 	}
123 123
 
124
-	public function filesize($path): false|int|float {
124
+	public function filesize($path): false | int | float {
125 125
 		if ($this->is_dir($path)) {
126 126
 			return 0; //by definition
127 127
 		} else {
@@ -230,7 +230,7 @@  discard block
 block discarded – undo
230 230
 			$this->mkdir($target);
231 231
 			while ($file = readdir($dir)) {
232 232
 				if (!Filesystem::isIgnoredDir($file)) {
233
-					if (!$this->copy($source . '/' . $file, $target . '/' . $file)) {
233
+					if (!$this->copy($source.'/'.$file, $target.'/'.$file)) {
234 234
 						closedir($dir);
235 235
 						return false;
236 236
 					}
@@ -285,12 +285,12 @@  discard block
 block discarded – undo
285 285
 		if (is_resource($dh)) {
286 286
 			while (($file = readdir($dh)) !== false) {
287 287
 				if (!\OC\Files\Filesystem::isIgnoredDir($file)) {
288
-					if ($this->is_dir($path . '/' . $file)) {
289
-						mkdir($target . '/' . $file);
290
-						$this->addLocalFolder($path . '/' . $file, $target . '/' . $file);
288
+					if ($this->is_dir($path.'/'.$file)) {
289
+						mkdir($target.'/'.$file);
290
+						$this->addLocalFolder($path.'/'.$file, $target.'/'.$file);
291 291
 					} else {
292
-						$tmp = $this->toTmpFile($path . '/' . $file);
293
-						rename($tmp, $target . '/' . $file);
292
+						$tmp = $this->toTmpFile($path.'/'.$file);
293
+						rename($tmp, $target.'/'.$file);
294 294
 					}
295 295
 				}
296 296
 			}
@@ -311,10 +311,10 @@  discard block
 block discarded – undo
311 311
 					continue;
312 312
 				}
313 313
 				if (strstr(strtolower($item), strtolower($query)) !== false) {
314
-					$files[] = $dir . '/' . $item;
314
+					$files[] = $dir.'/'.$item;
315 315
 				}
316
-				if ($this->is_dir($dir . '/' . $item)) {
317
-					$files = array_merge($files, $this->searchInDir($query, $dir . '/' . $item));
316
+				if ($this->is_dir($dir.'/'.$item)) {
317
+					$files = array_merge($files, $this->searchInDir($query, $dir.'/'.$item));
318 318
 				}
319 319
 			}
320 320
 		}
@@ -365,7 +365,7 @@  discard block
 block discarded – undo
365 365
 		if (!isset($this->watcher)) {
366 366
 			$this->watcher = new Watcher($storage);
367 367
 			$globalPolicy = \OC::$server->getConfig()->getSystemValue('filesystem_check_changes', Watcher::CHECK_NEVER);
368
-			$this->watcher->setPolicy((int)$this->getMountOption('filesystem_check_changes', $globalPolicy));
368
+			$this->watcher->setPolicy((int) $this->getMountOption('filesystem_check_changes', $globalPolicy));
369 369
 		}
370 370
 		return $this->watcher;
371 371
 	}
@@ -382,7 +382,7 @@  discard block
 block discarded – undo
382 382
 		}
383 383
 		if (!isset($storage->propagator)) {
384 384
 			$config = \OC::$server->getSystemConfig();
385
-			$storage->propagator = new Propagator($storage, \OC::$server->getDatabaseConnection(), ['appdata_' . $config->getValue('instanceid')]);
385
+			$storage->propagator = new Propagator($storage, \OC::$server->getDatabaseConnection(), ['appdata_'.$config->getValue('instanceid')]);
386 386
 		}
387 387
 		return $storage->propagator;
388 388
 	}
@@ -440,7 +440,7 @@  discard block
 block discarded – undo
440 440
 	 */
441 441
 	public function cleanPath($path) {
442 442
 		if (strlen($path) == 0 or $path[0] != '/') {
443
-			$path = '/' . $path;
443
+			$path = '/'.$path;
444 444
 		}
445 445
 
446 446
 		$output = [];
@@ -469,7 +469,7 @@  discard block
 block discarded – undo
469 469
 			return false;
470 470
 		} catch (\Exception $e) {
471 471
 			\OC::$server->get(LoggerInterface::class)->warning(
472
-				"External storage not available: " . $e->getMessage(),
472
+				"External storage not available: ".$e->getMessage(),
473 473
 				['exception' => $e]
474 474
 			);
475 475
 			return false;
@@ -623,7 +623,7 @@  discard block
 block discarded – undo
623 623
 				$result = true;
624 624
 				while ($result and ($file = readdir($dh)) !== false) {
625 625
 					if (!Filesystem::isIgnoredDir($file)) {
626
-						$result &= $this->copyFromStorage($sourceStorage, $sourceInternalPath . '/' . $file, $targetInternalPath . '/' . $file);
626
+						$result &= $this->copyFromStorage($sourceStorage, $sourceInternalPath.'/'.$file, $targetInternalPath.'/'.$file);
627 627
 					}
628 628
 				}
629 629
 			}
@@ -651,7 +651,7 @@  discard block
 block discarded – undo
651 651
 				$this->getCache()->remove($targetInternalPath);
652 652
 			}
653 653
 		}
654
-		return (bool)$result;
654
+		return (bool) $result;
655 655
 	}
656 656
 
657 657
 	/**
@@ -711,7 +711,7 @@  discard block
 block discarded – undo
711 711
 	 */
712 712
 	public function getMetaData($path) {
713 713
 		if (Filesystem::isFileBlacklisted($path)) {
714
-			throw new ForbiddenException('Invalid path: ' . $path, false);
714
+			throw new ForbiddenException('Invalid path: '.$path, false);
715 715
 		}
716 716
 
717 717
 		$permissions = $this->getPermissions($path);
@@ -762,7 +762,7 @@  discard block
 block discarded – undo
762 762
 			);
763 763
 		}
764 764
 		try {
765
-			$provider->acquireLock('files/' . md5($this->getId() . '::' . trim($path, '/')), $type, $this->getId() . '::' . $path);
765
+			$provider->acquireLock('files/'.md5($this->getId().'::'.trim($path, '/')), $type, $this->getId().'::'.$path);
766 766
 		} catch (LockedException $e) {
767 767
 			$e = new LockedException($e->getPath(), $e, $e->getExistingLock(), $path);
768 768
 			if ($logger) {
@@ -795,7 +795,7 @@  discard block
 block discarded – undo
795 795
 			);
796 796
 		}
797 797
 		try {
798
-			$provider->releaseLock('files/' . md5($this->getId() . '::' . trim($path, '/')), $type);
798
+			$provider->releaseLock('files/'.md5($this->getId().'::'.trim($path, '/')), $type);
799 799
 		} catch (LockedException $e) {
800 800
 			$e = new LockedException($e->getPath(), $e, $e->getExistingLock(), $path);
801 801
 			if ($logger) {
@@ -828,7 +828,7 @@  discard block
 block discarded – undo
828 828
 			);
829 829
 		}
830 830
 		try {
831
-			$provider->changeLock('files/' . md5($this->getId() . '::' . trim($path, '/')), $type);
831
+			$provider->changeLock('files/'.md5($this->getId().'::'.trim($path, '/')), $type);
832 832
 		} catch (LockedException $e) {
833 833
 			$e = new LockedException($e->getPath(), $e, $e->getExistingLock(), $path);
834 834
 			if ($logger) {
@@ -898,7 +898,7 @@  discard block
 block discarded – undo
898 898
 			$basePath = rtrim($directory, '/');
899 899
 			while (($file = readdir($dh)) !== false) {
900 900
 				if (!Filesystem::isIgnoredDir($file)) {
901
-					$childPath = $basePath . '/' . trim($file, '/');
901
+					$childPath = $basePath.'/'.trim($file, '/');
902 902
 					$metadata = $this->getMetaData($childPath);
903 903
 					if ($metadata !== null) {
904 904
 						yield $metadata;
Please login to merge, or discard this patch.