Passed
Push — master ( bf39ad...32551b )
by Robin
14:52 queued 12s
created
lib/private/Files/ObjectStore/ObjectStoreStorage.php 1 patch
Indentation   +553 added lines, -553 removed lines patch added patch discarded remove patch
@@ -42,560 +42,560 @@
 block discarded – undo
42 42
 use OCP\Files\Storage\IStorage;
43 43
 
44 44
 class ObjectStoreStorage extends \OC\Files\Storage\Common {
45
-	use CopyDirectory;
46
-
47
-	/**
48
-	 * @var \OCP\Files\ObjectStore\IObjectStore $objectStore
49
-	 */
50
-	protected $objectStore;
51
-	/**
52
-	 * @var string $id
53
-	 */
54
-	protected $id;
55
-	/**
56
-	 * @var \OC\User\User $user
57
-	 */
58
-	protected $user;
59
-
60
-	private $objectPrefix = 'urn:oid:';
61
-
62
-	private $logger;
63
-
64
-	public function __construct($params) {
65
-		if (isset($params['objectstore']) && $params['objectstore'] instanceof IObjectStore) {
66
-			$this->objectStore = $params['objectstore'];
67
-		} else {
68
-			throw new \Exception('missing IObjectStore instance');
69
-		}
70
-		if (isset($params['storageid'])) {
71
-			$this->id = 'object::store:' . $params['storageid'];
72
-		} else {
73
-			$this->id = 'object::store:' . $this->objectStore->getStorageId();
74
-		}
75
-		if (isset($params['objectPrefix'])) {
76
-			$this->objectPrefix = $params['objectPrefix'];
77
-		}
78
-		//initialize cache with root directory in cache
79
-		if (!$this->is_dir('/')) {
80
-			$this->mkdir('/');
81
-		}
82
-
83
-		$this->logger = \OC::$server->getLogger();
84
-	}
85
-
86
-	public function mkdir($path) {
87
-		$path = $this->normalizePath($path);
88
-
89
-		if ($this->file_exists($path)) {
90
-			return false;
91
-		}
92
-
93
-		$mTime = time();
94
-		$data = [
95
-			'mimetype' => 'httpd/unix-directory',
96
-			'size' => 0,
97
-			'mtime' => $mTime,
98
-			'storage_mtime' => $mTime,
99
-			'permissions' => \OCP\Constants::PERMISSION_ALL,
100
-		];
101
-		if ($path === '') {
102
-			//create root on the fly
103
-			$data['etag'] = $this->getETag('');
104
-			$this->getCache()->put('', $data);
105
-			return true;
106
-		} else {
107
-			// if parent does not exist, create it
108
-			$parent = $this->normalizePath(dirname($path));
109
-			$parentType = $this->filetype($parent);
110
-			if ($parentType === false) {
111
-				if (!$this->mkdir($parent)) {
112
-					// something went wrong
113
-					return false;
114
-				}
115
-			} elseif ($parentType === 'file') {
116
-				// parent is a file
117
-				return false;
118
-			}
119
-			// finally create the new dir
120
-			$mTime = time(); // update mtime
121
-			$data['mtime'] = $mTime;
122
-			$data['storage_mtime'] = $mTime;
123
-			$data['etag'] = $this->getETag($path);
124
-			$this->getCache()->put($path, $data);
125
-			return true;
126
-		}
127
-	}
128
-
129
-	/**
130
-	 * @param string $path
131
-	 * @return string
132
-	 */
133
-	private function normalizePath($path) {
134
-		$path = trim($path, '/');
135
-		//FIXME why do we sometimes get a path like 'files//username'?
136
-		$path = str_replace('//', '/', $path);
137
-
138
-		// dirname('/folder') returns '.' but internally (in the cache) we store the root as ''
139
-		if (!$path || $path === '.') {
140
-			$path = '';
141
-		}
142
-
143
-		return $path;
144
-	}
145
-
146
-	/**
147
-	 * Object Stores use a NoopScanner because metadata is directly stored in
148
-	 * the file cache and cannot really scan the filesystem. The storage passed in is not used anywhere.
149
-	 *
150
-	 * @param string $path
151
-	 * @param \OC\Files\Storage\Storage (optional) the storage to pass to the scanner
152
-	 * @return \OC\Files\ObjectStore\NoopScanner
153
-	 */
154
-	public function getScanner($path = '', $storage = null) {
155
-		if (!$storage) {
156
-			$storage = $this;
157
-		}
158
-		if (!isset($this->scanner)) {
159
-			$this->scanner = new NoopScanner($storage);
160
-		}
161
-		return $this->scanner;
162
-	}
163
-
164
-	public function getId() {
165
-		return $this->id;
166
-	}
167
-
168
-	public function rmdir($path) {
169
-		$path = $this->normalizePath($path);
170
-
171
-		if (!$this->is_dir($path)) {
172
-			return false;
173
-		}
174
-
175
-		if (!$this->rmObjects($path)) {
176
-			return false;
177
-		}
178
-
179
-		$this->getCache()->remove($path);
180
-
181
-		return true;
182
-	}
183
-
184
-	private function rmObjects($path) {
185
-		$children = $this->getCache()->getFolderContents($path);
186
-		foreach ($children as $child) {
187
-			if ($child['mimetype'] === 'httpd/unix-directory') {
188
-				if (!$this->rmObjects($child['path'])) {
189
-					return false;
190
-				}
191
-			} else {
192
-				if (!$this->unlink($child['path'])) {
193
-					return false;
194
-				}
195
-			}
196
-		}
197
-
198
-		return true;
199
-	}
200
-
201
-	public function unlink($path) {
202
-		$path = $this->normalizePath($path);
203
-		$stat = $this->stat($path);
204
-
205
-		if ($stat && isset($stat['fileid'])) {
206
-			if ($stat['mimetype'] === 'httpd/unix-directory') {
207
-				return $this->rmdir($path);
208
-			}
209
-			try {
210
-				$this->objectStore->deleteObject($this->getURN($stat['fileid']));
211
-			} catch (\Exception $ex) {
212
-				if ($ex->getCode() !== 404) {
213
-					$this->logger->logException($ex, [
214
-						'app' => 'objectstore',
215
-						'message' => 'Could not delete object ' . $this->getURN($stat['fileid']) . ' for ' . $path,
216
-					]);
217
-					return false;
218
-				}
219
-				//removing from cache is ok as it does not exist in the objectstore anyway
220
-			}
221
-			$this->getCache()->remove($path);
222
-			return true;
223
-		}
224
-		return false;
225
-	}
226
-
227
-	public function stat($path) {
228
-		$path = $this->normalizePath($path);
229
-		$cacheEntry = $this->getCache()->get($path);
230
-		if ($cacheEntry instanceof CacheEntry) {
231
-			return $cacheEntry->getData();
232
-		} else {
233
-			return false;
234
-		}
235
-	}
236
-
237
-	public function getPermissions($path) {
238
-		$stat = $this->stat($path);
239
-
240
-		if (is_array($stat) && isset($stat['permissions'])) {
241
-			return $stat['permissions'];
242
-		}
243
-
244
-		return parent::getPermissions($path);
245
-	}
246
-
247
-	/**
248
-	 * Override this method if you need a different unique resource identifier for your object storage implementation.
249
-	 * The default implementations just appends the fileId to 'urn:oid:'. Make sure the URN is unique over all users.
250
-	 * You may need a mapping table to store your URN if it cannot be generated from the fileid.
251
-	 *
252
-	 * @param int $fileId the fileid
253
-	 * @return null|string the unified resource name used to identify the object
254
-	 */
255
-	public function getURN($fileId) {
256
-		if (is_numeric($fileId)) {
257
-			return $this->objectPrefix . $fileId;
258
-		}
259
-		return null;
260
-	}
261
-
262
-	public function opendir($path) {
263
-		$path = $this->normalizePath($path);
264
-
265
-		try {
266
-			$files = [];
267
-			$folderContents = $this->getCache()->getFolderContents($path);
268
-			foreach ($folderContents as $file) {
269
-				$files[] = $file['name'];
270
-			}
271
-
272
-			return IteratorDirectory::wrap($files);
273
-		} catch (\Exception $e) {
274
-			$this->logger->logException($e);
275
-			return false;
276
-		}
277
-	}
278
-
279
-	public function filetype($path) {
280
-		$path = $this->normalizePath($path);
281
-		$stat = $this->stat($path);
282
-		if ($stat) {
283
-			if ($stat['mimetype'] === 'httpd/unix-directory') {
284
-				return 'dir';
285
-			}
286
-			return 'file';
287
-		} else {
288
-			return false;
289
-		}
290
-	}
291
-
292
-	public function fopen($path, $mode) {
293
-		$path = $this->normalizePath($path);
294
-
295
-		if (strrpos($path, '.') !== false) {
296
-			$ext = substr($path, strrpos($path, '.'));
297
-		} else {
298
-			$ext = '';
299
-		}
300
-
301
-		switch ($mode) {
302
-			case 'r':
303
-			case 'rb':
304
-				$stat = $this->stat($path);
305
-				if (is_array($stat)) {
306
-					// Reading 0 sized files is a waste of time
307
-					if (isset($stat['size']) && $stat['size'] === 0) {
308
-						return fopen('php://memory', $mode);
309
-					}
310
-
311
-					try {
312
-						return $this->objectStore->readObject($this->getURN($stat['fileid']));
313
-					} catch (NotFoundException $e) {
314
-						$this->logger->logException($e, [
315
-							'app' => 'objectstore',
316
-							'message' => 'Could not get object ' . $this->getURN($stat['fileid']) . ' for file ' . $path,
317
-						]);
318
-						throw $e;
319
-					} catch (\Exception $ex) {
320
-						$this->logger->logException($ex, [
321
-							'app' => 'objectstore',
322
-							'message' => 'Could not get object ' . $this->getURN($stat['fileid']) . ' for file ' . $path,
323
-						]);
324
-						return false;
325
-					}
326
-				} else {
327
-					return false;
328
-				}
329
-			// no break
330
-			case 'w':
331
-			case 'wb':
332
-			case 'w+':
333
-			case 'wb+':
334
-				$tmpFile = \OC::$server->getTempManager()->getTemporaryFile($ext);
335
-				$handle = fopen($tmpFile, $mode);
336
-				return CallbackWrapper::wrap($handle, null, null, function () use ($path, $tmpFile) {
337
-					$this->writeBack($tmpFile, $path);
338
-				});
339
-			case 'a':
340
-			case 'ab':
341
-			case 'r+':
342
-			case 'a+':
343
-			case 'x':
344
-			case 'x+':
345
-			case 'c':
346
-			case 'c+':
347
-				$tmpFile = \OC::$server->getTempManager()->getTemporaryFile($ext);
348
-				if ($this->file_exists($path)) {
349
-					$source = $this->fopen($path, 'r');
350
-					file_put_contents($tmpFile, $source);
351
-				}
352
-				$handle = fopen($tmpFile, $mode);
353
-				return CallbackWrapper::wrap($handle, null, null, function () use ($path, $tmpFile) {
354
-					$this->writeBack($tmpFile, $path);
355
-				});
356
-		}
357
-		return false;
358
-	}
359
-
360
-	public function file_exists($path) {
361
-		$path = $this->normalizePath($path);
362
-		return (bool)$this->stat($path);
363
-	}
364
-
365
-	public function rename($source, $target) {
366
-		$source = $this->normalizePath($source);
367
-		$target = $this->normalizePath($target);
368
-		$this->remove($target);
369
-		$this->getCache()->move($source, $target);
370
-		$this->touch(dirname($target));
371
-		return true;
372
-	}
373
-
374
-	public function getMimeType($path) {
375
-		$path = $this->normalizePath($path);
376
-		return parent::getMimeType($path);
377
-	}
378
-
379
-	public function touch($path, $mtime = null) {
380
-		if (is_null($mtime)) {
381
-			$mtime = time();
382
-		}
383
-
384
-		$path = $this->normalizePath($path);
385
-		$dirName = dirname($path);
386
-		$parentExists = $this->is_dir($dirName);
387
-		if (!$parentExists) {
388
-			return false;
389
-		}
390
-
391
-		$stat = $this->stat($path);
392
-		if (is_array($stat)) {
393
-			// update existing mtime in db
394
-			$stat['mtime'] = $mtime;
395
-			$this->getCache()->update($stat['fileid'], $stat);
396
-		} else {
397
-			try {
398
-				//create a empty file, need to have at least on char to make it
399
-				// work with all object storage implementations
400
-				$this->file_put_contents($path, ' ');
401
-				$mimeType = \OC::$server->getMimeTypeDetector()->detectPath($path);
402
-				$stat = [
403
-					'etag' => $this->getETag($path),
404
-					'mimetype' => $mimeType,
405
-					'size' => 0,
406
-					'mtime' => $mtime,
407
-					'storage_mtime' => $mtime,
408
-					'permissions' => \OCP\Constants::PERMISSION_ALL - \OCP\Constants::PERMISSION_CREATE,
409
-				];
410
-				$this->getCache()->put($path, $stat);
411
-			} catch (\Exception $ex) {
412
-				$this->logger->logException($ex, [
413
-					'app' => 'objectstore',
414
-					'message' => 'Could not create object for ' . $path,
415
-				]);
416
-				throw $ex;
417
-			}
418
-		}
419
-		return true;
420
-	}
421
-
422
-	public function writeBack($tmpFile, $path) {
423
-		$size = filesize($tmpFile);
424
-		$this->writeStream($path, fopen($tmpFile, 'r'), $size);
425
-	}
426
-
427
-	/**
428
-	 * external changes are not supported, exclusive access to the object storage is assumed
429
-	 *
430
-	 * @param string $path
431
-	 * @param int $time
432
-	 * @return false
433
-	 */
434
-	public function hasUpdated($path, $time) {
435
-		return false;
436
-	}
437
-
438
-	public function needsPartFile() {
439
-		return false;
440
-	}
441
-
442
-	public function file_put_contents($path, $data) {
443
-		$handle = $this->fopen($path, 'w+');
444
-		$result = fwrite($handle, $data);
445
-		fclose($handle);
446
-		return $result;
447
-	}
448
-
449
-	public function writeStream(string $path, $stream, int $size = null): int {
450
-		$stat = $this->stat($path);
451
-		if (empty($stat)) {
452
-			// create new file
453
-			$stat = [
454
-				'permissions' => \OCP\Constants::PERMISSION_ALL - \OCP\Constants::PERMISSION_CREATE,
455
-			];
456
-		}
457
-		// update stat with new data
458
-		$mTime = time();
459
-		$stat['size'] = (int)$size;
460
-		$stat['mtime'] = $mTime;
461
-		$stat['storage_mtime'] = $mTime;
462
-
463
-		$mimetypeDetector = \OC::$server->getMimeTypeDetector();
464
-		$mimetype = $mimetypeDetector->detectPath($path);
465
-
466
-		$stat['mimetype'] = $mimetype;
467
-		$stat['etag'] = $this->getETag($path);
468
-
469
-		$exists = $this->getCache()->inCache($path);
470
-		$uploadPath = $exists ? $path : $path . '.part';
471
-
472
-		if ($exists) {
473
-			$fileId = $stat['fileid'];
474
-		} else {
475
-			$fileId = $this->getCache()->put($uploadPath, $stat);
476
-		}
477
-
478
-		$urn = $this->getURN($fileId);
479
-		try {
480
-			//upload to object storage
481
-			if ($size === null) {
482
-				$countStream = CountWrapper::wrap($stream, function ($writtenSize) use ($fileId, &$size) {
483
-					$this->getCache()->update($fileId, [
484
-						'size' => $writtenSize,
485
-					]);
486
-					$size = $writtenSize;
487
-				});
488
-				$this->objectStore->writeObject($urn, $countStream);
489
-				if (is_resource($countStream)) {
490
-					fclose($countStream);
491
-				}
492
-				$stat['size'] = $size;
493
-			} else {
494
-				$this->objectStore->writeObject($urn, $stream);
495
-			}
496
-		} catch (\Exception $ex) {
497
-			if (!$exists) {
498
-				/*
45
+    use CopyDirectory;
46
+
47
+    /**
48
+     * @var \OCP\Files\ObjectStore\IObjectStore $objectStore
49
+     */
50
+    protected $objectStore;
51
+    /**
52
+     * @var string $id
53
+     */
54
+    protected $id;
55
+    /**
56
+     * @var \OC\User\User $user
57
+     */
58
+    protected $user;
59
+
60
+    private $objectPrefix = 'urn:oid:';
61
+
62
+    private $logger;
63
+
64
+    public function __construct($params) {
65
+        if (isset($params['objectstore']) && $params['objectstore'] instanceof IObjectStore) {
66
+            $this->objectStore = $params['objectstore'];
67
+        } else {
68
+            throw new \Exception('missing IObjectStore instance');
69
+        }
70
+        if (isset($params['storageid'])) {
71
+            $this->id = 'object::store:' . $params['storageid'];
72
+        } else {
73
+            $this->id = 'object::store:' . $this->objectStore->getStorageId();
74
+        }
75
+        if (isset($params['objectPrefix'])) {
76
+            $this->objectPrefix = $params['objectPrefix'];
77
+        }
78
+        //initialize cache with root directory in cache
79
+        if (!$this->is_dir('/')) {
80
+            $this->mkdir('/');
81
+        }
82
+
83
+        $this->logger = \OC::$server->getLogger();
84
+    }
85
+
86
+    public function mkdir($path) {
87
+        $path = $this->normalizePath($path);
88
+
89
+        if ($this->file_exists($path)) {
90
+            return false;
91
+        }
92
+
93
+        $mTime = time();
94
+        $data = [
95
+            'mimetype' => 'httpd/unix-directory',
96
+            'size' => 0,
97
+            'mtime' => $mTime,
98
+            'storage_mtime' => $mTime,
99
+            'permissions' => \OCP\Constants::PERMISSION_ALL,
100
+        ];
101
+        if ($path === '') {
102
+            //create root on the fly
103
+            $data['etag'] = $this->getETag('');
104
+            $this->getCache()->put('', $data);
105
+            return true;
106
+        } else {
107
+            // if parent does not exist, create it
108
+            $parent = $this->normalizePath(dirname($path));
109
+            $parentType = $this->filetype($parent);
110
+            if ($parentType === false) {
111
+                if (!$this->mkdir($parent)) {
112
+                    // something went wrong
113
+                    return false;
114
+                }
115
+            } elseif ($parentType === 'file') {
116
+                // parent is a file
117
+                return false;
118
+            }
119
+            // finally create the new dir
120
+            $mTime = time(); // update mtime
121
+            $data['mtime'] = $mTime;
122
+            $data['storage_mtime'] = $mTime;
123
+            $data['etag'] = $this->getETag($path);
124
+            $this->getCache()->put($path, $data);
125
+            return true;
126
+        }
127
+    }
128
+
129
+    /**
130
+     * @param string $path
131
+     * @return string
132
+     */
133
+    private function normalizePath($path) {
134
+        $path = trim($path, '/');
135
+        //FIXME why do we sometimes get a path like 'files//username'?
136
+        $path = str_replace('//', '/', $path);
137
+
138
+        // dirname('/folder') returns '.' but internally (in the cache) we store the root as ''
139
+        if (!$path || $path === '.') {
140
+            $path = '';
141
+        }
142
+
143
+        return $path;
144
+    }
145
+
146
+    /**
147
+     * Object Stores use a NoopScanner because metadata is directly stored in
148
+     * the file cache and cannot really scan the filesystem. The storage passed in is not used anywhere.
149
+     *
150
+     * @param string $path
151
+     * @param \OC\Files\Storage\Storage (optional) the storage to pass to the scanner
152
+     * @return \OC\Files\ObjectStore\NoopScanner
153
+     */
154
+    public function getScanner($path = '', $storage = null) {
155
+        if (!$storage) {
156
+            $storage = $this;
157
+        }
158
+        if (!isset($this->scanner)) {
159
+            $this->scanner = new NoopScanner($storage);
160
+        }
161
+        return $this->scanner;
162
+    }
163
+
164
+    public function getId() {
165
+        return $this->id;
166
+    }
167
+
168
+    public function rmdir($path) {
169
+        $path = $this->normalizePath($path);
170
+
171
+        if (!$this->is_dir($path)) {
172
+            return false;
173
+        }
174
+
175
+        if (!$this->rmObjects($path)) {
176
+            return false;
177
+        }
178
+
179
+        $this->getCache()->remove($path);
180
+
181
+        return true;
182
+    }
183
+
184
+    private function rmObjects($path) {
185
+        $children = $this->getCache()->getFolderContents($path);
186
+        foreach ($children as $child) {
187
+            if ($child['mimetype'] === 'httpd/unix-directory') {
188
+                if (!$this->rmObjects($child['path'])) {
189
+                    return false;
190
+                }
191
+            } else {
192
+                if (!$this->unlink($child['path'])) {
193
+                    return false;
194
+                }
195
+            }
196
+        }
197
+
198
+        return true;
199
+    }
200
+
201
+    public function unlink($path) {
202
+        $path = $this->normalizePath($path);
203
+        $stat = $this->stat($path);
204
+
205
+        if ($stat && isset($stat['fileid'])) {
206
+            if ($stat['mimetype'] === 'httpd/unix-directory') {
207
+                return $this->rmdir($path);
208
+            }
209
+            try {
210
+                $this->objectStore->deleteObject($this->getURN($stat['fileid']));
211
+            } catch (\Exception $ex) {
212
+                if ($ex->getCode() !== 404) {
213
+                    $this->logger->logException($ex, [
214
+                        'app' => 'objectstore',
215
+                        'message' => 'Could not delete object ' . $this->getURN($stat['fileid']) . ' for ' . $path,
216
+                    ]);
217
+                    return false;
218
+                }
219
+                //removing from cache is ok as it does not exist in the objectstore anyway
220
+            }
221
+            $this->getCache()->remove($path);
222
+            return true;
223
+        }
224
+        return false;
225
+    }
226
+
227
+    public function stat($path) {
228
+        $path = $this->normalizePath($path);
229
+        $cacheEntry = $this->getCache()->get($path);
230
+        if ($cacheEntry instanceof CacheEntry) {
231
+            return $cacheEntry->getData();
232
+        } else {
233
+            return false;
234
+        }
235
+    }
236
+
237
+    public function getPermissions($path) {
238
+        $stat = $this->stat($path);
239
+
240
+        if (is_array($stat) && isset($stat['permissions'])) {
241
+            return $stat['permissions'];
242
+        }
243
+
244
+        return parent::getPermissions($path);
245
+    }
246
+
247
+    /**
248
+     * Override this method if you need a different unique resource identifier for your object storage implementation.
249
+     * The default implementations just appends the fileId to 'urn:oid:'. Make sure the URN is unique over all users.
250
+     * You may need a mapping table to store your URN if it cannot be generated from the fileid.
251
+     *
252
+     * @param int $fileId the fileid
253
+     * @return null|string the unified resource name used to identify the object
254
+     */
255
+    public function getURN($fileId) {
256
+        if (is_numeric($fileId)) {
257
+            return $this->objectPrefix . $fileId;
258
+        }
259
+        return null;
260
+    }
261
+
262
+    public function opendir($path) {
263
+        $path = $this->normalizePath($path);
264
+
265
+        try {
266
+            $files = [];
267
+            $folderContents = $this->getCache()->getFolderContents($path);
268
+            foreach ($folderContents as $file) {
269
+                $files[] = $file['name'];
270
+            }
271
+
272
+            return IteratorDirectory::wrap($files);
273
+        } catch (\Exception $e) {
274
+            $this->logger->logException($e);
275
+            return false;
276
+        }
277
+    }
278
+
279
+    public function filetype($path) {
280
+        $path = $this->normalizePath($path);
281
+        $stat = $this->stat($path);
282
+        if ($stat) {
283
+            if ($stat['mimetype'] === 'httpd/unix-directory') {
284
+                return 'dir';
285
+            }
286
+            return 'file';
287
+        } else {
288
+            return false;
289
+        }
290
+    }
291
+
292
+    public function fopen($path, $mode) {
293
+        $path = $this->normalizePath($path);
294
+
295
+        if (strrpos($path, '.') !== false) {
296
+            $ext = substr($path, strrpos($path, '.'));
297
+        } else {
298
+            $ext = '';
299
+        }
300
+
301
+        switch ($mode) {
302
+            case 'r':
303
+            case 'rb':
304
+                $stat = $this->stat($path);
305
+                if (is_array($stat)) {
306
+                    // Reading 0 sized files is a waste of time
307
+                    if (isset($stat['size']) && $stat['size'] === 0) {
308
+                        return fopen('php://memory', $mode);
309
+                    }
310
+
311
+                    try {
312
+                        return $this->objectStore->readObject($this->getURN($stat['fileid']));
313
+                    } catch (NotFoundException $e) {
314
+                        $this->logger->logException($e, [
315
+                            'app' => 'objectstore',
316
+                            'message' => 'Could not get object ' . $this->getURN($stat['fileid']) . ' for file ' . $path,
317
+                        ]);
318
+                        throw $e;
319
+                    } catch (\Exception $ex) {
320
+                        $this->logger->logException($ex, [
321
+                            'app' => 'objectstore',
322
+                            'message' => 'Could not get object ' . $this->getURN($stat['fileid']) . ' for file ' . $path,
323
+                        ]);
324
+                        return false;
325
+                    }
326
+                } else {
327
+                    return false;
328
+                }
329
+            // no break
330
+            case 'w':
331
+            case 'wb':
332
+            case 'w+':
333
+            case 'wb+':
334
+                $tmpFile = \OC::$server->getTempManager()->getTemporaryFile($ext);
335
+                $handle = fopen($tmpFile, $mode);
336
+                return CallbackWrapper::wrap($handle, null, null, function () use ($path, $tmpFile) {
337
+                    $this->writeBack($tmpFile, $path);
338
+                });
339
+            case 'a':
340
+            case 'ab':
341
+            case 'r+':
342
+            case 'a+':
343
+            case 'x':
344
+            case 'x+':
345
+            case 'c':
346
+            case 'c+':
347
+                $tmpFile = \OC::$server->getTempManager()->getTemporaryFile($ext);
348
+                if ($this->file_exists($path)) {
349
+                    $source = $this->fopen($path, 'r');
350
+                    file_put_contents($tmpFile, $source);
351
+                }
352
+                $handle = fopen($tmpFile, $mode);
353
+                return CallbackWrapper::wrap($handle, null, null, function () use ($path, $tmpFile) {
354
+                    $this->writeBack($tmpFile, $path);
355
+                });
356
+        }
357
+        return false;
358
+    }
359
+
360
+    public function file_exists($path) {
361
+        $path = $this->normalizePath($path);
362
+        return (bool)$this->stat($path);
363
+    }
364
+
365
+    public function rename($source, $target) {
366
+        $source = $this->normalizePath($source);
367
+        $target = $this->normalizePath($target);
368
+        $this->remove($target);
369
+        $this->getCache()->move($source, $target);
370
+        $this->touch(dirname($target));
371
+        return true;
372
+    }
373
+
374
+    public function getMimeType($path) {
375
+        $path = $this->normalizePath($path);
376
+        return parent::getMimeType($path);
377
+    }
378
+
379
+    public function touch($path, $mtime = null) {
380
+        if (is_null($mtime)) {
381
+            $mtime = time();
382
+        }
383
+
384
+        $path = $this->normalizePath($path);
385
+        $dirName = dirname($path);
386
+        $parentExists = $this->is_dir($dirName);
387
+        if (!$parentExists) {
388
+            return false;
389
+        }
390
+
391
+        $stat = $this->stat($path);
392
+        if (is_array($stat)) {
393
+            // update existing mtime in db
394
+            $stat['mtime'] = $mtime;
395
+            $this->getCache()->update($stat['fileid'], $stat);
396
+        } else {
397
+            try {
398
+                //create a empty file, need to have at least on char to make it
399
+                // work with all object storage implementations
400
+                $this->file_put_contents($path, ' ');
401
+                $mimeType = \OC::$server->getMimeTypeDetector()->detectPath($path);
402
+                $stat = [
403
+                    'etag' => $this->getETag($path),
404
+                    'mimetype' => $mimeType,
405
+                    'size' => 0,
406
+                    'mtime' => $mtime,
407
+                    'storage_mtime' => $mtime,
408
+                    'permissions' => \OCP\Constants::PERMISSION_ALL - \OCP\Constants::PERMISSION_CREATE,
409
+                ];
410
+                $this->getCache()->put($path, $stat);
411
+            } catch (\Exception $ex) {
412
+                $this->logger->logException($ex, [
413
+                    'app' => 'objectstore',
414
+                    'message' => 'Could not create object for ' . $path,
415
+                ]);
416
+                throw $ex;
417
+            }
418
+        }
419
+        return true;
420
+    }
421
+
422
+    public function writeBack($tmpFile, $path) {
423
+        $size = filesize($tmpFile);
424
+        $this->writeStream($path, fopen($tmpFile, 'r'), $size);
425
+    }
426
+
427
+    /**
428
+     * external changes are not supported, exclusive access to the object storage is assumed
429
+     *
430
+     * @param string $path
431
+     * @param int $time
432
+     * @return false
433
+     */
434
+    public function hasUpdated($path, $time) {
435
+        return false;
436
+    }
437
+
438
+    public function needsPartFile() {
439
+        return false;
440
+    }
441
+
442
+    public function file_put_contents($path, $data) {
443
+        $handle = $this->fopen($path, 'w+');
444
+        $result = fwrite($handle, $data);
445
+        fclose($handle);
446
+        return $result;
447
+    }
448
+
449
+    public function writeStream(string $path, $stream, int $size = null): int {
450
+        $stat = $this->stat($path);
451
+        if (empty($stat)) {
452
+            // create new file
453
+            $stat = [
454
+                'permissions' => \OCP\Constants::PERMISSION_ALL - \OCP\Constants::PERMISSION_CREATE,
455
+            ];
456
+        }
457
+        // update stat with new data
458
+        $mTime = time();
459
+        $stat['size'] = (int)$size;
460
+        $stat['mtime'] = $mTime;
461
+        $stat['storage_mtime'] = $mTime;
462
+
463
+        $mimetypeDetector = \OC::$server->getMimeTypeDetector();
464
+        $mimetype = $mimetypeDetector->detectPath($path);
465
+
466
+        $stat['mimetype'] = $mimetype;
467
+        $stat['etag'] = $this->getETag($path);
468
+
469
+        $exists = $this->getCache()->inCache($path);
470
+        $uploadPath = $exists ? $path : $path . '.part';
471
+
472
+        if ($exists) {
473
+            $fileId = $stat['fileid'];
474
+        } else {
475
+            $fileId = $this->getCache()->put($uploadPath, $stat);
476
+        }
477
+
478
+        $urn = $this->getURN($fileId);
479
+        try {
480
+            //upload to object storage
481
+            if ($size === null) {
482
+                $countStream = CountWrapper::wrap($stream, function ($writtenSize) use ($fileId, &$size) {
483
+                    $this->getCache()->update($fileId, [
484
+                        'size' => $writtenSize,
485
+                    ]);
486
+                    $size = $writtenSize;
487
+                });
488
+                $this->objectStore->writeObject($urn, $countStream);
489
+                if (is_resource($countStream)) {
490
+                    fclose($countStream);
491
+                }
492
+                $stat['size'] = $size;
493
+            } else {
494
+                $this->objectStore->writeObject($urn, $stream);
495
+            }
496
+        } catch (\Exception $ex) {
497
+            if (!$exists) {
498
+                /*
499 499
 				 * Only remove the entry if we are dealing with a new file.
500 500
 				 * Else people lose access to existing files
501 501
 				 */
502
-				$this->getCache()->remove($uploadPath);
503
-				$this->logger->logException($ex, [
504
-					'app' => 'objectstore',
505
-					'message' => 'Could not create object ' . $urn . ' for ' . $path,
506
-				]);
507
-			} else {
508
-				$this->logger->logException($ex, [
509
-					'app' => 'objectstore',
510
-					'message' => 'Could not update object ' . $urn . ' for ' . $path,
511
-				]);
512
-			}
513
-			throw $ex; // make this bubble up
514
-		}
515
-
516
-		if ($exists) {
517
-			$this->getCache()->update($fileId, $stat);
518
-		} else {
519
-			if ($this->objectStore->objectExists($urn)) {
520
-				$this->getCache()->move($uploadPath, $path);
521
-			} else {
522
-				$this->getCache()->remove($uploadPath);
523
-				throw new \Exception("Object not found after writing (urn: $urn, path: $path)", 404);
524
-			}
525
-		}
526
-
527
-		return $size;
528
-	}
529
-
530
-	public function getObjectStore(): IObjectStore {
531
-		return $this->objectStore;
532
-	}
533
-
534
-	public function copyFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath, $preserveMtime = false) {
535
-		if ($sourceStorage->instanceOfStorage(ObjectStoreStorage::class)) {
536
-			/** @var ObjectStoreStorage $sourceStorage */
537
-			if ($sourceStorage->getObjectStore()->getStorageId() === $this->getObjectStore()->getStorageId()) {
538
-				$sourceEntry = $sourceStorage->getCache()->get($sourceInternalPath);
539
-				$this->copyInner($sourceEntry, $targetInternalPath);
540
-				return true;
541
-			}
542
-		}
543
-
544
-		return parent::copyFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
545
-	}
546
-
547
-	public function copy($path1, $path2) {
548
-		$path1 = $this->normalizePath($path1);
549
-		$path2 = $this->normalizePath($path2);
550
-
551
-		$cache = $this->getCache();
552
-		$sourceEntry = $cache->get($path1);
553
-		if (!$sourceEntry) {
554
-			throw new NotFoundException('Source object not found');
555
-		}
556
-
557
-		$this->copyInner($sourceEntry, $path2);
558
-
559
-		return true;
560
-	}
561
-
562
-	private function copyInner(ICacheEntry $sourceEntry, string $to) {
563
-		$cache = $this->getCache();
564
-
565
-		if ($sourceEntry->getMimeType() === FileInfo::MIMETYPE_FOLDER) {
566
-			if ($cache->inCache($to)) {
567
-				$cache->remove($to);
568
-			}
569
-			$this->mkdir($to);
570
-
571
-			foreach ($cache->getFolderContentsById($sourceEntry->getId()) as $child) {
572
-				$this->copyInner($child, $to . '/' . $child->getName());
573
-			}
574
-		} else {
575
-			$this->copyFile($sourceEntry, $to);
576
-		}
577
-	}
578
-
579
-	private function copyFile(ICacheEntry $sourceEntry, string $to) {
580
-		$cache = $this->getCache();
581
-
582
-		$sourceUrn = $this->getURN($sourceEntry->getId());
583
-
584
-		$cache->copyFromCache($cache, $sourceEntry, $to);
585
-		$targetEntry = $cache->get($to);
586
-
587
-		if (!$targetEntry) {
588
-			throw new \Exception('Target not in cache after copy');
589
-		}
590
-
591
-		$targetUrn = $this->getURN($targetEntry->getId());
592
-
593
-		try {
594
-			$this->objectStore->copyObject($sourceUrn, $targetUrn);
595
-		} catch (\Exception $e) {
596
-			$cache->remove($to);
597
-
598
-			throw $e;
599
-		}
600
-	}
502
+                $this->getCache()->remove($uploadPath);
503
+                $this->logger->logException($ex, [
504
+                    'app' => 'objectstore',
505
+                    'message' => 'Could not create object ' . $urn . ' for ' . $path,
506
+                ]);
507
+            } else {
508
+                $this->logger->logException($ex, [
509
+                    'app' => 'objectstore',
510
+                    'message' => 'Could not update object ' . $urn . ' for ' . $path,
511
+                ]);
512
+            }
513
+            throw $ex; // make this bubble up
514
+        }
515
+
516
+        if ($exists) {
517
+            $this->getCache()->update($fileId, $stat);
518
+        } else {
519
+            if ($this->objectStore->objectExists($urn)) {
520
+                $this->getCache()->move($uploadPath, $path);
521
+            } else {
522
+                $this->getCache()->remove($uploadPath);
523
+                throw new \Exception("Object not found after writing (urn: $urn, path: $path)", 404);
524
+            }
525
+        }
526
+
527
+        return $size;
528
+    }
529
+
530
+    public function getObjectStore(): IObjectStore {
531
+        return $this->objectStore;
532
+    }
533
+
534
+    public function copyFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath, $preserveMtime = false) {
535
+        if ($sourceStorage->instanceOfStorage(ObjectStoreStorage::class)) {
536
+            /** @var ObjectStoreStorage $sourceStorage */
537
+            if ($sourceStorage->getObjectStore()->getStorageId() === $this->getObjectStore()->getStorageId()) {
538
+                $sourceEntry = $sourceStorage->getCache()->get($sourceInternalPath);
539
+                $this->copyInner($sourceEntry, $targetInternalPath);
540
+                return true;
541
+            }
542
+        }
543
+
544
+        return parent::copyFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
545
+    }
546
+
547
+    public function copy($path1, $path2) {
548
+        $path1 = $this->normalizePath($path1);
549
+        $path2 = $this->normalizePath($path2);
550
+
551
+        $cache = $this->getCache();
552
+        $sourceEntry = $cache->get($path1);
553
+        if (!$sourceEntry) {
554
+            throw new NotFoundException('Source object not found');
555
+        }
556
+
557
+        $this->copyInner($sourceEntry, $path2);
558
+
559
+        return true;
560
+    }
561
+
562
+    private function copyInner(ICacheEntry $sourceEntry, string $to) {
563
+        $cache = $this->getCache();
564
+
565
+        if ($sourceEntry->getMimeType() === FileInfo::MIMETYPE_FOLDER) {
566
+            if ($cache->inCache($to)) {
567
+                $cache->remove($to);
568
+            }
569
+            $this->mkdir($to);
570
+
571
+            foreach ($cache->getFolderContentsById($sourceEntry->getId()) as $child) {
572
+                $this->copyInner($child, $to . '/' . $child->getName());
573
+            }
574
+        } else {
575
+            $this->copyFile($sourceEntry, $to);
576
+        }
577
+    }
578
+
579
+    private function copyFile(ICacheEntry $sourceEntry, string $to) {
580
+        $cache = $this->getCache();
581
+
582
+        $sourceUrn = $this->getURN($sourceEntry->getId());
583
+
584
+        $cache->copyFromCache($cache, $sourceEntry, $to);
585
+        $targetEntry = $cache->get($to);
586
+
587
+        if (!$targetEntry) {
588
+            throw new \Exception('Target not in cache after copy');
589
+        }
590
+
591
+        $targetUrn = $this->getURN($targetEntry->getId());
592
+
593
+        try {
594
+            $this->objectStore->copyObject($sourceUrn, $targetUrn);
595
+        } catch (\Exception $e) {
596
+            $cache->remove($to);
597
+
598
+            throw $e;
599
+        }
600
+    }
601 601
 }
