Passed
Push — master ( c104c1...e7f034 )
by Roeland
14:19 queued 02:28
created
apps/files_external/lib/Lib/Storage/AmazonS3.php 2 patches
Indentation   +642 added lines, -642 removed lines patch added patch discarded remove patch
@@ -52,646 +52,646 @@
 block discarded – undo
52 52
 use OCP\Constants;
53 53
 
54 54
 class AmazonS3 extends \OC\Files\Storage\Common {
55
-	use S3ConnectionTrait;
56
-	use S3ObjectTrait;
57
-
58
-	public function needsPartFile() {
59
-		return false;
60
-	}
61
-
62
-	/** @var CappedMemoryCache|Result[] */
63
-	private $objectCache;
64
-
65
-	/** @var CappedMemoryCache|bool[] */
66
-	private $directoryCache;
67
-
68
-	/** @var CappedMemoryCache|array */
69
-	private $filesCache;
70
-
71
-	public function __construct($parameters) {
72
-		parent::__construct($parameters);
73
-		$this->parseParams($parameters);
74
-		$this->objectCache = new CappedMemoryCache();
75
-		$this->directoryCache = new CappedMemoryCache();
76
-		$this->filesCache = new CappedMemoryCache();
77
-	}
78
-
79
-	/**
80
-	 * @param string $path
81
-	 * @return string correctly encoded path
82
-	 */
83
-	private function normalizePath($path) {
84
-		$path = trim($path, '/');
85
-
86
-		if (!$path) {
87
-			$path = '.';
88
-		}
89
-
90
-		return $path;
91
-	}
92
-
93
-	private function isRoot($path) {
94
-		return $path === '.';
95
-	}
96
-
97
-	private function cleanKey($path) {
98
-		if ($this->isRoot($path)) {
99
-			return '/';
100
-		}
101
-		return $path;
102
-	}
103
-
104
-	private function clearCache() {
105
-		$this->objectCache = new CappedMemoryCache();
106
-		$this->directoryCache = new CappedMemoryCache();
107
-		$this->filesCache = new CappedMemoryCache();
108
-	}
109
-
110
-	private function invalidateCache($key) {
111
-		unset($this->objectCache[$key]);
112
-		$keys = array_keys($this->objectCache->getData());
113
-		$keyLength = strlen($key);
114
-		foreach ($keys as $existingKey) {
115
-			if (substr($existingKey, 0, $keyLength) === $key) {
116
-				unset($this->objectCache[$existingKey]);
117
-			}
118
-		}
119
-		unset($this->directoryCache[$key], $this->filesCache[$key]);
120
-	}
121
-
122
-	/**
123
-	 * @param $key
124
-	 * @return Result|boolean
125
-	 */
126
-	private function headObject($key) {
127
-		if (!isset($this->objectCache[$key])) {
128
-			try {
129
-				$this->objectCache[$key] = $this->getConnection()->headObject([
130
-					'Bucket' => $this->bucket,
131
-					'Key' => $key
132
-				]);
133
-			} catch (S3Exception $e) {
134
-				if ($e->getStatusCode() >= 500) {
135
-					throw $e;
136
-				}
137
-				$this->objectCache[$key] = false;
138
-			}
139
-		}
140
-
141
-		return $this->objectCache[$key];
142
-	}
143
-
144
-	/**
145
-	 * Return true if directory exists
146
-	 *
147
-	 * There are no folders in s3. A folder like structure could be archived
148
-	 * by prefixing files with the folder name.
149
-	 *
150
-	 * Implementation from flysystem-aws-s3-v3:
151
-	 * https://github.com/thephpleague/flysystem-aws-s3-v3/blob/8241e9cc5b28f981e0d24cdaf9867f14c7498ae4/src/AwsS3Adapter.php#L670-L694
152
-	 *
153
-	 * @param $path
154
-	 * @return bool
155
-	 * @throws \Exception
156
-	 */
157
-	private function doesDirectoryExist($path) {
158
-		if (!isset($this->directoryCache[$path])) {
159
-			// Maybe this isn't an actual key, but a prefix.
160
-			// Do a prefix listing of objects to determine.
161
-			try {
162
-				$result = $this->getConnection()->listObjects([
163
-					'Bucket' => $this->bucket,
164
-					'Prefix' => rtrim($path, '/') . '/',
165
-					'MaxKeys' => 1,
166
-				]);
167
-				$this->directoryCache[$path] = $result['Contents'] || $result['CommonPrefixes'];
168
-			} catch (S3Exception $e) {
169
-				if ($e->getStatusCode() === 403) {
170
-					$this->directoryCache[$path] = false;
171
-				}
172
-				throw $e;
173
-			}
174
-		}
175
-
176
-		return $this->directoryCache[$path];
177
-	}
178
-
179
-	/**
180
-	 * Updates old storage ids (v0.2.1 and older) that are based on key and secret to new ones based on the bucket name.
181
-	 * TODO Do this in an update.php. requires iterating over all users and loading the mount.json from their home
182
-	 *
183
-	 * @param array $params
184
-	 */
185
-	public function updateLegacyId(array $params) {
186
-		$oldId = 'amazon::' . $params['key'] . md5($params['secret']);
187
-
188
-		// find by old id or bucket
189
-		$stmt = \OC::$server->getDatabaseConnection()->prepare(
190
-			'SELECT `numeric_id`, `id` FROM `*PREFIX*storages` WHERE `id` IN (?, ?)'
191
-		);
192
-		$stmt->execute([$oldId, $this->id]);
193
-		while ($row = $stmt->fetch()) {
194
-			$storages[$row['id']] = $row['numeric_id'];
195
-		}
196
-
197
-		if (isset($storages[$this->id]) && isset($storages[$oldId])) {
198
-			// if both ids exist, delete the old storage and corresponding filecache entries
199
-			\OC\Files\Cache\Storage::remove($oldId);
200
-		} elseif (isset($storages[$oldId])) {
201
-			// if only the old id exists do an update
202
-			$stmt = \OC::$server->getDatabaseConnection()->prepare(
203
-				'UPDATE `*PREFIX*storages` SET `id` = ? WHERE `id` = ?'
204
-			);
205
-			$stmt->execute([$this->id, $oldId]);
206
-		}
207
-		// only the bucket based id may exist, do nothing
208
-	}
209
-
210
-	/**
211
-	 * Remove a file or folder
212
-	 *
213
-	 * @param string $path
214
-	 * @return bool
215
-	 */
216
-	protected function remove($path) {
217
-		// remember fileType to reduce http calls
218
-		$fileType = $this->filetype($path);
219
-		if ($fileType === 'dir') {
220
-			return $this->rmdir($path);
221
-		} elseif ($fileType === 'file') {
222
-			return $this->unlink($path);
223
-		} else {
224
-			return false;
225
-		}
226
-	}
227
-
228
-	public function mkdir($path) {
229
-		$path = $this->normalizePath($path);
230
-
231
-		if ($this->is_dir($path)) {
232
-			return false;
233
-		}
234
-
235
-		try {
236
-			$this->getConnection()->putObject([
237
-				'Bucket' => $this->bucket,
238
-				'Key' => $path . '/',
239
-				'Body' => '',
240
-				'ContentType' => 'httpd/unix-directory'
241
-			]);
242
-			$this->testTimeout();
243
-		} catch (S3Exception $e) {
244
-			\OC::$server->getLogger()->logException($e, ['app' => 'files_external']);
245
-			return false;
246
-		}
247
-
248
-		$this->invalidateCache($path);
249
-
250
-		return true;
251
-	}
252
-
253
-	public function file_exists($path) {
254
-		return $this->filetype($path) !== false;
255
-	}
256
-
257
-
258
-	public function rmdir($path) {
259
-		$path = $this->normalizePath($path);
260
-
261
-		if ($this->isRoot($path)) {
262
-			return $this->clearBucket();
263
-		}
264
-
265
-		if (!$this->file_exists($path)) {
266
-			return false;
267
-		}
268
-
269
-		$this->invalidateCache($path);
270
-		return $this->batchDelete($path);
271
-	}
272
-
273
-	protected function clearBucket() {
274
-		$this->clearCache();
275
-		try {
276
-			$this->getConnection()->clearBucket($this->bucket);
277
-			return true;
278
-			// clearBucket() is not working with Ceph, so if it fails we try the slower approach
279
-		} catch (\Exception $e) {
280
-			return $this->batchDelete();
281
-		}
282
-	}
283
-
284
-	private function batchDelete($path = null) {
285
-		$params = [
286
-			'Bucket' => $this->bucket
287
-		];
288
-		if ($path !== null) {
289
-			$params['Prefix'] = $path . '/';
290
-		}
291
-		try {
292
-			$connection = $this->getConnection();
293
-			// Since there are no real directories on S3, we need
294
-			// to delete all objects prefixed with the path.
295
-			do {
296
-				// instead of the iterator, manually loop over the list ...
297
-				$objects = $connection->listObjects($params);
298
-				// ... so we can delete the files in batches
299
-				if (isset($objects['Contents'])) {
300
-					$connection->deleteObjects([
301
-						'Bucket' => $this->bucket,
302
-						'Delete' => [
303
-							'Objects' => $objects['Contents']
304
-						]
305
-					]);
306
-					$this->testTimeout();
307
-				}
308
-				// we reached the end when the list is no longer truncated
309
-			} while ($objects['IsTruncated']);
310
-		} catch (S3Exception $e) {
311
-			\OC::$server->getLogger()->logException($e, ['app' => 'files_external']);
312
-			return false;
313
-		}
314
-		return true;
315
-	}
316
-
317
-	public function opendir($path) {
318
-		$path = $this->normalizePath($path);
319
-
320
-		if ($this->isRoot($path)) {
321
-			$path = '';
322
-		} else {
323
-			$path .= '/';
324
-		}
325
-
326
-		try {
327
-			$files = [];
328
-			$results = $this->getConnection()->getPaginator('ListObjects', [
329
-				'Bucket' => $this->bucket,
330
-				'Delimiter' => '/',
331
-				'Prefix' => $path,
332
-			]);
333
-
334
-			foreach ($results as $result) {
335
-				// sub folders
336
-				if (is_array($result['CommonPrefixes'])) {
337
-					foreach ($result['CommonPrefixes'] as $prefix) {
338
-						$directoryName = trim($prefix['Prefix'], '/');
339
-						$files[] = substr($directoryName, strlen($path));
340
-						$this->directoryCache[$directoryName] = true;
341
-					}
342
-				}
343
-				if (is_array($result['Contents'])) {
344
-					foreach ($result['Contents'] as $object) {
345
-						if (isset($object['Key']) && $object['Key'] === $path) {
346
-							// it's the directory itself, skip
347
-							continue;
348
-						}
349
-						$file = basename(
350
-							isset($object['Key']) ? $object['Key'] : $object['Prefix']
351
-						);
352
-						$files[] = $file;
353
-
354
-						// store this information for later usage
355
-						$this->filesCache[$path . $file] = [
356
-							'ContentLength' => $object['Size'],
357
-							'LastModified' => (string)$object['LastModified'],
358
-						];
359
-					}
360
-				}
361
-			}
362
-
363
-			return IteratorDirectory::wrap($files);
364
-		} catch (S3Exception $e) {
365
-			\OC::$server->getLogger()->logException($e, ['app' => 'files_external']);
366
-			return false;
367
-		}
368
-	}
369
-
370
-	public function stat($path) {
371
-		$path = $this->normalizePath($path);
372
-
373
-		try {
374
-			$stat = [];
375
-			if ($this->is_dir($path)) {
376
-				//folders don't really exist
377
-				$stat['size'] = -1; //unknown
378
-				$stat['mtime'] = time();
379
-				$cacheEntry = $this->getCache()->get($path);
380
-				if ($cacheEntry instanceof CacheEntry && $this->getMountOption('filesystem_check_changes', 1) !== 1) {
381
-					$stat['size'] = $cacheEntry->getSize();
382
-					$stat['mtime'] = $cacheEntry->getMTime();
383
-				}
384
-			} else {
385
-				$stat['size'] = $this->getContentLength($path);
386
-				$stat['mtime'] = strtotime($this->getLastModified($path));
387
-			}
388
-			$stat['atime'] = time();
389
-
390
-			return $stat;
391
-		} catch (S3Exception $e) {
392
-			\OC::$server->getLogger()->logException($e, ['app' => 'files_external']);
393
-			return false;
394
-		}
395
-	}
396
-
397
-	/**
398
-	 * Return content length for object
399
-	 *
400
-	 * When the information is already present (e.g. opendir has been called before)
401
-	 * this value is return. Otherwise a headObject is emitted.
402
-	 *
403
-	 * @param $path
404
-	 * @return int|mixed
405
-	 */
406
-	private function getContentLength($path) {
407
-		if (isset($this->filesCache[$path])) {
408
-			return (int)$this->filesCache[$path]['ContentLength'];
409
-		}
410
-
411
-		$result = $this->headObject($path);
412
-		if (isset($result['ContentLength'])) {
413
-			return (int)$result['ContentLength'];
414
-		}
415
-
416
-		return 0;
417
-	}
418
-
419
-	/**
420
-	 * Return last modified for object
421
-	 *
422
-	 * When the information is already present (e.g. opendir has been called before)
423
-	 * this value is return. Otherwise a headObject is emitted.
424
-	 *
425
-	 * @param $path
426
-	 * @return mixed|string
427
-	 */
428
-	private function getLastModified($path) {
429
-		if (isset($this->filesCache[$path])) {
430
-			return $this->filesCache[$path]['LastModified'];
431
-		}
432
-
433
-		$result = $this->headObject($path);
434
-		if (isset($result['LastModified'])) {
435
-			return $result['LastModified'];
436
-		}
437
-
438
-		return 'now';
439
-	}
440
-
441
-	public function is_dir($path) {
442
-		$path = $this->normalizePath($path);
443
-
444
-		if (isset($this->filesCache[$path])) {
445
-			return false;
446
-		}
447
-
448
-		try {
449
-			return $this->isRoot($path) || $this->doesDirectoryExist($path);
450
-		} catch (S3Exception $e) {
451
-			\OC::$server->getLogger()->logException($e, ['app' => 'files_external']);
452
-			return false;
453
-		}
454
-	}
455
-
456
-	public function filetype($path) {
457
-		$path = $this->normalizePath($path);
458
-
459
-		if ($this->isRoot($path)) {
460
-			return 'dir';
461
-		}
462
-
463
-		try {
464
-			if (isset($this->filesCache[$path]) || $this->headObject($path)) {
465
-				return 'file';
466
-			}
467
-			if ($this->doesDirectoryExist($path)) {
468
-				return 'dir';
469
-			}
470
-		} catch (S3Exception $e) {
471
-			\OC::$server->getLogger()->logException($e, ['app' => 'files_external']);
472
-			return false;
473
-		}
474
-
475
-		return false;
476
-	}
477
-
478
-	public function getPermissions($path) {
479
-		$type = $this->filetype($path);
480
-		if (!$type) {
481
-			return 0;
482
-		}
483
-		return $type === 'dir' ? Constants::PERMISSION_ALL : Constants::PERMISSION_ALL - Constants::PERMISSION_CREATE;
484
-	}
485
-
486
-	public function unlink($path) {
487
-		$path = $this->normalizePath($path);
488
-
489
-		if ($this->is_dir($path)) {
490
-			return $this->rmdir($path);
491
-		}
492
-
493
-		try {
494
-			$this->deleteObject($path);
495
-			$this->invalidateCache($path);
496
-		} catch (S3Exception $e) {
497
-			\OC::$server->getLogger()->logException($e, ['app' => 'files_external']);
498
-			return false;
499
-		}
500
-
501
-		return true;
502
-	}
503
-
504
-	public function fopen($path, $mode) {
505
-		$path = $this->normalizePath($path);
506
-
507
-		switch ($mode) {
508
-			case 'r':
509
-			case 'rb':
510
-				// Don't try to fetch empty files
511
-				$stat = $this->stat($path);
512
-				if (is_array($stat) && isset($stat['size']) && $stat['size'] === 0) {
513
-					return fopen('php://memory', $mode);
514
-				}
515
-
516
-				try {
517
-					return $this->readObject($path);
518
-				} catch (S3Exception $e) {
519
-					\OC::$server->getLogger()->logException($e, ['app' => 'files_external']);
520
-					return false;
521
-				}
522
-			case 'w':
523
-			case 'wb':
524
-				$tmpFile = \OC::$server->getTempManager()->getTemporaryFile();
525
-
526
-				$handle = fopen($tmpFile, 'w');
527
-				return CallbackWrapper::wrap($handle, null, null, function () use ($path, $tmpFile) {
528
-					$this->writeBack($tmpFile, $path);
529
-				});
530
-			case 'a':
531
-			case 'ab':
532
-			case 'r+':
533
-			case 'w+':
534
-			case 'wb+':
535
-			case 'a+':
536
-			case 'x':
537
-			case 'x+':
538
-			case 'c':
539
-			case 'c+':
540
-				if (strrpos($path, '.') !== false) {
541
-					$ext = substr($path, strrpos($path, '.'));
542
-				} else {
543
-					$ext = '';
544
-				}
545
-				$tmpFile = \OC::$server->getTempManager()->getTemporaryFile($ext);
546
-				if ($this->file_exists($path)) {
547
-					$source = $this->readObject($path);
548
-					file_put_contents($tmpFile, $source);
549
-				}
550
-
551
-				$handle = fopen($tmpFile, $mode);
552
-				return CallbackWrapper::wrap($handle, null, null, function () use ($path, $tmpFile) {
553
-					$this->writeBack($tmpFile, $path);
554
-				});
555
-		}
556
-		return false;
557
-	}
558
-
559
-	public function touch($path, $mtime = null) {
560
-		if (is_null($mtime)) {
561
-			$mtime = time();
562
-		}
563
-		$metadata = [
564
-			'lastmodified' => gmdate(\DateTime::RFC1123, $mtime)
565
-		];
566
-
567
-		try {
568
-			if (!$this->file_exists($path)) {
569
-				$mimeType = \OC::$server->getMimeTypeDetector()->detectPath($path);
570
-				$this->getConnection()->putObject([
571
-					'Bucket' => $this->bucket,
572
-					'Key' => $this->cleanKey($path),
573
-					'Metadata' => $metadata,
574
-					'Body' => '',
575
-					'ContentType' => $mimeType,
576
-					'MetadataDirective' => 'REPLACE',
577
-				]);
578
-				$this->testTimeout();
579
-			}
580
-		} catch (S3Exception $e) {
581
-			\OC::$server->getLogger()->logException($e, ['app' => 'files_external']);
582
-			return false;
583
-		}
584
-
585
-		$this->invalidateCache($path);
586
-		return true;
587
-	}
588
-
589
-	public function copy($path1, $path2) {
590
-		$path1 = $this->normalizePath($path1);
591
-		$path2 = $this->normalizePath($path2);
592
-
593
-		if ($this->is_file($path1)) {
594
-			try {
595
-				$this->getConnection()->copyObject([
596
-					'Bucket' => $this->bucket,
597
-					'Key' => $this->cleanKey($path2),
598
-					'CopySource' => S3Client::encodeKey($this->bucket . '/' . $path1)
599
-				]);
600
-				$this->testTimeout();
601
-			} catch (S3Exception $e) {
602
-				\OC::$server->getLogger()->logException($e, ['app' => 'files_external']);
603
-				return false;
604
-			}
605
-		} else {
606
-			$this->remove($path2);
607
-
608
-			try {
609
-				$this->getConnection()->copyObject([
610
-					'Bucket' => $this->bucket,
611
-					'Key' => $path2 . '/',
612
-					'CopySource' => S3Client::encodeKey($this->bucket . '/' . $path1 . '/')
613
-				]);
614
-				$this->testTimeout();
615
-			} catch (S3Exception $e) {
616
-				\OC::$server->getLogger()->logException($e, ['app' => 'files_external']);
617
-				return false;
618
-			}
619
-
620
-			$dh = $this->opendir($path1);
621
-			if (is_resource($dh)) {
622
-				while (($file = readdir($dh)) !== false) {
623
-					if (\OC\Files\Filesystem::isIgnoredDir($file)) {
624
-						continue;
625
-					}
626
-
627
-					$source = $path1 . '/' . $file;
628
-					$target = $path2 . '/' . $file;
629
-					$this->copy($source, $target);
630
-				}
631
-			}
632
-		}
633
-
634
-		$this->invalidateCache($path2);
635
-
636
-		return true;
637
-	}
638
-
639
-	public function rename($path1, $path2) {
640
-		$path1 = $this->normalizePath($path1);
641
-		$path2 = $this->normalizePath($path2);
642
-
643
-		if ($this->is_file($path1)) {
644
-			if ($this->copy($path1, $path2) === false) {
645
-				return false;
646
-			}
647
-
648
-			if ($this->unlink($path1) === false) {
649
-				$this->unlink($path2);
650
-				return false;
651
-			}
652
-		} else {
653
-			if ($this->copy($path1, $path2) === false) {
654
-				return false;
655
-			}
656
-
657
-			if ($this->rmdir($path1) === false) {
658
-				$this->rmdir($path2);
659
-				return false;
660
-			}
661
-		}
662
-
663
-		return true;
664
-	}
665
-
666
-	public function test() {
667
-		$this->getConnection()->headBucket([
668
-			'Bucket' => $this->bucket
669
-		]);
670
-		return true;
671
-	}
672
-
673
-	public function getId() {
674
-		return $this->id;
675
-	}
676
-
677
-	public function writeBack($tmpFile, $path) {
678
-		try {
679
-			$source = fopen($tmpFile, 'r');
680
-			$this->writeObject($path, $source);
681
-			$this->invalidateCache($path);
682
-
683
-			unlink($tmpFile);
684
-			return true;
685
-		} catch (S3Exception $e) {
686
-			\OC::$server->getLogger()->logException($e, ['app' => 'files_external']);
687
-			return false;
688
-		}
689
-	}
690
-
691
-	/**
692
-	 * check if curl is installed
693
-	 */
694
-	public static function checkDependencies() {
695
-		return true;
696
-	}
55
+    use S3ConnectionTrait;
56
+    use S3ObjectTrait;
57
+
58
+    public function needsPartFile() {
59
+        return false;
60
+    }
61
+
62
+    /** @var CappedMemoryCache|Result[] */
63
+    private $objectCache;
64
+
65
+    /** @var CappedMemoryCache|bool[] */
66
+    private $directoryCache;
67
+
68
+    /** @var CappedMemoryCache|array */
69
+    private $filesCache;
70
+
71
+    public function __construct($parameters) {
72
+        parent::__construct($parameters);
73
+        $this->parseParams($parameters);
74
+        $this->objectCache = new CappedMemoryCache();
75
+        $this->directoryCache = new CappedMemoryCache();
76
+        $this->filesCache = new CappedMemoryCache();
77
+    }
78
+
79
+    /**
80
+     * @param string $path
81
+     * @return string correctly encoded path
82
+     */
83
+    private function normalizePath($path) {
84
+        $path = trim($path, '/');
85
+
86
+        if (!$path) {
87
+            $path = '.';
88
+        }
89
+
90
+        return $path;
91
+    }
92
+
93
+    private function isRoot($path) {
94
+        return $path === '.';
95
+    }
96
+
97
+    private function cleanKey($path) {
98
+        if ($this->isRoot($path)) {
99
+            return '/';
100
+        }
101
+        return $path;
102
+    }
103
+
104
+    private function clearCache() {
105
+        $this->objectCache = new CappedMemoryCache();
106
+        $this->directoryCache = new CappedMemoryCache();
107
+        $this->filesCache = new CappedMemoryCache();
108
+    }
109
+
110
+    private function invalidateCache($key) {
111
+        unset($this->objectCache[$key]);
112
+        $keys = array_keys($this->objectCache->getData());
113
+        $keyLength = strlen($key);
114
+        foreach ($keys as $existingKey) {
115
+            if (substr($existingKey, 0, $keyLength) === $key) {
116
+                unset($this->objectCache[$existingKey]);
117
+            }
118
+        }
119
+        unset($this->directoryCache[$key], $this->filesCache[$key]);
120
+    }
121
+
122
+    /**
123
+     * @param $key
124
+     * @return Result|boolean
125
+     */
126
+    private function headObject($key) {
127
+        if (!isset($this->objectCache[$key])) {
128
+            try {
129
+                $this->objectCache[$key] = $this->getConnection()->headObject([
130
+                    'Bucket' => $this->bucket,
131
+                    'Key' => $key
132
+                ]);
133
+            } catch (S3Exception $e) {
134
+                if ($e->getStatusCode() >= 500) {
135
+                    throw $e;
136
+                }
137
+                $this->objectCache[$key] = false;
138
+            }
139
+        }
140
+
141
+        return $this->objectCache[$key];
142
+    }
143
+
144
+    /**
145
+     * Return true if directory exists
146
+     *
147
+     * There are no folders in s3. A folder like structure could be archived
148
+     * by prefixing files with the folder name.
149
+     *
150
+     * Implementation from flysystem-aws-s3-v3:
151
+     * https://github.com/thephpleague/flysystem-aws-s3-v3/blob/8241e9cc5b28f981e0d24cdaf9867f14c7498ae4/src/AwsS3Adapter.php#L670-L694
152
+     *
153
+     * @param $path
154
+     * @return bool
155
+     * @throws \Exception
156
+     */
157
+    private function doesDirectoryExist($path) {
158
+        if (!isset($this->directoryCache[$path])) {
159
+            // Maybe this isn't an actual key, but a prefix.
160
+            // Do a prefix listing of objects to determine.
161
+            try {
162
+                $result = $this->getConnection()->listObjects([
163
+                    'Bucket' => $this->bucket,
164
+                    'Prefix' => rtrim($path, '/') . '/',
165
+                    'MaxKeys' => 1,
166
+                ]);
167
+                $this->directoryCache[$path] = $result['Contents'] || $result['CommonPrefixes'];
168
+            } catch (S3Exception $e) {
169
+                if ($e->getStatusCode() === 403) {
170
+                    $this->directoryCache[$path] = false;
171
+                }
172
+                throw $e;
173
+            }
174
+        }
175
+
176
+        return $this->directoryCache[$path];
177
+    }
178
+
179
+    /**
180
+     * Updates old storage ids (v0.2.1 and older) that are based on key and secret to new ones based on the bucket name.
181
+     * TODO Do this in an update.php. requires iterating over all users and loading the mount.json from their home
182
+     *
183
+     * @param array $params
184
+     */
185
+    public function updateLegacyId(array $params) {
186
+        $oldId = 'amazon::' . $params['key'] . md5($params['secret']);
187
+
188
+        // find by old id or bucket
189
+        $stmt = \OC::$server->getDatabaseConnection()->prepare(
190
+            'SELECT `numeric_id`, `id` FROM `*PREFIX*storages` WHERE `id` IN (?, ?)'
191
+        );
192
+        $stmt->execute([$oldId, $this->id]);
193
+        while ($row = $stmt->fetch()) {
194
+            $storages[$row['id']] = $row['numeric_id'];
195
+        }
196
+
197
+        if (isset($storages[$this->id]) && isset($storages[$oldId])) {
198
+            // if both ids exist, delete the old storage and corresponding filecache entries
199
+            \OC\Files\Cache\Storage::remove($oldId);
200
+        } elseif (isset($storages[$oldId])) {
201
+            // if only the old id exists do an update
202
+            $stmt = \OC::$server->getDatabaseConnection()->prepare(
203
+                'UPDATE `*PREFIX*storages` SET `id` = ? WHERE `id` = ?'
204
+            );
205
+            $stmt->execute([$this->id, $oldId]);
206
+        }
207
+        // only the bucket based id may exist, do nothing
208
+    }
209
+
210
+    /**
211
+     * Remove a file or folder
212
+     *
213
+     * @param string $path
214
+     * @return bool
215
+     */
216
+    protected function remove($path) {
217
+        // remember fileType to reduce http calls
218
+        $fileType = $this->filetype($path);
219
+        if ($fileType === 'dir') {
220
+            return $this->rmdir($path);
221
+        } elseif ($fileType === 'file') {
222
+            return $this->unlink($path);
223
+        } else {
224
+            return false;
225
+        }
226
+    }
227
+
228
+    public function mkdir($path) {
229
+        $path = $this->normalizePath($path);
230
+
231
+        if ($this->is_dir($path)) {
232
+            return false;
233
+        }
234
+
235
+        try {
236
+            $this->getConnection()->putObject([
237
+                'Bucket' => $this->bucket,
238
+                'Key' => $path . '/',
239
+                'Body' => '',
240
+                'ContentType' => 'httpd/unix-directory'
241
+            ]);
242
+            $this->testTimeout();
243
+        } catch (S3Exception $e) {
244
+            \OC::$server->getLogger()->logException($e, ['app' => 'files_external']);
245
+            return false;
246
+        }
247
+
248
+        $this->invalidateCache($path);
249
+
250
+        return true;
251
+    }
252
+
253
+    public function file_exists($path) {
254
+        return $this->filetype($path) !== false;
255
+    }
256
+
257
+
258
+    public function rmdir($path) {
259
+        $path = $this->normalizePath($path);
260
+
261
+        if ($this->isRoot($path)) {
262
+            return $this->clearBucket();
263
+        }
264
+
265
+        if (!$this->file_exists($path)) {
266
+            return false;
267
+        }
268
+
269
+        $this->invalidateCache($path);
270
+        return $this->batchDelete($path);
271
+    }
272
+
273
+    protected function clearBucket() {
274
+        $this->clearCache();
275
+        try {
276
+            $this->getConnection()->clearBucket($this->bucket);
277
+            return true;
278
+            // clearBucket() is not working with Ceph, so if it fails we try the slower approach
279
+        } catch (\Exception $e) {
280
+            return $this->batchDelete();
281
+        }
282
+    }
283
+
284
+    private function batchDelete($path = null) {
285
+        $params = [
286
+            'Bucket' => $this->bucket
287
+        ];
288
+        if ($path !== null) {
289
+            $params['Prefix'] = $path . '/';
290
+        }
291
+        try {
292
+            $connection = $this->getConnection();
293
+            // Since there are no real directories on S3, we need
294
+            // to delete all objects prefixed with the path.
295
+            do {
296
+                // instead of the iterator, manually loop over the list ...
297
+                $objects = $connection->listObjects($params);
298
+                // ... so we can delete the files in batches
299
+                if (isset($objects['Contents'])) {
300
+                    $connection->deleteObjects([
301
+                        'Bucket' => $this->bucket,
302
+                        'Delete' => [
303
+                            'Objects' => $objects['Contents']
304
+                        ]
305
+                    ]);
306
+                    $this->testTimeout();
307
+                }
308
+                // we reached the end when the list is no longer truncated
309
+            } while ($objects['IsTruncated']);
310
+        } catch (S3Exception $e) {
311
+            \OC::$server->getLogger()->logException($e, ['app' => 'files_external']);
312
+            return false;
313
+        }
314
+        return true;
315
+    }
316
+
317
+    public function opendir($path) {
318
+        $path = $this->normalizePath($path);
319
+
320
+        if ($this->isRoot($path)) {
321
+            $path = '';
322
+        } else {
323
+            $path .= '/';
324
+        }
325
+
326
+        try {
327
+            $files = [];
328
+            $results = $this->getConnection()->getPaginator('ListObjects', [
329
+                'Bucket' => $this->bucket,
330
+                'Delimiter' => '/',
331
+                'Prefix' => $path,
332
+            ]);
333
+
334
+            foreach ($results as $result) {
335
+                // sub folders
336
+                if (is_array($result['CommonPrefixes'])) {
337
+                    foreach ($result['CommonPrefixes'] as $prefix) {
338
+                        $directoryName = trim($prefix['Prefix'], '/');
339
+                        $files[] = substr($directoryName, strlen($path));
340
+                        $this->directoryCache[$directoryName] = true;
341
+                    }
342
+                }
343
+                if (is_array($result['Contents'])) {
344
+                    foreach ($result['Contents'] as $object) {
345
+                        if (isset($object['Key']) && $object['Key'] === $path) {
346
+                            // it's the directory itself, skip
347
+                            continue;
348
+                        }
349
+                        $file = basename(
350
+                            isset($object['Key']) ? $object['Key'] : $object['Prefix']
351
+                        );
352
+                        $files[] = $file;
353
+
354
+                        // store this information for later usage
355
+                        $this->filesCache[$path . $file] = [
356
+                            'ContentLength' => $object['Size'],
357
+                            'LastModified' => (string)$object['LastModified'],
358
+                        ];
359
+                    }
360
+                }
361
+            }
362
+
363
+            return IteratorDirectory::wrap($files);
364
+        } catch (S3Exception $e) {
365
+            \OC::$server->getLogger()->logException($e, ['app' => 'files_external']);
366
+            return false;
367
+        }
368
+    }
369
+
370
+    public function stat($path) {
371
+        $path = $this->normalizePath($path);
372
+
373
+        try {
374
+            $stat = [];
375
+            if ($this->is_dir($path)) {
376
+                //folders don't really exist
377
+                $stat['size'] = -1; //unknown
378
+                $stat['mtime'] = time();
379
+                $cacheEntry = $this->getCache()->get($path);
380
+                if ($cacheEntry instanceof CacheEntry && $this->getMountOption('filesystem_check_changes', 1) !== 1) {
381
+                    $stat['size'] = $cacheEntry->getSize();
382
+                    $stat['mtime'] = $cacheEntry->getMTime();
383
+                }
384
+            } else {
385
+                $stat['size'] = $this->getContentLength($path);
386
+                $stat['mtime'] = strtotime($this->getLastModified($path));
387
+            }
388
+            $stat['atime'] = time();
389
+
390
+            return $stat;
391
+        } catch (S3Exception $e) {
392
+            \OC::$server->getLogger()->logException($e, ['app' => 'files_external']);
393
+            return false;
394
+        }
395
+    }
396
+
397
+    /**
398
+     * Return content length for object
399
+     *
400
+     * When the information is already present (e.g. opendir has been called before)
401
+     * this value is return. Otherwise a headObject is emitted.
402
+     *
403
+     * @param $path
404
+     * @return int|mixed
405
+     */
406
+    private function getContentLength($path) {
407
+        if (isset($this->filesCache[$path])) {
408
+            return (int)$this->filesCache[$path]['ContentLength'];
409
+        }
410
+
411
+        $result = $this->headObject($path);
412
+        if (isset($result['ContentLength'])) {
413
+            return (int)$result['ContentLength'];
414
+        }
415
+
416
+        return 0;
417
+    }
418
+
419
+    /**
420
+     * Return last modified for object
421
+     *
422
+     * When the information is already present (e.g. opendir has been called before)
423
+     * this value is return. Otherwise a headObject is emitted.
424
+     *
425
+     * @param $path
426
+     * @return mixed|string
427
+     */
428
+    private function getLastModified($path) {
429
+        if (isset($this->filesCache[$path])) {
430
+            return $this->filesCache[$path]['LastModified'];
431
+        }
432
+
433
+        $result = $this->headObject($path);
434
+        if (isset($result['LastModified'])) {
435
+            return $result['LastModified'];
436
+        }
437
+
438
+        return 'now';
439
+    }
440
+
441
+    public function is_dir($path) {
442
+        $path = $this->normalizePath($path);
443
+
444
+        if (isset($this->filesCache[$path])) {
445
+            return false;
446
+        }
447
+
448
+        try {
449
+            return $this->isRoot($path) || $this->doesDirectoryExist($path);
450
+        } catch (S3Exception $e) {
451
+            \OC::$server->getLogger()->logException($e, ['app' => 'files_external']);
452
+            return false;
453
+        }
454
+    }
455
+
456
+    public function filetype($path) {
457
+        $path = $this->normalizePath($path);
458
+
459
+        if ($this->isRoot($path)) {
460
+            return 'dir';
461
+        }
462
+
463
+        try {
464
+            if (isset($this->filesCache[$path]) || $this->headObject($path)) {
465
+                return 'file';
466
+            }
467
+            if ($this->doesDirectoryExist($path)) {
468
+                return 'dir';
469
+            }
470
+        } catch (S3Exception $e) {
471
+            \OC::$server->getLogger()->logException($e, ['app' => 'files_external']);
472
+            return false;
473
+        }
474
+
475
+        return false;
476
+    }
477
+
478
+    public function getPermissions($path) {
479
+        $type = $this->filetype($path);
480
+        if (!$type) {
481
+            return 0;
482
+        }
483
+        return $type === 'dir' ? Constants::PERMISSION_ALL : Constants::PERMISSION_ALL - Constants::PERMISSION_CREATE;
484
+    }
485
+
486
+    public function unlink($path) {
487
+        $path = $this->normalizePath($path);
488
+
489
+        if ($this->is_dir($path)) {
490
+            return $this->rmdir($path);
491
+        }
492
+
493
+        try {
494
+            $this->deleteObject($path);
495
+            $this->invalidateCache($path);
496
+        } catch (S3Exception $e) {
497
+            \OC::$server->getLogger()->logException($e, ['app' => 'files_external']);
498
+            return false;
499
+        }
500
+
501
+        return true;
502
+    }
503
+
504
+    public function fopen($path, $mode) {
505
+        $path = $this->normalizePath($path);
506
+
507
+        switch ($mode) {
508
+            case 'r':
509
+            case 'rb':
510
+                // Don't try to fetch empty files
511
+                $stat = $this->stat($path);
512
+                if (is_array($stat) && isset($stat['size']) && $stat['size'] === 0) {
513
+                    return fopen('php://memory', $mode);
514
+                }
515
+
516
+                try {
517
+                    return $this->readObject($path);
518
+                } catch (S3Exception $e) {
519
+                    \OC::$server->getLogger()->logException($e, ['app' => 'files_external']);
520
+                    return false;
521
+                }
522
+            case 'w':
523
+            case 'wb':
524
+                $tmpFile = \OC::$server->getTempManager()->getTemporaryFile();
525
+
526
+                $handle = fopen($tmpFile, 'w');
527
+                return CallbackWrapper::wrap($handle, null, null, function () use ($path, $tmpFile) {
528
+                    $this->writeBack($tmpFile, $path);
529
+                });
530
+            case 'a':
531
+            case 'ab':
532
+            case 'r+':
533
+            case 'w+':
534
+            case 'wb+':
535
+            case 'a+':
536
+            case 'x':
537
+            case 'x+':
538
+            case 'c':
539
+            case 'c+':
540
+                if (strrpos($path, '.') !== false) {
541
+                    $ext = substr($path, strrpos($path, '.'));
542
+                } else {
543
+                    $ext = '';
544
+                }
545
+                $tmpFile = \OC::$server->getTempManager()->getTemporaryFile($ext);
546
+                if ($this->file_exists($path)) {
547
+                    $source = $this->readObject($path);
548
+                    file_put_contents($tmpFile, $source);
549
+                }
550
+
551
+                $handle = fopen($tmpFile, $mode);
552
+                return CallbackWrapper::wrap($handle, null, null, function () use ($path, $tmpFile) {
553
+                    $this->writeBack($tmpFile, $path);
554
+                });
555
+        }
556
+        return false;
557
+    }
558
+
559
+    public function touch($path, $mtime = null) {
560
+        if (is_null($mtime)) {
561
+            $mtime = time();
562
+        }
563
+        $metadata = [
564
+            'lastmodified' => gmdate(\DateTime::RFC1123, $mtime)
565
+        ];
566
+
567
+        try {
568
+            if (!$this->file_exists($path)) {
569
+                $mimeType = \OC::$server->getMimeTypeDetector()->detectPath($path);
570
+                $this->getConnection()->putObject([
571
+                    'Bucket' => $this->bucket,
572
+                    'Key' => $this->cleanKey($path),
573
+                    'Metadata' => $metadata,
574
+                    'Body' => '',
575
+                    'ContentType' => $mimeType,
576
+                    'MetadataDirective' => 'REPLACE',
577
+                ]);
578
+                $this->testTimeout();
579
+            }
580
+        } catch (S3Exception $e) {
581
+            \OC::$server->getLogger()->logException($e, ['app' => 'files_external']);
582
+            return false;
583
+        }
584
+
585
+        $this->invalidateCache($path);
586
+        return true;
587
+    }
588
+
589
+    public function copy($path1, $path2) {
590
+        $path1 = $this->normalizePath($path1);
591
+        $path2 = $this->normalizePath($path2);
592
+
593
+        if ($this->is_file($path1)) {
594
+            try {
595
+                $this->getConnection()->copyObject([
596
+                    'Bucket' => $this->bucket,
597
+                    'Key' => $this->cleanKey($path2),
598
+                    'CopySource' => S3Client::encodeKey($this->bucket . '/' . $path1)
599
+                ]);
600
+                $this->testTimeout();
601
+            } catch (S3Exception $e) {
602
+                \OC::$server->getLogger()->logException($e, ['app' => 'files_external']);
603
+                return false;
604
+            }
605
+        } else {
606
+            $this->remove($path2);
607
+
608
+            try {
609
+                $this->getConnection()->copyObject([
610
+                    'Bucket' => $this->bucket,
611
+                    'Key' => $path2 . '/',
612
+                    'CopySource' => S3Client::encodeKey($this->bucket . '/' . $path1 . '/')
613
+                ]);
614
+                $this->testTimeout();
615
+            } catch (S3Exception $e) {
616
+                \OC::$server->getLogger()->logException($e, ['app' => 'files_external']);
617
+                return false;
618
+            }
619
+
620
+            $dh = $this->opendir($path1);
621
+            if (is_resource($dh)) {
622
+                while (($file = readdir($dh)) !== false) {
623
+                    if (\OC\Files\Filesystem::isIgnoredDir($file)) {
624
+                        continue;
625
+                    }
626
+
627
+                    $source = $path1 . '/' . $file;
628
+                    $target = $path2 . '/' . $file;
629
+                    $this->copy($source, $target);
630
+                }
631
+            }
632
+        }
633
+
634
+        $this->invalidateCache($path2);
635
+
636
+        return true;
637
+    }
638
+
639
+    public function rename($path1, $path2) {
640
+        $path1 = $this->normalizePath($path1);
641
+        $path2 = $this->normalizePath($path2);
642
+
643
+        if ($this->is_file($path1)) {
644
+            if ($this->copy($path1, $path2) === false) {
645
+                return false;
646
+            }
647
+
648
+            if ($this->unlink($path1) === false) {
649
+                $this->unlink($path2);
650
+                return false;
651
+            }
652
+        } else {
653
+            if ($this->copy($path1, $path2) === false) {
654
+                return false;
655
+            }
656
+
657
+            if ($this->rmdir($path1) === false) {
658
+                $this->rmdir($path2);
659
+                return false;
660
+            }
661
+        }
662
+
663
+        return true;
664
+    }
665
+
666
+    public function test() {
667
+        $this->getConnection()->headBucket([
668
+            'Bucket' => $this->bucket
669
+        ]);
670
+        return true;
671
+    }
672
+
673
+    public function getId() {
674
+        return $this->id;
675
+    }
676
+
677
+    public function writeBack($tmpFile, $path) {
678
+        try {
679
+            $source = fopen($tmpFile, 'r');
680
+            $this->writeObject($path, $source);
681
+            $this->invalidateCache($path);
682
+
683
+            unlink($tmpFile);
684
+            return true;
685
+        } catch (S3Exception $e) {
686
+            \OC::$server->getLogger()->logException($e, ['app' => 'files_external']);
687
+            return false;
688
+        }
689
+    }
690
+
691
+    /**
692
+     * check if curl is installed
693
+     */
694
+    public static function checkDependencies() {
695
+        return true;
696
+    }
697 697
 }