Please login to merge, or discard this patch.
lib/private/Files/Storage/Wrapper/Jail.php 1 patch
Indentation   +496 added lines, -496 removed lines patch added patch discarded remove patch
@@ -42,500 +42,500 @@
 block discarded – undo
42 42
  * This restricts access to a subfolder of the wrapped storage with the subfolder becoming the root folder new storage
43 43
  */
44 44
 class Jail extends Wrapper {
45
-	/**
46
-	 * @var string
47
-	 */
48
-	protected $rootPath;
49
-
50
-	/**
51
-	 * @param array $arguments ['storage' => $storage, 'root' => $root]
52
-	 *
53
-	 * $storage: The storage that will be wrapper
54
-	 * $root: The folder in the wrapped storage that will become the root folder of the wrapped storage
55
-	 */
56
-	public function __construct($arguments) {
57
-		parent::__construct($arguments);
58
-		$this->rootPath = $arguments['root'];
59
-	}
60
-
61
-	public function getUnjailedPath($path) {
62
-		return trim(Filesystem::normalizePath($this->rootPath . '/' . $path), '/');
63
-	}
64
-
65
-	/**
66
-	 * This is separate from Wrapper::getWrapperStorage so we can get the jailed storage consistently even if the jail is inside another wrapper
67
-	 */
68
-	public function getUnjailedStorage() {
69
-		return $this->storage;
70
-	}
71
-
72
-
73
-	public function getJailedPath($path) {
74
-		$root = rtrim($this->rootPath, '/') . '/';
75
-
76
-		if ($path !== $this->rootPath && strpos($path, $root) !== 0) {
77
-			return null;
78
-		} else {
79
-			$path = substr($path, strlen($this->rootPath));
80
-			return trim($path, '/');
81
-		}
82
-	}
83
-
84
-	public function getId() {
85
-		return parent::getId();
86
-	}
87
-
88
-	/**
89
-	 * see https://www.php.net/manual/en/function.mkdir.php
90
-	 *
91
-	 * @param string $path
92
-	 * @return bool
93
-	 */
94
-	public function mkdir($path) {
95
-		return $this->getWrapperStorage()->mkdir($this->getUnjailedPath($path));
96
-	}
97
-
98
-	/**
99
-	 * see https://www.php.net/manual/en/function.rmdir.php
100
-	 *
101
-	 * @param string $path
102
-	 * @return bool
103
-	 */
104
-	public function rmdir($path) {
105
-		return $this->getWrapperStorage()->rmdir($this->getUnjailedPath($path));
106
-	}
107
-
108
-	/**
109
-	 * see https://www.php.net/manual/en/function.opendir.php
110
-	 *
111
-	 * @param string $path
112
-	 * @return resource|bool
113
-	 */
114
-	public function opendir($path) {
115
-		return $this->getWrapperStorage()->opendir($this->getUnjailedPath($path));
116
-	}
117
-
118
-	/**
119
-	 * see https://www.php.net/manual/en/function.is_dir.php
120
-	 *
121
-	 * @param string $path
122
-	 * @return bool
123
-	 */
124
-	public function is_dir($path) {
125
-		return $this->getWrapperStorage()->is_dir($this->getUnjailedPath($path));
126
-	}
127
-
128
-	/**
129
-	 * see https://www.php.net/manual/en/function.is_file.php
130
-	 *
131
-	 * @param string $path
132
-	 * @return bool
133
-	 */
134
-	public function is_file($path) {
135
-		return $this->getWrapperStorage()->is_file($this->getUnjailedPath($path));
136
-	}
137
-
138
-	/**
139
-	 * see https://www.php.net/manual/en/function.stat.php
140
-	 * only the following keys are required in the result: size and mtime
141
-	 *
142
-	 * @param string $path
143
-	 * @return array|bool
144
-	 */
145
-	public function stat($path) {
146
-		return $this->getWrapperStorage()->stat($this->getUnjailedPath($path));
147
-	}
148
-
149
-	/**
150
-	 * see https://www.php.net/manual/en/function.filetype.php
151
-	 *
152
-	 * @param string $path
153
-	 * @return bool
154
-	 */
155
-	public function filetype($path) {
156
-		return $this->getWrapperStorage()->filetype($this->getUnjailedPath($path));
157
-	}
158
-
159
-	/**
160
-	 * see https://www.php.net/manual/en/function.filesize.php
161
-	 * The result for filesize when called on a folder is required to be 0
162
-	 *
163
-	 * @param string $path
164
-	 * @return int|bool
165
-	 */
166
-	public function filesize($path) {
167
-		return $this->getWrapperStorage()->filesize($this->getUnjailedPath($path));
168
-	}
169
-
170
-	/**
171
-	 * check if a file can be created in $path
172
-	 *
173
-	 * @param string $path
174
-	 * @return bool
175
-	 */
176
-	public function isCreatable($path) {
177
-		return $this->getWrapperStorage()->isCreatable($this->getUnjailedPath($path));
178
-	}
179
-
180
-	/**
181
-	 * check if a file can be read
182
-	 *
183
-	 * @param string $path
184
-	 * @return bool
185
-	 */
186
-	public function isReadable($path) {
187
-		return $this->getWrapperStorage()->isReadable($this->getUnjailedPath($path));
188
-	}
189
-
190
-	/**
191
-	 * check if a file can be written to
192
-	 *
193
-	 * @param string $path
194
-	 * @return bool
195
-	 */
196
-	public function isUpdatable($path) {
197
-		return $this->getWrapperStorage()->isUpdatable($this->getUnjailedPath($path));
198
-	}
199
-
200
-	/**
201
-	 * check if a file can be deleted
202
-	 *
203
-	 * @param string $path
204
-	 * @return bool
205
-	 */
206
-	public function isDeletable($path) {
207
-		return $this->getWrapperStorage()->isDeletable($this->getUnjailedPath($path));
208
-	}
209
-
210
-	/**
211
-	 * check if a file can be shared
212
-	 *
213
-	 * @param string $path
214
-	 * @return bool
215
-	 */
216
-	public function isSharable($path) {
217
-		return $this->getWrapperStorage()->isSharable($this->getUnjailedPath($path));
218
-	}
219
-
220
-	/**
221
-	 * get the full permissions of a path.
222
-	 * Should return a combination of the PERMISSION_ constants defined in lib/public/constants.php
223
-	 *
224
-	 * @param string $path
225
-	 * @return int
226
-	 */
227
-	public function getPermissions($path) {
228
-		return $this->getWrapperStorage()->getPermissions($this->getUnjailedPath($path));
229
-	}
230
-
231
-	/**
232
-	 * see https://www.php.net/manual/en/function.file_exists.php
233
-	 *
234
-	 * @param string $path
235
-	 * @return bool
236
-	 */
237
-	public function file_exists($path) {
238
-		return $this->getWrapperStorage()->file_exists($this->getUnjailedPath($path));
239
-	}
240
-
241
-	/**
242
-	 * see https://www.php.net/manual/en/function.filemtime.php
243
-	 *
244
-	 * @param string $path
245
-	 * @return int|bool
246
-	 */
247
-	public function filemtime($path) {
248
-		return $this->getWrapperStorage()->filemtime($this->getUnjailedPath($path));
249
-	}
250
-
251
-	/**
252
-	 * see https://www.php.net/manual/en/function.file_get_contents.php
253
-	 *
254
-	 * @param string $path
255
-	 * @return string|bool
256
-	 */
257
-	public function file_get_contents($path) {
258
-		return $this->getWrapperStorage()->file_get_contents($this->getUnjailedPath($path));
259
-	}
260
-
261
-	/**
262
-	 * see https://www.php.net/manual/en/function.file_put_contents.php
263
-	 *
264
-	 * @param string $path
265
-	 * @param mixed $data
266
-	 * @return int|false
267
-	 */
268
-	public function file_put_contents($path, $data) {
269
-		return $this->getWrapperStorage()->file_put_contents($this->getUnjailedPath($path), $data);
270
-	}
271
-
272
-	/**
273
-	 * see https://www.php.net/manual/en/function.unlink.php
274
-	 *
275
-	 * @param string $path
276
-	 * @return bool
277
-	 */
278
-	public function unlink($path) {
279
-		return $this->getWrapperStorage()->unlink($this->getUnjailedPath($path));
280
-	}
281
-
282
-	/**
283
-	 * see https://www.php.net/manual/en/function.rename.php
284
-	 *
285
-	 * @param string $path1
286
-	 * @param string $path2
287
-	 * @return bool
288
-	 */
289
-	public function rename($path1, $path2) {
290
-		return $this->getWrapperStorage()->rename($this->getUnjailedPath($path1), $this->getUnjailedPath($path2));
291
-	}
292
-
293
-	/**
294
-	 * see https://www.php.net/manual/en/function.copy.php
295
-	 *
296
-	 * @param string $path1
297
-	 * @param string $path2
298
-	 * @return bool
299
-	 */
300
-	public function copy($path1, $path2) {
301
-		return $this->getWrapperStorage()->copy($this->getUnjailedPath($path1), $this->getUnjailedPath($path2));
302
-	}
303
-
304
-	/**
305
-	 * see https://www.php.net/manual/en/function.fopen.php
306
-	 *
307
-	 * @param string $path
308
-	 * @param string $mode
309
-	 * @return resource|bool
310
-	 */
311
-	public function fopen($path, $mode) {
312
-		return $this->getWrapperStorage()->fopen($this->getUnjailedPath($path), $mode);
313
-	}
314
-
315
-	/**
316
-	 * get the mimetype for a file or folder
317
-	 * The mimetype for a folder is required to be "httpd/unix-directory"
318
-	 *
319
-	 * @param string $path
320
-	 * @return string|bool
321
-	 */
322
-	public function getMimeType($path) {
323
-		return $this->getWrapperStorage()->getMimeType($this->getUnjailedPath($path));
324
-	}
325
-
326
-	/**
327
-	 * see https://www.php.net/manual/en/function.hash.php
328
-	 *
329
-	 * @param string $type
330
-	 * @param string $path
331
-	 * @param bool $raw
332
-	 * @return string|bool
333
-	 */
334
-	public function hash($type, $path, $raw = false) {
335
-		return $this->getWrapperStorage()->hash($type, $this->getUnjailedPath($path), $raw);
336
-	}
337
-
338
-	/**
339
-	 * see https://www.php.net/manual/en/function.free_space.php
340
-	 *
341
-	 * @param string $path
342
-	 * @return int|bool
343
-	 */
344
-	public function free_space($path) {
345
-		return $this->getWrapperStorage()->free_space($this->getUnjailedPath($path));
346
-	}
347
-
348
-	/**
349
-	 * search for occurrences of $query in file names
350
-	 *
351
-	 * @param string $query
352
-	 * @return array|bool
353
-	 */
354
-	public function search($query) {
355
-		return $this->getWrapperStorage()->search($query);
356
-	}
357
-
358
-	/**
359
-	 * see https://www.php.net/manual/en/function.touch.php
360
-	 * If the backend does not support the operation, false should be returned
361
-	 *
362
-	 * @param string $path
363
-	 * @param int $mtime
364
-	 * @return bool
365
-	 */
366
-	public function touch($path, $mtime = null) {
367
-		return $this->getWrapperStorage()->touch($this->getUnjailedPath($path), $mtime);
368
-	}
369
-
370
-	/**
371
-	 * get the path to a local version of the file.
372
-	 * The local version of the file can be temporary and doesn't have to be persistent across requests
373
-	 *
374
-	 * @param string $path
375
-	 * @return string|bool
376
-	 */
377
-	public function getLocalFile($path) {
378
-		return $this->getWrapperStorage()->getLocalFile($this->getUnjailedPath($path));
379
-	}
380
-
381
-	/**
382
-	 * check if a file or folder has been updated since $time
383
-	 *
384
-	 * @param string $path
385
-	 * @param int $time
386
-	 * @return bool
387
-	 *
388
-	 * hasUpdated for folders should return at least true if a file inside the folder is add, removed or renamed.
389
-	 * returning true for other changes in the folder is optional
390
-	 */
391
-	public function hasUpdated($path, $time) {
392
-		return $this->getWrapperStorage()->hasUpdated($this->getUnjailedPath($path), $time);
393
-	}
394
-
395
-	/**
396
-	 * get a cache instance for the storage
397
-	 *
398
-	 * @param string $path
399
-	 * @param \OC\Files\Storage\Storage (optional) the storage to pass to the cache
400
-	 * @return \OC\Files\Cache\Cache
401
-	 */
402
-	public function getCache($path = '', $storage = null) {
403
-		if (!$storage) {
404
-			$storage = $this->getWrapperStorage();
405
-		}
406
-		$sourceCache = $this->getWrapperStorage()->getCache($this->getUnjailedPath($path), $storage);
407
-		return new CacheJail($sourceCache, $this->rootPath);
408
-	}
409
-
410
-	/**
411
-	 * get the user id of the owner of a file or folder
412
-	 *
413
-	 * @param string $path
414
-	 * @return string
415
-	 */
416
-	public function getOwner($path) {
417
-		return $this->getWrapperStorage()->getOwner($this->getUnjailedPath($path));
418
-	}
419
-
420
-	/**
421
-	 * get a watcher instance for the cache
422
-	 *
423
-	 * @param string $path
424
-	 * @param \OC\Files\Storage\Storage (optional) the storage to pass to the watcher
425
-	 * @return \OC\Files\Cache\Watcher
426
-	 */
427
-	public function getWatcher($path = '', $storage = null) {
428
-		if (!$storage) {
429
-			$storage = $this;
430
-		}
431
-		return $this->getWrapperStorage()->getWatcher($this->getUnjailedPath($path), $storage);
432
-	}
433
-
434
-	/**
435
-	 * get the ETag for a file or folder
436
-	 *
437
-	 * @param string $path
438
-	 * @return string|bool
439
-	 */
440
-	public function getETag($path) {
441
-		return $this->getWrapperStorage()->getETag($this->getUnjailedPath($path));
442
-	}
443
-
444
-	public function getMetaData($path) {
445
-		return $this->getWrapperStorage()->getMetaData($this->getUnjailedPath($path));
446
-	}
447
-
448
-	/**
449
-	 * @param string $path
450
-	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
451
-	 * @param \OCP\Lock\ILockingProvider $provider
452
-	 * @throws \OCP\Lock\LockedException
453
-	 */
454
-	public function acquireLock($path, $type, ILockingProvider $provider) {
455
-		$this->getWrapperStorage()->acquireLock($this->getUnjailedPath($path), $type, $provider);
456
-	}
457
-
458
-	/**
459
-	 * @param string $path
460
-	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
461
-	 * @param \OCP\Lock\ILockingProvider $provider
462
-	 */
463
-	public function releaseLock($path, $type, ILockingProvider $provider) {
464
-		$this->getWrapperStorage()->releaseLock($this->getUnjailedPath($path), $type, $provider);
465
-	}
466
-
467
-	/**
468
-	 * @param string $path
469
-	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
470
-	 * @param \OCP\Lock\ILockingProvider $provider
471
-	 */
472
-	public function changeLock($path, $type, ILockingProvider $provider) {
473
-		$this->getWrapperStorage()->changeLock($this->getUnjailedPath($path), $type, $provider);
474
-	}
475
-
476
-	/**
477
-	 * Resolve the path for the source of the share
478
-	 *
479
-	 * @param string $path
480
-	 * @return array
481
-	 */
482
-	public function resolvePath($path) {
483
-		return [$this->getWrapperStorage(), $this->getUnjailedPath($path)];
484
-	}
485
-
486
-	/**
487
-	 * @param IStorage $sourceStorage
488
-	 * @param string $sourceInternalPath
489
-	 * @param string $targetInternalPath
490
-	 * @return bool
491
-	 */
492
-	public function copyFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) {
493
-		if ($sourceStorage === $this) {
494
-			return $this->copy($sourceInternalPath, $targetInternalPath);
495
-		}
496
-		return $this->getWrapperStorage()->copyFromStorage($sourceStorage, $sourceInternalPath, $this->getUnjailedPath($targetInternalPath));
497
-	}
498
-
499
-	/**
500
-	 * @param IStorage $sourceStorage
501
-	 * @param string $sourceInternalPath
502
-	 * @param string $targetInternalPath
503
-	 * @return bool
504
-	 */
505
-	public function moveFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) {
506
-		if ($sourceStorage === $this) {
507
-			return $this->rename($sourceInternalPath, $targetInternalPath);
508
-		}
509
-		return $this->getWrapperStorage()->moveFromStorage($sourceStorage, $sourceInternalPath, $this->getUnjailedPath($targetInternalPath));
510
-	}
511
-
512
-	public function getPropagator($storage = null) {
513
-		if (isset($this->propagator)) {
514
-			return $this->propagator;
515
-		}
516
-
517
-		if (!$storage) {
518
-			$storage = $this;
519
-		}
520
-		$this->propagator = new JailPropagator($storage, \OC::$server->getDatabaseConnection());
521
-		return $this->propagator;
522
-	}
523
-
524
-	public function writeStream(string $path, $stream, int $size = null): int {
525
-		$storage = $this->getWrapperStorage();
526
-		if ($storage->instanceOfStorage(IWriteStreamStorage::class)) {
527
-			/** @var IWriteStreamStorage $storage */
528
-			return $storage->writeStream($this->getUnjailedPath($path), $stream, $size);
529
-		} else {
530
-			$target = $this->fopen($path, 'w');
531
-			[$count, $result] = \OC_Helper::streamCopy($stream, $target);
532
-			fclose($stream);
533
-			fclose($target);
534
-			return $count;
535
-		}
536
-	}
537
-
538
-	public function getDirectoryContent($directory): \Traversable {
539
-		return $this->getWrapperStorage()->getDirectoryContent($this->getUnjailedPath($directory));
540
-	}
45
+    /**
46
+     * @var string
47
+     */
48
+    protected $rootPath;
49
+
50
+    /**
51
+     * @param array $arguments ['storage' => $storage, 'root' => $root]
52
+     *
53
+     * $storage: The storage that will be wrapper
54
+     * $root: The folder in the wrapped storage that will become the root folder of the wrapped storage
55
+     */
56
+    public function __construct($arguments) {
57
+        parent::__construct($arguments);
58
+        $this->rootPath = $arguments['root'];
59
+    }
60
+
61
+    public function getUnjailedPath($path) {
62
+        return trim(Filesystem::normalizePath($this->rootPath . '/' . $path), '/');
63
+    }
64
+
65
+    /**
66
+     * This is separate from Wrapper::getWrapperStorage so we can get the jailed storage consistently even if the jail is inside another wrapper
67
+     */
68
+    public function getUnjailedStorage() {
69
+        return $this->storage;
70
+    }
71
+
72
+
73
+    public function getJailedPath($path) {
74
+        $root = rtrim($this->rootPath, '/') . '/';
75
+
76
+        if ($path !== $this->rootPath && strpos($path, $root) !== 0) {
77
+            return null;
78
+        } else {
79
+            $path = substr($path, strlen($this->rootPath));
80
+            return trim($path, '/');
81
+        }
82
+    }
83
+
84
+    public function getId() {
85
+        return parent::getId();
86
+    }
87
+
88
+    /**
89
+     * see https://www.php.net/manual/en/function.mkdir.php
90
+     *
91
+     * @param string $path
92
+     * @return bool
93
+     */
94
+    public function mkdir($path) {
95
+        return $this->getWrapperStorage()->mkdir($this->getUnjailedPath($path));
96
+    }
97
+
98
+    /**
99
+     * see https://www.php.net/manual/en/function.rmdir.php
100
+     *
101
+     * @param string $path
102
+     * @return bool
103
+     */
104
+    public function rmdir($path) {
105
+        return $this->getWrapperStorage()->rmdir($this->getUnjailedPath($path));
106
+    }
107
+
108
+    /**
109
+     * see https://www.php.net/manual/en/function.opendir.php
110
+     *
111
+     * @param string $path
112
+     * @return resource|bool
113
+     */
114
+    public function opendir($path) {
115
+        return $this->getWrapperStorage()->opendir($this->getUnjailedPath($path));
116
+    }
117
+
118
+    /**
119
+     * see https://www.php.net/manual/en/function.is_dir.php
120
+     *
121
+     * @param string $path
122
+     * @return bool
123
+     */
124
+    public function is_dir($path) {
125
+        return $this->getWrapperStorage()->is_dir($this->getUnjailedPath($path));
126
+    }
127
+
128
+    /**
129
+     * see https://www.php.net/manual/en/function.is_file.php
130
+     *
131
+     * @param string $path
132
+     * @return bool
133
+     */
134
+    public function is_file($path) {
135
+        return $this->getWrapperStorage()->is_file($this->getUnjailedPath($path));
136
+    }
137
+
138
+    /**
139
+     * see https://www.php.net/manual/en/function.stat.php
140
+     * only the following keys are required in the result: size and mtime
141
+     *
142
+     * @param string $path
143
+     * @return array|bool
144
+     */
145
+    public function stat($path) {
146
+        return $this->getWrapperStorage()->stat($this->getUnjailedPath($path));
147
+    }
148
+
149
+    /**
150
+     * see https://www.php.net/manual/en/function.filetype.php
151
+     *
152
+     * @param string $path
153
+     * @return bool
154
+     */
155
+    public function filetype($path) {
156
+        return $this->getWrapperStorage()->filetype($this->getUnjailedPath($path));
157
+    }
158
+
159
+    /**
160
+     * see https://www.php.net/manual/en/function.filesize.php
161
+     * The result for filesize when called on a folder is required to be 0
162
+     *
163
+     * @param string $path
164
+     * @return int|bool
165
+     */
166
+    public function filesize($path) {
167
+        return $this->getWrapperStorage()->filesize($this->getUnjailedPath($path));
168
+    }
169
+
170
+    /**
171
+     * check if a file can be created in $path
172
+     *
173
+     * @param string $path
174
+     * @return bool
175
+     */
176
+    public function isCreatable($path) {
177
+        return $this->getWrapperStorage()->isCreatable($this->getUnjailedPath($path));
178
+    }
179
+
180
+    /**
181
+     * check if a file can be read
182
+     *
183
+     * @param string $path
184
+     * @return bool
185
+     */
186
+    public function isReadable($path) {
187
+        return $this->getWrapperStorage()->isReadable($this->getUnjailedPath($path));
188
+    }
189
+
190
+    /**
191
+     * check if a file can be written to
192
+     *
193
+     * @param string $path
194
+     * @return bool
195
+     */
196
+    public function isUpdatable($path) {
197
+        return $this->getWrapperStorage()->isUpdatable($this->getUnjailedPath($path));
198
+    }
199
+
200
+    /**
201
+     * check if a file can be deleted
202
+     *
203
+     * @param string $path
204
+     * @return bool
205
+     */
206
+    public function isDeletable($path) {
207
+        return $this->getWrapperStorage()->isDeletable($this->getUnjailedPath($path));
208
+    }
209
+
210
+    /**
211
+     * check if a file can be shared
212
+     *
213
+     * @param string $path
214
+     * @return bool
215
+     */
216
+    public function isSharable($path) {
217
+        return $this->getWrapperStorage()->isSharable($this->getUnjailedPath($path));
218
+    }
219
+
220
+    /**
221
+     * get the full permissions of a path.
222
+     * Should return a combination of the PERMISSION_ constants defined in lib/public/constants.php
223
+     *
224
+     * @param string $path
225
+     * @return int
226
+     */
227
+    public function getPermissions($path) {
228
+        return $this->getWrapperStorage()->getPermissions($this->getUnjailedPath($path));
229
+    }
230
+
231
+    /**
232
+     * see https://www.php.net/manual/en/function.file_exists.php
233
+     *
234
+     * @param string $path
235
+     * @return bool
236
+     */
237
+    public function file_exists($path) {
238
+        return $this->getWrapperStorage()->file_exists($this->getUnjailedPath($path));
239
+    }
240
+
241
+    /**
242
+     * see https://www.php.net/manual/en/function.filemtime.php
243
+     *
244
+     * @param string $path
245
+     * @return int|bool
246
+     */
247
+    public function filemtime($path) {
248
+        return $this->getWrapperStorage()->filemtime($this->getUnjailedPath($path));
249
+    }
250
+
251
+    /**
252
+     * see https://www.php.net/manual/en/function.file_get_contents.php
253
+     *
254
+     * @param string $path
255
+     * @return string|bool
256
+     */
257
+    public function file_get_contents($path) {
258
+        return $this->getWrapperStorage()->file_get_contents($this->getUnjailedPath($path));
259
+    }
260
+
261
+    /**
262
+     * see https://www.php.net/manual/en/function.file_put_contents.php
263
+     *
264
+     * @param string $path
265
+     * @param mixed $data
266
+     * @return int|false
267
+     */
268
+    public function file_put_contents($path, $data) {
269
+        return $this->getWrapperStorage()->file_put_contents($this->getUnjailedPath($path), $data);
270
+    }
271
+
272
+    /**
273
+     * see https://www.php.net/manual/en/function.unlink.php
274
+     *
275
+     * @param string $path
276
+     * @return bool
277
+     */
278
+    public function unlink($path) {
279
+        return $this->getWrapperStorage()->unlink($this->getUnjailedPath($path));
280
+    }
281
+
282
+    /**
283
+     * see https://www.php.net/manual/en/function.rename.php
284
+     *
285
+     * @param string $path1
286
+     * @param string $path2
287
+     * @return bool
288
+     */
289
+    public function rename($path1, $path2) {
290
+        return $this->getWrapperStorage()->rename($this->getUnjailedPath($path1), $this->getUnjailedPath($path2));
291
+    }
292
+
293
+    /**
294
+     * see https://www.php.net/manual/en/function.copy.php
295
+     *
296
+     * @param string $path1
297
+     * @param string $path2
298
+     * @return bool
299
+     */
300
+    public function copy($path1, $path2) {
301
+        return $this->getWrapperStorage()->copy($this->getUnjailedPath($path1), $this->getUnjailedPath($path2));
302
+    }
303
+
304
+    /**
305
+     * see https://www.php.net/manual/en/function.fopen.php
306
+     *
307
+     * @param string $path
308
+     * @param string $mode
309
+     * @return resource|bool
310
+     */
311
+    public function fopen($path, $mode) {
312
+        return $this->getWrapperStorage()->fopen($this->getUnjailedPath($path), $mode);
313
+    }
314
+
315
+    /**
316
+     * get the mimetype for a file or folder
317
+     * The mimetype for a folder is required to be "httpd/unix-directory"
318
+     *
319
+     * @param string $path
320
+     * @return string|bool
321
+     */
322
+    public function getMimeType($path) {
323
+        return $this->getWrapperStorage()->getMimeType($this->getUnjailedPath($path));
324
+    }
325
+
326
+    /**
327
+     * see https://www.php.net/manual/en/function.hash.php
328
+     *
329
+     * @param string $type
330
+     * @param string $path
331
+     * @param bool $raw
332
+     * @return string|bool
333
+     */
334
+    public function hash($type, $path, $raw = false) {
335
+        return $this->getWrapperStorage()->hash($type, $this->getUnjailedPath($path), $raw);
336
+    }
337
+
338
+    /**
339
+     * see https://www.php.net/manual/en/function.free_space.php
340
+     *
341
+     * @param string $path
342
+     * @return int|bool
343
+     */
344
+    public function free_space($path) {
345
+        return $this->getWrapperStorage()->free_space($this->getUnjailedPath($path));
346
+    }
347
+
348
+    /**
349
+     * search for occurrences of $query in file names
350
+     *
351
+     * @param string $query
352
+     * @return array|bool
353
+     */
354
+    public function search($query) {
355
+        return $this->getWrapperStorage()->search($query);
356
+    }
357
+
358
+    /**
359
+     * see https://www.php.net/manual/en/function.touch.php
360
+     * If the backend does not support the operation, false should be returned
361
+     *
362
+     * @param string $path
363
+     * @param int $mtime
364
+     * @return bool
365
+     */
366
+    public function touch($path, $mtime = null) {
367
+        return $this->getWrapperStorage()->touch($this->getUnjailedPath($path), $mtime);
368
+    }
369
+
370
+    /**
371
+     * get the path to a local version of the file.
372
+     * The local version of the file can be temporary and doesn't have to be persistent across requests
373
+     *
374
+     * @param string $path
375
+     * @return string|bool
376
+     */
377
+    public function getLocalFile($path) {
378
+        return $this->getWrapperStorage()->getLocalFile($this->getUnjailedPath($path));
379
+    }
380
+
381
+    /**
382
+     * check if a file or folder has been updated since $time
383
+     *
384
+     * @param string $path
385
+     * @param int $time
386
+     * @return bool
387
+     *
388
+     * hasUpdated for folders should return at least true if a file inside the folder is add, removed or renamed.
389
+     * returning true for other changes in the folder is optional
390
+     */
391
+    public function hasUpdated($path, $time) {
392
+        return $this->getWrapperStorage()->hasUpdated($this->getUnjailedPath($path), $time);
393
+    }
394
+
395
+    /**
396
+     * get a cache instance for the storage
397
+     *
398
+     * @param string $path
399
+     * @param \OC\Files\Storage\Storage (optional) the storage to pass to the cache
400
+     * @return \OC\Files\Cache\Cache
401
+     */
402
+    public function getCache($path = '', $storage = null) {
403
+        if (!$storage) {
404
+            $storage = $this->getWrapperStorage();
405
+        }
406
+        $sourceCache = $this->getWrapperStorage()->getCache($this->getUnjailedPath($path), $storage);
407
+        return new CacheJail($sourceCache, $this->rootPath);
408
+    }
409
+
410
+    /**
411
+     * get the user id of the owner of a file or folder
412
+     *
413
+     * @param string $path
414
+     * @return string
415
+     */
416
+    public function getOwner($path) {
417
+        return $this->getWrapperStorage()->getOwner($this->getUnjailedPath($path));
418
+    }
419
+
420
+    /**
421
+     * get a watcher instance for the cache
422
+     *
423
+     * @param string $path
424
+     * @param \OC\Files\Storage\Storage (optional) the storage to pass to the watcher
425
+     * @return \OC\Files\Cache\Watcher
426
+     */
427
+    public function getWatcher($path = '', $storage = null) {
428
+        if (!$storage) {
429
+            $storage = $this;
430
+        }
431
+        return $this->getWrapperStorage()->getWatcher($this->getUnjailedPath($path), $storage);
432
+    }
433
+
434
+    /**
435
+     * get the ETag for a file or folder
436
+     *
437
+     * @param string $path
438
+     * @return string|bool
439
+     */
440
+    public function getETag($path) {
441
+        return $this->getWrapperStorage()->getETag($this->getUnjailedPath($path));
442
+    }
443
+
444
+    public function getMetaData($path) {
445
+        return $this->getWrapperStorage()->getMetaData($this->getUnjailedPath($path));
446
+    }
447
+
448
+    /**
449
+     * @param string $path
450
+     * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
451
+     * @param \OCP\Lock\ILockingProvider $provider
452
+     * @throws \OCP\Lock\LockedException
453
+     */
454
+    public function acquireLock($path, $type, ILockingProvider $provider) {
455
+        $this->getWrapperStorage()->acquireLock($this->getUnjailedPath($path), $type, $provider);
456
+    }
457
+
458
+    /**
459
+     * @param string $path
460
+     * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
461
+     * @param \OCP\Lock\ILockingProvider $provider
462
+     */
463
+    public function releaseLock($path, $type, ILockingProvider $provider) {
464
+        $this->getWrapperStorage()->releaseLock($this->getUnjailedPath($path), $type, $provider);
465
+    }
466
+
467
+    /**
468
+     * @param string $path
469
+     * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
470
+     * @param \OCP\Lock\ILockingProvider $provider
471
+     */
472
+    public function changeLock($path, $type, ILockingProvider $provider) {
473
+        $this->getWrapperStorage()->changeLock($this->getUnjailedPath($path), $type, $provider);
474
+    }
475
+
476
+    /**
477
+     * Resolve the path for the source of the share
478
+     *
479
+     * @param string $path
480
+     * @return array
481
+     */
482
+    public function resolvePath($path) {
483
+        return [$this->getWrapperStorage(), $this->getUnjailedPath($path)];
484
+    }
485
+
486
+    /**
487
+     * @param IStorage $sourceStorage
488
+     * @param string $sourceInternalPath
489
+     * @param string $targetInternalPath
490
+     * @return bool
491
+     */
492
+    public function copyFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) {
493
+        if ($sourceStorage === $this) {
494
+            return $this->copy($sourceInternalPath, $targetInternalPath);
495
+        }
496
+        return $this->getWrapperStorage()->copyFromStorage($sourceStorage, $sourceInternalPath, $this->getUnjailedPath($targetInternalPath));
497
+    }
498
+
499
+    /**
500
+     * @param IStorage $sourceStorage
501
+     * @param string $sourceInternalPath
502
+     * @param string $targetInternalPath
503
+     * @return bool
504
+     */
505
+    public function moveFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) {
506
+        if ($sourceStorage === $this) {
507
+            return $this->rename($sourceInternalPath, $targetInternalPath);
508
+        }
509
+        return $this->getWrapperStorage()->moveFromStorage($sourceStorage, $sourceInternalPath, $this->getUnjailedPath($targetInternalPath));
510
+    }
511
+
512
+    public function getPropagator($storage = null) {
513
+        if (isset($this->propagator)) {
514
+            return $this->propagator;
515
+        }
516
+
517
+        if (!$storage) {
518
+            $storage = $this;
519
+        }
520
+        $this->propagator = new JailPropagator($storage, \OC::$server->getDatabaseConnection());
521
+        return $this->propagator;
522
+    }
523
+
524
+    public function writeStream(string $path, $stream, int $size = null): int {
525
+        $storage = $this->getWrapperStorage();
526
+        if ($storage->instanceOfStorage(IWriteStreamStorage::class)) {
527
+            /** @var IWriteStreamStorage $storage */
528
+            return $storage->writeStream($this->getUnjailedPath($path), $stream, $size);
529
+        } else {
530
+            $target = $this->fopen($path, 'w');
531
+            [$count, $result] = \OC_Helper::streamCopy($stream, $target);
532
+            fclose($stream);
533
+            fclose($target);
534
+            return $count;
535
+        }
536
+    }
537
+
538
+    public function getDirectoryContent($directory): \Traversable {
539
+        return $this->getWrapperStorage()->getDirectoryContent($this->getUnjailedPath($directory));
540
+    }
541 541
 }
Please login to merge, or discard this patch.