Please login to merge, or discard this patch.
Spacing   +15 added lines, -15 removed lines patch added patch discarded remove patch
@@ -161,7 +161,7 @@  discard block
 block discarded – undo
161 161
 			try {
162 162
 				$result = $this->getConnection()->listObjects([
163 163
 					'Bucket' => $this->bucket,
164
-					'Prefix' => rtrim($path, '/') . '/',
164
+					'Prefix' => rtrim($path, '/').'/',
165 165
 					'MaxKeys' => 1,
166 166
 				]);
167 167
 				$this->directoryCache[$path] = $result['Contents'] || $result['CommonPrefixes'];
@@ -183,7 +183,7 @@  discard block
 block discarded – undo
183 183
 	 * @param array $params
184 184
 	 */
185 185
 	public function updateLegacyId(array $params) {
186
-		$oldId = 'amazon::' . $params['key'] . md5($params['secret']);
186
+		$oldId = 'amazon::'.$params['key'].md5($params['secret']);
187 187
 
188 188
 		// find by old id or bucket
189 189
 		$stmt = \OC::$server->getDatabaseConnection()->prepare(
@@ -235,7 +235,7 @@  discard block
 block discarded – undo
235 235
 		try {
236 236
 			$this->getConnection()->putObject([
237 237
 				'Bucket' => $this->bucket,
238
-				'Key' => $path . '/',
238
+				'Key' => $path.'/',
239 239
 				'Body' => '',
240 240
 				'ContentType' => 'httpd/unix-directory'
241 241
 			]);
@@ -286,7 +286,7 @@  discard block
 block discarded – undo
286 286
 			'Bucket' => $this->bucket
287 287
 		];
288 288
 		if ($path !== null) {
289
-			$params['Prefix'] = $path . '/';
289
+			$params['Prefix'] = $path.'/';
290 290
 		}
291 291
 		try {
292 292
 			$connection = $this->getConnection();
@@ -352,9 +352,9 @@  discard block
 block discarded – undo
352 352
 						$files[] = $file;
353 353
 
354 354
 						// store this information for later usage
355
-						$this->filesCache[$path . $file] = [
355
+						$this->filesCache[$path.$file] = [
356 356
 							'ContentLength' => $object['Size'],
357
-							'LastModified' => (string)$object['LastModified'],
357
+							'LastModified' => (string) $object['LastModified'],
358 358
 						];
359 359
 					}
360 360
 				}
@@ -405,12 +405,12 @@  discard block
 block discarded – undo
405 405
 	 */
406 406
 	private function getContentLength($path) {
407 407
 		if (isset($this->filesCache[$path])) {
408
-			return (int)$this->filesCache[$path]['ContentLength'];
408
+			return (int) $this->filesCache[$path]['ContentLength'];
409 409
 		}
410 410
 
411 411
 		$result = $this->headObject($path);
412 412
 		if (isset($result['ContentLength'])) {
413
-			return (int)$result['ContentLength'];
413
+			return (int) $result['ContentLength'];
414 414
 		}
415 415
 
416 416
 		return 0;
@@ -524,7 +524,7 @@  discard block
 block discarded – undo
524 524
 				$tmpFile = \OC::$server->getTempManager()->getTemporaryFile();
525 525
 
526 526
 				$handle = fopen($tmpFile, 'w');
527
-				return CallbackWrapper::wrap($handle, null, null, function () use ($path, $tmpFile) {
527
+				return CallbackWrapper::wrap($handle, null, null, function() use ($path, $tmpFile) {
528 528
 					$this->writeBack($tmpFile, $path);
529 529
 				});
530 530
 			case 'a':
@@ -549,7 +549,7 @@  discard block
 block discarded – undo
549 549
 				}
550 550
 
551 551
 				$handle = fopen($tmpFile, $mode);
552
-				return CallbackWrapper::wrap($handle, null, null, function () use ($path, $tmpFile) {
552
+				return CallbackWrapper::wrap($handle, null, null, function() use ($path, $tmpFile) {
553 553
 					$this->writeBack($tmpFile, $path);
554 554
 				});
555 555
 		}
@@ -595,7 +595,7 @@  discard block
 block discarded – undo
595 595
 				$this->getConnection()->copyObject([
596 596
 					'Bucket' => $this->bucket,
597 597
 					'Key' => $this->cleanKey($path2),
598
-					'CopySource' => S3Client::encodeKey($this->bucket . '/' . $path1)
598
+					'CopySource' => S3Client::encodeKey($this->bucket.'/'.$path1)
599 599
 				]);
600 600
 				$this->testTimeout();
601 601
 			} catch (S3Exception $e) {
@@ -608,8 +608,8 @@  discard block
 block discarded – undo
608 608
 			try {
609 609
 				$this->getConnection()->copyObject([
610 610
 					'Bucket' => $this->bucket,
611
-					'Key' => $path2 . '/',
612
-					'CopySource' => S3Client::encodeKey($this->bucket . '/' . $path1 . '/')
611
+					'Key' => $path2.'/',
612
+					'CopySource' => S3Client::encodeKey($this->bucket.'/'.$path1.'/')
613 613
 				]);
614 614
 				$this->testTimeout();
615 615
 			} catch (S3Exception $e) {
@@ -624,8 +624,8 @@  discard block
 block discarded – undo
624 624
 						continue;
625 625
 					}
626 626
 
627
-					$source = $path1 . '/' . $file;
628
-					$target = $path2 . '/' . $file;
627
+					$source = $path1.'/'.$file;
628
+					$target = $path2.'/'.$file;
629 629
 					$this->copy($source, $target);
630 630
 				}
631 631
 			}
Please login to merge, or discard this patch.
lib/private/Files/ObjectStore/ObjectStoreStorage.php 1 patch
Indentation   +483 added lines, -483 removed lines patch added patch discarded remove patch
@@ -37,490 +37,490 @@
 block discarded – undo
37 37
 use OCP\Files\ObjectStore\IObjectStore;
38 38
 
39 39
 class ObjectStoreStorage extends \OC\Files\Storage\Common {
40
-	/**
41
-	 * @var \OCP\Files\ObjectStore\IObjectStore $objectStore
42
-	 */
43
-	protected $objectStore;
44
-	/**
45
-	 * @var string $id
46
-	 */
47
-	protected $id;
48
-	/**
49
-	 * @var \OC\User\User $user
50
-	 */
51
-	protected $user;
52
-
53
-	private $objectPrefix = 'urn:oid:';
54
-
55
-	private $logger;
56
-
57
-	public function __construct($params) {
58
-		if (isset($params['objectstore']) && $params['objectstore'] instanceof IObjectStore) {
59
-			$this->objectStore = $params['objectstore'];
60
-		} else {
61
-			throw new \Exception('missing IObjectStore instance');
62
-		}
63
-		if (isset($params['storageid'])) {
64
-			$this->id = 'object::store:' . $params['storageid'];
65
-		} else {
66
-			$this->id = 'object::store:' . $this->objectStore->getStorageId();
67
-		}
68
-		if (isset($params['objectPrefix'])) {
69
-			$this->objectPrefix = $params['objectPrefix'];
70
-		}
71
-		//initialize cache with root directory in cache
72
-		if (!$this->is_dir('/')) {
73
-			$this->mkdir('/');
74
-		}
75
-
76
-		$this->logger = \OC::$server->getLogger();
77
-	}
78
-
79
-	public function mkdir($path) {
80
-		$path = $this->normalizePath($path);
81
-
82
-		if ($this->file_exists($path)) {
83
-			return false;
84
-		}
85
-
86
-		$mTime = time();
87
-		$data = [
88
-			'mimetype' => 'httpd/unix-directory',
89
-			'size' => 0,
90
-			'mtime' => $mTime,
91
-			'storage_mtime' => $mTime,
92
-			'permissions' => \OCP\Constants::PERMISSION_ALL,
93
-		];
94
-		if ($path === '') {
95
-			//create root on the fly
96
-			$data['etag'] = $this->getETag('');
97
-			$this->getCache()->put('', $data);
98
-			return true;
99
-		} else {
100
-			// if parent does not exist, create it
101
-			$parent = $this->normalizePath(dirname($path));
102
-			$parentType = $this->filetype($parent);
103
-			if ($parentType === false) {
104
-				if (!$this->mkdir($parent)) {
105
-					// something went wrong
106
-					return false;
107
-				}
108
-			} elseif ($parentType === 'file') {
109
-				// parent is a file
110
-				return false;
111
-			}
112
-			// finally create the new dir
113
-			$mTime = time(); // update mtime
114
-			$data['mtime'] = $mTime;
115
-			$data['storage_mtime'] = $mTime;
116
-			$data['etag'] = $this->getETag($path);
117
-			$this->getCache()->put($path, $data);
118
-			return true;
119
-		}
120
-	}
121
-
122
-	/**
123
-	 * @param string $path
124
-	 * @return string
125
-	 */
126
-	private function normalizePath($path) {
127
-		$path = trim($path, '/');
128
-		//FIXME why do we sometimes get a path like 'files//username'?
129
-		$path = str_replace('//', '/', $path);
130
-
131
-		// dirname('/folder') returns '.' but internally (in the cache) we store the root as ''
132
-		if (!$path || $path === '.') {
133
-			$path = '';
134
-		}
135
-
136
-		return $path;
137
-	}
138
-
139
-	/**
140
-	 * Object Stores use a NoopScanner because metadata is directly stored in
141
-	 * the file cache and cannot really scan the filesystem. The storage passed in is not used anywhere.
142
-	 *
143
-	 * @param string $path
144
-	 * @param \OC\Files\Storage\Storage (optional) the storage to pass to the scanner
145
-	 * @return \OC\Files\ObjectStore\NoopScanner
146
-	 */
147
-	public function getScanner($path = '', $storage = null) {
148
-		if (!$storage) {
149
-			$storage = $this;
150
-		}
151
-		if (!isset($this->scanner)) {
152
-			$this->scanner = new NoopScanner($storage);
153
-		}
154
-		return $this->scanner;
155
-	}
156
-
157
-	public function getId() {
158
-		return $this->id;
159
-	}
160
-
161
-	public function rmdir($path) {
162
-		$path = $this->normalizePath($path);
163
-
164
-		if (!$this->is_dir($path)) {
165
-			return false;
166
-		}
167
-
168
-		if (!$this->rmObjects($path)) {
169
-			return false;
170
-		}
171
-
172
-		$this->getCache()->remove($path);
173
-
174
-		return true;
175
-	}
176
-
177
-	private function rmObjects($path) {
178
-		$children = $this->getCache()->getFolderContents($path);
179
-		foreach ($children as $child) {
180
-			if ($child['mimetype'] === 'httpd/unix-directory') {
181
-				if (!$this->rmObjects($child['path'])) {
182
-					return false;
183
-				}
184
-			} else {
185
-				if (!$this->unlink($child['path'])) {
186
-					return false;
187
-				}
188
-			}
189
-		}
190
-
191
-		return true;
192
-	}
193
-
194
-	public function unlink($path) {
195
-		$path = $this->normalizePath($path);
196
-		$stat = $this->stat($path);
197
-
198
-		if ($stat && isset($stat['fileid'])) {
199
-			if ($stat['mimetype'] === 'httpd/unix-directory') {
200
-				return $this->rmdir($path);
201
-			}
202
-			try {
203
-				$this->objectStore->deleteObject($this->getURN($stat['fileid']));
204
-			} catch (\Exception $ex) {
205
-				if ($ex->getCode() !== 404) {
206
-					$this->logger->logException($ex, [
207
-						'app' => 'objectstore',
208
-						'message' => 'Could not delete object ' . $this->getURN($stat['fileid']) . ' for ' . $path,
209
-					]);
210
-					return false;
211
-				}
212
-				//removing from cache is ok as it does not exist in the objectstore anyway
213
-			}
214
-			$this->getCache()->remove($path);
215
-			return true;
216
-		}
217
-		return false;
218
-	}
219
-
220
-	public function stat($path) {
221
-		$path = $this->normalizePath($path);
222
-		$cacheEntry = $this->getCache()->get($path);
223
-		if ($cacheEntry instanceof CacheEntry) {
224
-			return $cacheEntry->getData();
225
-		} else {
226
-			return false;
227
-		}
228
-	}
229
-
230
-	public function getPermissions($path) {
231
-		$stat = $this->stat($path);
232
-
233
-		if (is_array($stat) && isset($stat['permissions'])) {
234
-			return $stat['permissions'];
235
-		}
236
-
237
-		return parent::getPermissions($path);
238
-	}
239
-
240
-	/**
241
-	 * Override this method if you need a different unique resource identifier for your object storage implementation.
242
-	 * The default implementations just appends the fileId to 'urn:oid:'. Make sure the URN is unique over all users.
243
-	 * You may need a mapping table to store your URN if it cannot be generated from the fileid.
244
-	 *
245
-	 * @param int $fileId the fileid
246
-	 * @return null|string the unified resource name used to identify the object
247
-	 */
248
-	public function getURN($fileId) {
249
-		if (is_numeric($fileId)) {
250
-			return $this->objectPrefix . $fileId;
251
-		}
252
-		return null;
253
-	}
254
-
255
-	public function opendir($path) {
256
-		$path = $this->normalizePath($path);
257
-
258
-		try {
259
-			$files = [];
260
-			$folderContents = $this->getCache()->getFolderContents($path);
261
-			foreach ($folderContents as $file) {
262
-				$files[] = $file['name'];
263
-			}
264
-
265
-			return IteratorDirectory::wrap($files);
266
-		} catch (\Exception $e) {
267
-			$this->logger->logException($e);
268
-			return false;
269
-		}
270
-	}
271
-
272
-	public function filetype($path) {
273
-		$path = $this->normalizePath($path);
274
-		$stat = $this->stat($path);
275
-		if ($stat) {
276
-			if ($stat['mimetype'] === 'httpd/unix-directory') {
277
-				return 'dir';
278
-			}
279
-			return 'file';
280
-		} else {
281
-			return false;
282
-		}
283
-	}
284
-
285
-	public function fopen($path, $mode) {
286
-		$path = $this->normalizePath($path);
287
-
288
-		if (strrpos($path, '.') !== false) {
289
-			$ext = substr($path, strrpos($path, '.'));
290
-		} else {
291
-			$ext = '';
292
-		}
293
-
294
-		switch ($mode) {
295
-			case 'r':
296
-			case 'rb':
297
-				$stat = $this->stat($path);
298
-				if (is_array($stat)) {
299
-					// Reading 0 sized files is a waste of time
300
-					if (isset($stat['size']) && $stat['size'] === 0) {
301
-						return fopen('php://memory', $mode);
302
-					}
303
-
304
-					try {
305
-						return $this->objectStore->readObject($this->getURN($stat['fileid']));
306
-					} catch (NotFoundException $e) {
307
-						$this->logger->logException($e, [
308
-							'app' => 'objectstore',
309
-							'message' => 'Could not get object ' . $this->getURN($stat['fileid']) . ' for file ' . $path,
310
-						]);
311
-						throw $e;
312
-					} catch (\Exception $ex) {
313
-						$this->logger->logException($ex, [
314
-							'app' => 'objectstore',
315
-							'message' => 'Could not get object ' . $this->getURN($stat['fileid']) . ' for file ' . $path,
316
-						]);
317
-						return false;
318
-					}
319
-				} else {
320
-					return false;
321
-				}
322
-				// no break
323
-			case 'w':
324
-			case 'wb':
325
-			case 'w+':
326
-			case 'wb+':
327
-				$tmpFile = \OC::$server->getTempManager()->getTemporaryFile($ext);
328
-				$handle = fopen($tmpFile, $mode);
329
-				return CallbackWrapper::wrap($handle, null, null, function () use ($path, $tmpFile) {
330
-					$this->writeBack($tmpFile, $path);
331
-				});
332
-			case 'a':
333
-			case 'ab':
334
-			case 'r+':
335
-			case 'a+':
336
-			case 'x':
337
-			case 'x+':
338
-			case 'c':
339
-			case 'c+':
340
-				$tmpFile = \OC::$server->getTempManager()->getTemporaryFile($ext);
341
-				if ($this->file_exists($path)) {
342
-					$source = $this->fopen($path, 'r');
343
-					file_put_contents($tmpFile, $source);
344
-				}
345
-				$handle = fopen($tmpFile, $mode);
346
-				return CallbackWrapper::wrap($handle, null, null, function () use ($path, $tmpFile) {
347
-					$this->writeBack($tmpFile, $path);
348
-				});
349
-		}
350
-		return false;
351
-	}
352
-
353
-	public function file_exists($path) {
354
-		$path = $this->normalizePath($path);
355
-		return (bool)$this->stat($path);
356
-	}
357
-
358
-	public function rename($source, $target) {
359
-		$source = $this->normalizePath($source);
360
-		$target = $this->normalizePath($target);
361
-		$this->remove($target);
362
-		$this->getCache()->move($source, $target);
363
-		$this->touch(dirname($target));
364
-		return true;
365
-	}
366
-
367
-	public function getMimeType($path) {
368
-		$path = $this->normalizePath($path);
369
-		return parent::getMimeType($path);
370
-	}
371
-
372
-	public function touch($path, $mtime = null) {
373
-		if (is_null($mtime)) {
374
-			$mtime = time();
375
-		}
376
-
377
-		$path = $this->normalizePath($path);
378
-		$dirName = dirname($path);
379
-		$parentExists = $this->is_dir($dirName);
380
-		if (!$parentExists) {
381
-			return false;
382
-		}
383
-
384
-		$stat = $this->stat($path);
385
-		if (is_array($stat)) {
386
-			// update existing mtime in db
387
-			$stat['mtime'] = $mtime;
388
-			$this->getCache()->update($stat['fileid'], $stat);
389
-		} else {
390
-			try {
391
-				//create a empty file, need to have at least on char to make it
392
-				// work with all object storage implementations
393
-				$this->file_put_contents($path, ' ');
394
-				$mimeType = \OC::$server->getMimeTypeDetector()->detectPath($path);
395
-				$stat = [
396
-					'etag' => $this->getETag($path),
397
-					'mimetype' => $mimeType,
398
-					'size' => 0,
399
-					'mtime' => $mtime,
400
-					'storage_mtime' => $mtime,
401
-					'permissions' => \OCP\Constants::PERMISSION_ALL - \OCP\Constants::PERMISSION_CREATE,
402
-				];
403
-				$this->getCache()->put($path, $stat);
404
-			} catch (\Exception $ex) {
405
-				$this->logger->logException($ex, [
406
-					'app' => 'objectstore',
407
-					'message' => 'Could not create object for ' . $path,
408
-				]);
409
-				throw $ex;
410
-			}
411
-		}
412
-		return true;
413
-	}
414
-
415
-	public function writeBack($tmpFile, $path) {
416
-		$size = filesize($tmpFile);
417
-		$this->writeStream($path, fopen($tmpFile, 'r'), $size);
418
-	}
419
-
420
-	/**
421
-	 * external changes are not supported, exclusive access to the object storage is assumed
422
-	 *
423
-	 * @param string $path
424
-	 * @param int $time
425
-	 * @return false
426
-	 */
427
-	public function hasUpdated($path, $time) {
428
-		return false;
429
-	}
430
-
431
-	public function needsPartFile() {
432
-		return false;
433
-	}
434
-
435
-	public function file_put_contents($path, $data) {
436
-		$handle = $this->fopen($path, 'w+');
437
-		fwrite($handle, $data);
438
-		fclose($handle);
439
-		return true;
440
-	}
441
-
442
-	public function writeStream(string $path, $stream, int $size = null): int {
443
-		$stat = $this->stat($path);
444
-		if (empty($stat)) {
445
-			// create new file
446
-			$stat = [
447
-				'permissions' => \OCP\Constants::PERMISSION_ALL - \OCP\Constants::PERMISSION_CREATE,
448
-			];
449
-		}
450
-		// update stat with new data
451
-		$mTime = time();
452
-		$stat['size'] = (int)$size;
453
-		$stat['mtime'] = $mTime;
454
-		$stat['storage_mtime'] = $mTime;
455
-
456
-		$mimetypeDetector = \OC::$server->getMimeTypeDetector();
457
-		$mimetype = $mimetypeDetector->detectPath($path);
458
-
459
-		$stat['mimetype'] = $mimetype;
460
-		$stat['etag'] = $this->getETag($path);
461
-
462
-		$exists = $this->getCache()->inCache($path);
463
-		$uploadPath = $exists ? $path : $path . '.part';
464
-
465
-		if ($exists) {
466
-			$fileId = $stat['fileid'];
467
-		} else {
468
-			$fileId = $this->getCache()->put($uploadPath, $stat);
469
-		}
470
-
471
-		$urn = $this->getURN($fileId);
472
-		try {
473
-			//upload to object storage
474
-			if ($size === null) {
475
-				$countStream = CountWrapper::wrap($stream, function ($writtenSize) use ($fileId, &$size) {
476
-					$this->getCache()->update($fileId, [
477
-						'size' => $writtenSize
478
-					]);
479
-					$size = $writtenSize;
480
-				});
481
-				$this->objectStore->writeObject($urn, $countStream);
482
-				if (is_resource($countStream)) {
483
-					fclose($countStream);
484
-				}
485
-				$stat['size'] = $size;
486
-			} else {
487
-				$this->objectStore->writeObject($urn, $stream);
488
-			}
489
-		} catch (\Exception $ex) {
490
-			if (!$exists) {
491
-				/*
40
+    /**
41
+     * @var \OCP\Files\ObjectStore\IObjectStore $objectStore
42
+     */
43
+    protected $objectStore;
44
+    /**
45
+     * @var string $id
46
+     */
47
+    protected $id;
48
+    /**
49
+     * @var \OC\User\User $user
50
+     */
51
+    protected $user;
52
+
53
+    private $objectPrefix = 'urn:oid:';
54
+
55
+    private $logger;
56
+
57
+    public function __construct($params) {
58
+        if (isset($params['objectstore']) && $params['objectstore'] instanceof IObjectStore) {
59
+            $this->objectStore = $params['objectstore'];
60
+        } else {
61
+            throw new \Exception('missing IObjectStore instance');
62
+        }
63
+        if (isset($params['storageid'])) {
64
+            $this->id = 'object::store:' . $params['storageid'];
65
+        } else {
66
+            $this->id = 'object::store:' . $this->objectStore->getStorageId();
67
+        }
68
+        if (isset($params['objectPrefix'])) {
69
+            $this->objectPrefix = $params['objectPrefix'];
70
+        }
71
+        //initialize cache with root directory in cache
72
+        if (!$this->is_dir('/')) {
73
+            $this->mkdir('/');
74
+        }
75
+
76
+        $this->logger = \OC::$server->getLogger();
77
+    }
78
+
79
+    public function mkdir($path) {
80
+        $path = $this->normalizePath($path);
81
+
82
+        if ($this->file_exists($path)) {
83
+            return false;
84
+        }
85
+
86
+        $mTime = time();
87
+        $data = [
88
+            'mimetype' => 'httpd/unix-directory',
89
+            'size' => 0,
90
+            'mtime' => $mTime,
91
+            'storage_mtime' => $mTime,
92
+            'permissions' => \OCP\Constants::PERMISSION_ALL,
93
+        ];
94
+        if ($path === '') {
95
+            //create root on the fly
96
+            $data['etag'] = $this->getETag('');
97
+            $this->getCache()->put('', $data);
98
+            return true;
99
+        } else {
100
+            // if parent does not exist, create it
101
+            $parent = $this->normalizePath(dirname($path));
102
+            $parentType = $this->filetype($parent);
103
+            if ($parentType === false) {
104
+                if (!$this->mkdir($parent)) {
105
+                    // something went wrong
106
+                    return false;
107
+                }
108
+            } elseif ($parentType === 'file') {
109
+                // parent is a file
110
+                return false;
111
+            }
112
+            // finally create the new dir
113
+            $mTime = time(); // update mtime
114
+            $data['mtime'] = $mTime;
115
+            $data['storage_mtime'] = $mTime;
116
+            $data['etag'] = $this->getETag($path);
117
+            $this->getCache()->put($path, $data);
118
+            return true;
119
+        }
120
+    }
121
+
122
+    /**
123
+     * @param string $path
124
+     * @return string
125
+     */
126
+    private function normalizePath($path) {
127
+        $path = trim($path, '/');
128
+        //FIXME why do we sometimes get a path like 'files//username'?
129
+        $path = str_replace('//', '/', $path);
130
+
131
+        // dirname('/folder') returns '.' but internally (in the cache) we store the root as ''
132
+        if (!$path || $path === '.') {
133
+            $path = '';
134
+        }
135
+
136
+        return $path;
137
+    }
138
+
139
+    /**
140
+     * Object Stores use a NoopScanner because metadata is directly stored in
141
+     * the file cache and cannot really scan the filesystem. The storage passed in is not used anywhere.
142
+     *
143
+     * @param string $path
144
+     * @param \OC\Files\Storage\Storage (optional) the storage to pass to the scanner
145
+     * @return \OC\Files\ObjectStore\NoopScanner
146
+     */
147
+    public function getScanner($path = '', $storage = null) {
148
+        if (!$storage) {
149
+            $storage = $this;
150
+        }
151
+        if (!isset($this->scanner)) {
152
+            $this->scanner = new NoopScanner($storage);
153
+        }
154
+        return $this->scanner;
155
+    }
156
+
157
+    public function getId() {
158
+        return $this->id;
159
+    }
160
+
161
+    public function rmdir($path) {
162
+        $path = $this->normalizePath($path);
163
+
164
+        if (!$this->is_dir($path)) {
165
+            return false;
166
+        }
167
+
168
+        if (!$this->rmObjects($path)) {
169
+            return false;
170
+        }
171
+
172
+        $this->getCache()->remove($path);
173
+
174
+        return true;
175
+    }
176
+
177
+    private function rmObjects($path) {
178
+        $children = $this->getCache()->getFolderContents($path);
179
+        foreach ($children as $child) {
180
+            if ($child['mimetype'] === 'httpd/unix-directory') {
181
+                if (!$this->rmObjects($child['path'])) {
182
+                    return false;
183
+                }
184
+            } else {
185
+                if (!$this->unlink($child['path'])) {
186
+                    return false;
187
+                }
188
+            }
189
+        }
190
+
191
+        return true;
192
+    }
193
+
194
+    public function unlink($path) {
195
+        $path = $this->normalizePath($path);
196
+        $stat = $this->stat($path);
197
+
198
+        if ($stat && isset($stat['fileid'])) {
199
+            if ($stat['mimetype'] === 'httpd/unix-directory') {
200
+                return $this->rmdir($path);
201
+            }
202
+            try {
203
+                $this->objectStore->deleteObject($this->getURN($stat['fileid']));
204
+            } catch (\Exception $ex) {
205
+                if ($ex->getCode() !== 404) {
206
+                    $this->logger->logException($ex, [
207
+                        'app' => 'objectstore',
208
+                        'message' => 'Could not delete object ' . $this->getURN($stat['fileid']) . ' for ' . $path,
209
+                    ]);
210
+                    return false;
211
+                }
212
+                //removing from cache is ok as it does not exist in the objectstore anyway
213
+            }
214
+            $this->getCache()->remove($path);
215
+            return true;
216
+        }
217
+        return false;
218
+    }
219
+
220
+    public function stat($path) {
221
+        $path = $this->normalizePath($path);
222
+        $cacheEntry = $this->getCache()->get($path);
223
+        if ($cacheEntry instanceof CacheEntry) {
224
+            return $cacheEntry->getData();
225
+        } else {
226
+            return false;
227
+        }
228
+    }
229
+
230
+    public function getPermissions($path) {
231
+        $stat = $this->stat($path);
232
+
233
+        if (is_array($stat) && isset($stat['permissions'])) {
234
+            return $stat['permissions'];
235
+        }
236
+
237
+        return parent::getPermissions($path);
238
+    }
239
+
240
+    /**
241
+     * Override this method if you need a different unique resource identifier for your object storage implementation.
242
+     * The default implementations just appends the fileId to 'urn:oid:'. Make sure the URN is unique over all users.
243
+     * You may need a mapping table to store your URN if it cannot be generated from the fileid.
244
+     *
245
+     * @param int $fileId the fileid
246
+     * @return null|string the unified resource name used to identify the object
247
+     */
248
+    public function getURN($fileId) {
249
+        if (is_numeric($fileId)) {
250
+            return $this->objectPrefix . $fileId;
251
+        }
252
+        return null;
253
+    }
254
+
255
+    public function opendir($path) {
256
+        $path = $this->normalizePath($path);
257
+
258
+        try {
259
+            $files = [];
260
+            $folderContents = $this->getCache()->getFolderContents($path);
261
+            foreach ($folderContents as $file) {
262
+                $files[] = $file['name'];
263
+            }
264
+
265
+            return IteratorDirectory::wrap($files);
266
+        } catch (\Exception $e) {
267
+            $this->logger->logException($e);
268
+            return false;
269
+        }
270
+    }
271
+
272
+    public function filetype($path) {
273
+        $path = $this->normalizePath($path);
274
+        $stat = $this->stat($path);
275
+        if ($stat) {
276
+            if ($stat['mimetype'] === 'httpd/unix-directory') {
277
+                return 'dir';
278
+            }
279
+            return 'file';
280
+        } else {
281
+            return false;
282
+        }
283
+    }
284
+
285
+    public function fopen($path, $mode) {
286
+        $path = $this->normalizePath($path);
287
+
288
+        if (strrpos($path, '.') !== false) {
289
+            $ext = substr($path, strrpos($path, '.'));
290
+        } else {
291
+            $ext = '';
292
+        }
293
+
294
+        switch ($mode) {
295
+            case 'r':
296
+            case 'rb':
297
+                $stat = $this->stat($path);
298
+                if (is_array($stat)) {
299
+                    // Reading 0 sized files is a waste of time
300
+                    if (isset($stat['size']) && $stat['size'] === 0) {
301
+                        return fopen('php://memory', $mode);
302
+                    }
303
+
304
+                    try {
305
+                        return $this->objectStore->readObject($this->getURN($stat['fileid']));
306
+                    } catch (NotFoundException $e) {
307
+                        $this->logger->logException($e, [
308
+                            'app' => 'objectstore',
309
+                            'message' => 'Could not get object ' . $this->getURN($stat['fileid']) . ' for file ' . $path,
310
+                        ]);
311
+                        throw $e;
312
+                    } catch (\Exception $ex) {
313
+                        $this->logger->logException($ex, [
314
+                            'app' => 'objectstore',
315
+                            'message' => 'Could not get object ' . $this->getURN($stat['fileid']) . ' for file ' . $path,
316
+                        ]);
317
+                        return false;
318
+                    }
319
+                } else {
320
+                    return false;
321
+                }
322
+                // no break
323
+            case 'w':
324
+            case 'wb':
325
+            case 'w+':
326
+            case 'wb+':
327
+                $tmpFile = \OC::$server->getTempManager()->getTemporaryFile($ext);
328
+                $handle = fopen($tmpFile, $mode);
329
+                return CallbackWrapper::wrap($handle, null, null, function () use ($path, $tmpFile) {
330
+                    $this->writeBack($tmpFile, $path);
331
+                });
332
+            case 'a':
333
+            case 'ab':
334
+            case 'r+':
335
+            case 'a+':
336
+            case 'x':
337
+            case 'x+':
338
+            case 'c':
339
+            case 'c+':
340
+                $tmpFile = \OC::$server->getTempManager()->getTemporaryFile($ext);
341
+                if ($this->file_exists($path)) {
342
+                    $source = $this->fopen($path, 'r');
343
+                    file_put_contents($tmpFile, $source);
344
+                }
345
+                $handle = fopen($tmpFile, $mode);
346
+                return CallbackWrapper::wrap($handle, null, null, function () use ($path, $tmpFile) {
347
+                    $this->writeBack($tmpFile, $path);
348
+                });
349
+        }
350
+        return false;
351
+    }
352
+
353
+    public function file_exists($path) {
354
+        $path = $this->normalizePath($path);
355
+        return (bool)$this->stat($path);
356
+    }
357
+
358
+    public function rename($source, $target) {
359
+        $source = $this->normalizePath($source);
360
+        $target = $this->normalizePath($target);
361
+        $this->remove($target);
362
+        $this->getCache()->move($source, $target);
363
+        $this->touch(dirname($target));
364
+        return true;
365
+    }
366
+
367
+    public function getMimeType($path) {
368
+        $path = $this->normalizePath($path);
369
+        return parent::getMimeType($path);
370
+    }
371
+
372
+    public function touch($path, $mtime = null) {
373
+        if (is_null($mtime)) {
374
+            $mtime = time();
375
+        }
376
+
377
+        $path = $this->normalizePath($path);
378
+        $dirName = dirname($path);
379
+        $parentExists = $this->is_dir($dirName);
380
+        if (!$parentExists) {
381
+            return false;
382
+        }
383
+
384
+        $stat = $this->stat($path);
385
+        if (is_array($stat)) {
386
+            // update existing mtime in db
387
+            $stat['mtime'] = $mtime;
388
+            $this->getCache()->update($stat['fileid'], $stat);
389
+        } else {
390
+            try {
391
+                //create a empty file, need to have at least on char to make it
392
+                // work with all object storage implementations
393
+                $this->file_put_contents($path, ' ');
394
+                $mimeType = \OC::$server->getMimeTypeDetector()->detectPath($path);
395
+                $stat = [
396
+                    'etag' => $this->getETag($path),
397
+                    'mimetype' => $mimeType,
398
+                    'size' => 0,
399
+                    'mtime' => $mtime,
400
+                    'storage_mtime' => $mtime,
401
+                    'permissions' => \OCP\Constants::PERMISSION_ALL - \OCP\Constants::PERMISSION_CREATE,
402
+                ];
403
+                $this->getCache()->put($path, $stat);
404
+            } catch (\Exception $ex) {
405
+                $this->logger->logException($ex, [
406
+                    'app' => 'objectstore',
407
+                    'message' => 'Could not create object for ' . $path,
408
+                ]);
409
+                throw $ex;
410
+            }
411
+        }
412
+        return true;
413
+    }
414
+
415
+    public function writeBack($tmpFile, $path) {
416
+        $size = filesize($tmpFile);
417
+        $this->writeStream($path, fopen($tmpFile, 'r'), $size);
418
+    }
419
+
420
+    /**
421
+     * external changes are not supported, exclusive access to the object storage is assumed
422
+     *
423
+     * @param string $path
424
+     * @param int $time
425
+     * @return false
426
+     */
427
+    public function hasUpdated($path, $time) {
428
+        return false;
429
+    }
430
+
431
+    public function needsPartFile() {
432
+        return false;
433
+    }
434
+
435
+    public function file_put_contents($path, $data) {
436
+        $handle = $this->fopen($path, 'w+');
437
+        fwrite($handle, $data);
438
+        fclose($handle);
439
+        return true;
440
+    }
441
+
442
+    public function writeStream(string $path, $stream, int $size = null): int {
443
+        $stat = $this->stat($path);
444
+        if (empty($stat)) {
445
+            // create new file
446
+            $stat = [
447
+                'permissions' => \OCP\Constants::PERMISSION_ALL - \OCP\Constants::PERMISSION_CREATE,
448
+            ];
449
+        }
450
+        // update stat with new data
451
+        $mTime = time();
452
+        $stat['size'] = (int)$size;
453
+        $stat['mtime'] = $mTime;
454
+        $stat['storage_mtime'] = $mTime;
455
+
456
+        $mimetypeDetector = \OC::$server->getMimeTypeDetector();
457
+        $mimetype = $mimetypeDetector->detectPath($path);
458
+
459
+        $stat['mimetype'] = $mimetype;
460
+        $stat['etag'] = $this->getETag($path);
461
+
462
+        $exists = $this->getCache()->inCache($path);
463
+        $uploadPath = $exists ? $path : $path . '.part';
464
+
465
+        if ($exists) {
466
+            $fileId = $stat['fileid'];
467
+        } else {
468
+            $fileId = $this->getCache()->put($uploadPath, $stat);
469
+        }
470
+
471
+        $urn = $this->getURN($fileId);
472
+        try {
473
+            //upload to object storage
474
+            if ($size === null) {
475
+                $countStream = CountWrapper::wrap($stream, function ($writtenSize) use ($fileId, &$size) {
476
+                    $this->getCache()->update($fileId, [
477
+                        'size' => $writtenSize
478
+                    ]);
479
+                    $size = $writtenSize;
480
+                });
481
+                $this->objectStore->writeObject($urn, $countStream);
482
+                if (is_resource($countStream)) {
483
+                    fclose($countStream);
484
+                }
485
+                $stat['size'] = $size;
486
+            } else {
487
+                $this->objectStore->writeObject($urn, $stream);
488
+            }
489
+        } catch (\Exception $ex) {
490
+            if (!$exists) {
491
+                /*
492 492
 				 * Only remove the entry if we are dealing with a new file.
493 493
 				 * Else people lose access to existing files
494 494
 				 */
495
-				$this->getCache()->remove($uploadPath);
496
-				$this->logger->logException($ex, [
497
-					'app' => 'objectstore',
498
-					'message' => 'Could not create object ' . $urn . ' for ' . $path,
499
-				]);
500
-			} else {
501
-				$this->logger->logException($ex, [
502
-					'app' => 'objectstore',
503
-					'message' => 'Could not update object ' . $urn . ' for ' . $path,
504
-				]);
505
-			}
506
-			throw $ex; // make this bubble up
507
-		}
508
-
509
-		if ($exists) {
510
-			$this->getCache()->update($fileId, $stat);
511
-		} else {
512
-			if ($this->objectStore->objectExists($urn)) {
513
-				$this->getCache()->move($uploadPath, $path);
514
-			} else {
515
-				$this->getCache()->remove($uploadPath);
516
-				throw new \Exception("Object not found after writing (urn: $urn, path: $path)", 404);
517
-			}
518
-		}
519
-
520
-		return $size;
521
-	}
522
-
523
-	public function getObjectStore(): IObjectStore {
524
-		return $this->objectStore;
525
-	}
495
+                $this->getCache()->remove($uploadPath);
496
+                $this->logger->logException($ex, [
497
+                    'app' => 'objectstore',
498
+                    'message' => 'Could not create object ' . $urn . ' for ' . $path,
499
+                ]);
500
+            } else {
501
+                $this->logger->logException($ex, [
502
+                    'app' => 'objectstore',
503
+                    'message' => 'Could not update object ' . $urn . ' for ' . $path,
504
+                ]);
505
+            }
506
+            throw $ex; // make this bubble up
507
+        }
508
+
509
+        if ($exists) {
510
+            $this->getCache()->update($fileId, $stat);
511
+        } else {
512
+            if ($this->objectStore->objectExists($urn)) {
513
+                $this->getCache()->move($uploadPath, $path);
514
+            } else {
515
+                $this->getCache()->remove($uploadPath);
516
+                throw new \Exception("Object not found after writing (urn: $urn, path: $path)", 404);
517
+            }
518
+        }
519
+
520
+        return $size;
521
+    }
522
+
523
+    public function getObjectStore(): IObjectStore {
524
+        return $this->objectStore;
525
+    }
526 526
 }
Please login to merge, or discard this patch.