Passed
Push — master ( 993609...7fc68f )
by Morris
29:30 queued 18:18
created
apps/files_external/lib/Lib/Storage/AmazonS3.php 2 patches
Indentation   +608 added lines, -608 removed lines patch added patch discarded remove patch
@@ -46,613 +46,613 @@
 block discarded – undo
46 46
 use OCP\Constants;
47 47
 
48 48
 class AmazonS3 extends \OC\Files\Storage\Common {
49
-	use S3ConnectionTrait;
50
-	use S3ObjectTrait;
51
-
52
-	public function needsPartFile() {
53
-		return false;
54
-	}
55
-
56
-	/**
57
-	 * @var int in seconds
58
-	 */
59
-	private $rescanDelay = 10;
60
-
61
-	/** @var CappedMemoryCache|Result[] */
62
-	private $objectCache;
63
-
64
-	/** @var CappedMemoryCache|array */
65
-	private $filesCache;
66
-
67
-	public function __construct($parameters) {
68
-		parent::__construct($parameters);
69
-		$this->parseParams($parameters);
70
-		$this->objectCache = new CappedMemoryCache();
71
-		$this->filesCache = new CappedMemoryCache();
72
-	}
73
-
74
-	/**
75
-	 * @param string $path
76
-	 * @return string correctly encoded path
77
-	 */
78
-	private function normalizePath($path) {
79
-		$path = trim($path, '/');
80
-
81
-		if (!$path) {
82
-			$path = '.';
83
-		}
84
-
85
-		return $path;
86
-	}
87
-
88
-	private function isRoot($path) {
89
-		return $path === '.';
90
-	}
91
-
92
-	private function cleanKey($path) {
93
-		if ($this->isRoot($path)) {
94
-			return '/';
95
-		}
96
-		return $path;
97
-	}
98
-
99
-	private function clearCache() {
100
-		$this->objectCache = new CappedMemoryCache();
101
-		$this->filesCache = new CappedMemoryCache();
102
-	}
103
-
104
-	private function invalidateCache($key) {
105
-		unset($this->objectCache[$key]);
106
-		$keys = array_keys($this->objectCache->getData());
107
-		$keyLength = strlen($key);
108
-		foreach ($keys as $existingKey) {
109
-			if (substr($existingKey, 0, $keyLength) === $key) {
110
-				unset($this->objectCache[$existingKey]);
111
-			}
112
-		}
113
-		unset($this->filesCache[$key]);
114
-	}
115
-
116
-	/**
117
-	 * @param $key
118
-	 * @return Result|boolean
119
-	 */
120
-	private function headObject($key) {
121
-		if (!isset($this->objectCache[$key])) {
122
-			try {
123
-				$this->objectCache[$key] = $this->getConnection()->headObject(array(
124
-					'Bucket' => $this->bucket,
125
-					'Key' => $key
126
-				));
127
-			} catch (S3Exception $e) {
128
-				if ($e->getStatusCode() >= 500) {
129
-					throw $e;
130
-				}
131
-				$this->objectCache[$key] = false;
132
-			}
133
-		}
134
-
135
-		return $this->objectCache[$key];
136
-	}
137
-
138
-	/**
139
-	 * Updates old storage ids (v0.2.1 and older) that are based on key and secret to new ones based on the bucket name.
140
-	 * TODO Do this in an update.php. requires iterating over all users and loading the mount.json from their home
141
-	 *
142
-	 * @param array $params
143
-	 */
144
-	public function updateLegacyId(array $params) {
145
-		$oldId = 'amazon::' . $params['key'] . md5($params['secret']);
146
-
147
-		// find by old id or bucket
148
-		$stmt = \OC::$server->getDatabaseConnection()->prepare(
149
-			'SELECT `numeric_id`, `id` FROM `*PREFIX*storages` WHERE `id` IN (?, ?)'
150
-		);
151
-		$stmt->execute(array($oldId, $this->id));
152
-		while ($row = $stmt->fetch()) {
153
-			$storages[$row['id']] = $row['numeric_id'];
154
-		}
155
-
156
-		if (isset($storages[$this->id]) && isset($storages[$oldId])) {
157
-			// if both ids exist, delete the old storage and corresponding filecache entries
158
-			\OC\Files\Cache\Storage::remove($oldId);
159
-		} else if (isset($storages[$oldId])) {
160
-			// if only the old id exists do an update
161
-			$stmt = \OC::$server->getDatabaseConnection()->prepare(
162
-				'UPDATE `*PREFIX*storages` SET `id` = ? WHERE `id` = ?'
163
-			);
164
-			$stmt->execute(array($this->id, $oldId));
165
-		}
166
-		// only the bucket based id may exist, do nothing
167
-	}
168
-
169
-	/**
170
-	 * Remove a file or folder
171
-	 *
172
-	 * @param string $path
173
-	 * @return bool
174
-	 */
175
-	protected function remove($path) {
176
-		// remember fileType to reduce http calls
177
-		$fileType = $this->filetype($path);
178
-		if ($fileType === 'dir') {
179
-			return $this->rmdir($path);
180
-		} else if ($fileType === 'file') {
181
-			return $this->unlink($path);
182
-		} else {
183
-			return false;
184
-		}
185
-	}
186
-
187
-	public function mkdir($path) {
188
-		$path = $this->normalizePath($path);
189
-
190
-		if ($this->is_dir($path)) {
191
-			return false;
192
-		}
193
-
194
-		try {
195
-			$this->getConnection()->putObject(array(
196
-				'Bucket' => $this->bucket,
197
-				'Key' => $path . '/',
198
-				'Body' => '',
199
-				'ContentType' => 'httpd/unix-directory'
200
-			));
201
-			$this->testTimeout();
202
-		} catch (S3Exception $e) {
203
-			\OC::$server->getLogger()->logException($e, ['app' => 'files_external']);
204
-			return false;
205
-		}
206
-
207
-		$this->invalidateCache($path);
208
-
209
-		return true;
210
-	}
211
-
212
-	public function file_exists($path) {
213
-		return $this->filetype($path) !== false;
214
-	}
215
-
216
-
217
-	public function rmdir($path) {
218
-		$path = $this->normalizePath($path);
219
-
220
-		if ($this->isRoot($path)) {
221
-			return $this->clearBucket();
222
-		}
223
-
224
-		if (!$this->file_exists($path)) {
225
-			return false;
226
-		}
227
-
228
-		$this->invalidateCache($path);
229
-		return $this->batchDelete($path);
230
-	}
231
-
232
-	protected function clearBucket() {
233
-		$this->clearCache();
234
-		try {
235
-			$this->getConnection()->clearBucket($this->bucket);
236
-			return true;
237
-			// clearBucket() is not working with Ceph, so if it fails we try the slower approach
238
-		} catch (\Exception $e) {
239
-			return $this->batchDelete();
240
-		}
241
-	}
242
-
243
-	private function batchDelete($path = null) {
244
-		$params = array(
245
-			'Bucket' => $this->bucket
246
-		);
247
-		if ($path !== null) {
248
-			$params['Prefix'] = $path . '/';
249
-		}
250
-		try {
251
-			$connection = $this->getConnection();
252
-			// Since there are no real directories on S3, we need
253
-			// to delete all objects prefixed with the path.
254
-			do {
255
-				// instead of the iterator, manually loop over the list ...
256
-				$objects = $connection->listObjects($params);
257
-				// ... so we can delete the files in batches
258
-				if (isset($objects['Contents'])) {
259
-					$connection->deleteObjects([
260
-						'Bucket' => $this->bucket,
261
-						'Delete' => [
262
-							'Objects' => $objects['Contents']
263
-						]
264
-					]);
265
-					$this->testTimeout();
266
-				}
267
-				// we reached the end when the list is no longer truncated
268
-			} while ($objects['IsTruncated']);
269
-		} catch (S3Exception $e) {
270
-			\OC::$server->getLogger()->logException($e, ['app' => 'files_external']);
271
-			return false;
272
-		}
273
-		return true;
274
-	}
275
-
276
-	public function opendir($path) {
277
-		$path = $this->normalizePath($path);
278
-
279
-		if ($this->isRoot($path)) {
280
-			$path = '';
281
-		} else {
282
-			$path .= '/';
283
-		}
284
-
285
-		try {
286
-			$files = array();
287
-			$results = $this->getConnection()->getPaginator('ListObjects', [
288
-				'Bucket' => $this->bucket,
289
-				'Delimiter' => '/',
290
-				'Prefix' => $path,
291
-			]);
292
-
293
-			foreach ($results as $result) {
294
-				// sub folders
295
-				if (is_array($result['CommonPrefixes'])) {
296
-					foreach ($result['CommonPrefixes'] as $prefix) {
297
-						$files[] = substr(trim($prefix['Prefix'], '/'), strlen($path));
298
-					}
299
-				}
300
-				if (is_array($result['Contents'])) {
301
-					foreach ($result['Contents'] as $object) {
302
-						if (isset($object['Key']) && $object['Key'] === $path) {
303
-							// it's the directory itself, skip
304
-							continue;
305
-						}
306
-						$file = basename(
307
-							isset($object['Key']) ? $object['Key'] : $object['Prefix']
308
-						);
309
-						$files[] = $file;
310
-
311
-						// store this information for later usage
312
-						$this->filesCache[$path . $file] = [
313
-							'ContentLength' => $object['Size'],
314
-							'LastModified' => (string)$object['LastModified'],
315
-						];
316
-					}
317
-				}
318
-			}
319
-
320
-			return IteratorDirectory::wrap($files);
321
-		} catch (S3Exception $e) {
322
-			\OC::$server->getLogger()->logException($e, ['app' => 'files_external']);
323
-			return false;
324
-		}
325
-	}
326
-
327
-	public function stat($path) {
328
-		$path = $this->normalizePath($path);
329
-
330
-		try {
331
-			$stat = [];
332
-			if ($this->is_dir($path)) {
333
-				//folders don't really exist
334
-				$stat['size'] = -1; //unknown
335
-				$stat['mtime'] = time() - $this->rescanDelay * 1000;
336
-			} else {
337
-				$stat['size'] = $this->getContentLength($path);
338
-				$stat['mtime'] = strtotime($this->getLastModified($path));
339
-			}
340
-			$stat['atime'] = time();
341
-
342
-			return $stat;
343
-		} catch (S3Exception $e) {
344
-			\OC::$server->getLogger()->logException($e, ['app' => 'files_external']);
345
-			return false;
346
-		}
347
-	}
348
-
349
-	/**
350
-	 * Return content length for object
351
-	 *
352
-	 * When the information is already present (e.g. opendir has been called before)
353
-	 * this value is return. Otherwise a headObject is emitted.
354
-	 *
355
-	 * @param $path
356
-	 * @return int|mixed
357
-	 */
358
-	private function getContentLength($path) {
359
-		if (isset($this->filesCache[$path])) {
360
-			return $this->filesCache[$path]['ContentLength'];
361
-		}
362
-
363
-		$result = $this->headObject($path);
364
-		if (isset($result['ContentLength'])) {
365
-			return $result['ContentLength'];
366
-		}
367
-
368
-		return 0;
369
-	}
370
-
371
-	/**
372
-	 * Return last modified for object
373
-	 *
374
-	 * When the information is already present (e.g. opendir has been called before)
375
-	 * this value is return. Otherwise a headObject is emitted.
376
-	 *
377
-	 * @param $path
378
-	 * @return mixed|string
379
-	 */
380
-	private function getLastModified($path) {
381
-		if (isset($this->filesCache[$path])) {
382
-			return $this->filesCache[$path]['LastModified'];
383
-		}
384
-
385
-		$result = $this->headObject($path);
386
-		if (isset($result['LastModified'])) {
387
-			return $result['LastModified'];
388
-		}
389
-
390
-		return 'now';
391
-	}
392
-
393
-	public function is_dir($path) {
394
-		$path = $this->normalizePath($path);
395
-		try {
396
-			return $this->isRoot($path) || $this->headObject($path . '/');
397
-		} catch (S3Exception $e) {
398
-			\OC::$server->getLogger()->logException($e, ['app' => 'files_external']);
399
-			return false;
400
-		}
401
-	}
402
-
403
-	public function filetype($path) {
404
-		$path = $this->normalizePath($path);
405
-
406
-		if ($this->isRoot($path)) {
407
-			return 'dir';
408
-		}
409
-
410
-		try {
411
-			if (isset($this->filesCache[$path]) || $this->headObject($path)) {
412
-				return 'file';
413
-			}
414
-			if ($this->headObject($path . '/')) {
415
-				return 'dir';
416
-			}
417
-		} catch (S3Exception $e) {
418
-			\OC::$server->getLogger()->logException($e, ['app' => 'files_external']);
419
-			return false;
420
-		}
421
-
422
-		return false;
423
-	}
424
-
425
-	public function getPermissions($path) {
426
-		$type = $this->filetype($path);
427
-		if (!$type) {
428
-			return 0;
429
-		}
430
-		return $type === 'dir' ? Constants::PERMISSION_ALL : Constants::PERMISSION_ALL - Constants::PERMISSION_CREATE;
431
-	}
432
-
433
-	public function unlink($path) {
434
-		$path = $this->normalizePath($path);
435
-
436
-		if ($this->is_dir($path)) {
437
-			return $this->rmdir($path);
438
-		}
439
-
440
-		try {
441
-			$this->deleteObject($path);
442
-			$this->invalidateCache($path);
443
-		} catch (S3Exception $e) {
444
-			\OC::$server->getLogger()->logException($e, ['app' => 'files_external']);
445
-			return false;
446
-		}
447
-
448
-		return true;
449
-	}
450
-
451
-	public function fopen($path, $mode) {
452
-		$path = $this->normalizePath($path);
453
-
454
-		switch ($mode) {
455
-			case 'r':
456
-			case 'rb':
457
-				try {
458
-					return $this->readObject($path);
459
-				} catch (S3Exception $e) {
460
-					\OC::$server->getLogger()->logException($e, ['app' => 'files_external']);
461
-					return false;
462
-				}
463
-			case 'w':
464
-			case 'wb':
465
-				$tmpFile = \OC::$server->getTempManager()->getTemporaryFile();
466
-
467
-				$handle = fopen($tmpFile, 'w');
468
-				return CallbackWrapper::wrap($handle, null, null, function () use ($path, $tmpFile) {
469
-					$this->writeBack($tmpFile, $path);
470
-				});
471
-			case 'a':
472
-			case 'ab':
473
-			case 'r+':
474
-			case 'w+':
475
-			case 'wb+':
476
-			case 'a+':
477
-			case 'x':
478
-			case 'x+':
479
-			case 'c':
480
-			case 'c+':
481
-				if (strrpos($path, '.') !== false) {
482
-					$ext = substr($path, strrpos($path, '.'));
483
-				} else {
484
-					$ext = '';
485
-				}
486
-				$tmpFile = \OC::$server->getTempManager()->getTemporaryFile($ext);
487
-				if ($this->file_exists($path)) {
488
-					$source = $this->readObject($path);
489
-					file_put_contents($tmpFile, $source);
490
-				}
491
-
492
-				$handle = fopen($tmpFile, $mode);
493
-				return CallbackWrapper::wrap($handle, null, null, function () use ($path, $tmpFile) {
494
-					$this->writeBack($tmpFile, $path);
495
-				});
496
-		}
497
-		return false;
498
-	}
499
-
500
-	public function touch($path, $mtime = null) {
501
-		$path = $this->normalizePath($path);
502
-
503
-		$metadata = array();
504
-		if (is_null($mtime)) {
505
-			$mtime = time();
506
-		}
507
-		$metadata = [
508
-			'lastmodified' => gmdate(\DateTime::RFC1123, $mtime)
509
-		];
510
-
511
-		$fileType = $this->filetype($path);
512
-		try {
513
-			if ($fileType !== false) {
514
-				if ($fileType === 'dir' && !$this->isRoot($path)) {
515
-					$path .= '/';
516
-				}
517
-				$this->getConnection()->copyObject([
518
-					'Bucket' => $this->bucket,
519
-					'Key' => $this->cleanKey($path),
520
-					'Metadata' => $metadata,
521
-					'CopySource' => $this->bucket . '/' . $path,
522
-					'MetadataDirective' => 'REPLACE',
523
-				]);
524
-				$this->testTimeout();
525
-			} else {
526
-				$mimeType = \OC::$server->getMimeTypeDetector()->detectPath($path);
527
-				$this->getConnection()->putObject([
528
-					'Bucket' => $this->bucket,
529
-					'Key' => $this->cleanKey($path),
530
-					'Metadata' => $metadata,
531
-					'Body' => '',
532
-					'ContentType' => $mimeType,
533
-					'MetadataDirective' => 'REPLACE',
534
-				]);
535
-				$this->testTimeout();
536
-			}
537
-		} catch (S3Exception $e) {
538
-			\OC::$server->getLogger()->logException($e, ['app' => 'files_external']);
539
-			return false;
540
-		}
541
-
542
-		$this->invalidateCache($path);
543
-		return true;
544
-	}
545
-
546
-	public function copy($path1, $path2) {
547
-		$path1 = $this->normalizePath($path1);
548
-		$path2 = $this->normalizePath($path2);
549
-
550
-		if ($this->is_file($path1)) {
551
-			try {
552
-				$this->getConnection()->copyObject(array(
553
-					'Bucket' => $this->bucket,
554
-					'Key' => $this->cleanKey($path2),
555
-					'CopySource' => S3Client::encodeKey($this->bucket . '/' . $path1)
556
-				));
557
-				$this->testTimeout();
558
-			} catch (S3Exception $e) {
559
-				\OC::$server->getLogger()->logException($e, ['app' => 'files_external']);
560
-				return false;
561
-			}
562
-		} else {
563
-			$this->remove($path2);
564
-
565
-			try {
566
-				$this->getConnection()->copyObject(array(
567
-					'Bucket' => $this->bucket,
568
-					'Key' => $path2 . '/',
569
-					'CopySource' => S3Client::encodeKey($this->bucket . '/' . $path1 . '/')
570
-				));
571
-				$this->testTimeout();
572
-			} catch (S3Exception $e) {
573
-				\OC::$server->getLogger()->logException($e, ['app' => 'files_external']);
574
-				return false;
575
-			}
576
-
577
-			$dh = $this->opendir($path1);
578
-			if (is_resource($dh)) {
579
-				while (($file = readdir($dh)) !== false) {
580
-					if (\OC\Files\Filesystem::isIgnoredDir($file)) {
581
-						continue;
582
-					}
583
-
584
-					$source = $path1 . '/' . $file;
585
-					$target = $path2 . '/' . $file;
586
-					$this->copy($source, $target);
587
-				}
588
-			}
589
-		}
590
-
591
-		$this->invalidateCache($path2);
592
-
593
-		return true;
594
-	}
595
-
596
-	public function rename($path1, $path2) {
597
-		$path1 = $this->normalizePath($path1);
598
-		$path2 = $this->normalizePath($path2);
599
-
600
-		if ($this->is_file($path1)) {
601
-
602
-			if ($this->copy($path1, $path2) === false) {
603
-				return false;
604
-			}
605
-
606
-			if ($this->unlink($path1) === false) {
607
-				$this->unlink($path2);
608
-				return false;
609
-			}
610
-		} else {
611
-
612
-			if ($this->copy($path1, $path2) === false) {
613
-				return false;
614
-			}
615
-
616
-			if ($this->rmdir($path1) === false) {
617
-				$this->rmdir($path2);
618
-				return false;
619
-			}
620
-		}
621
-
622
-		return true;
623
-	}
624
-
625
-	public function test() {
626
-		$this->getConnection()->headBucket([
627
-			'Bucket' => $this->bucket
628
-		]);
629
-		return true;
630
-	}
631
-
632
-	public function getId() {
633
-		return $this->id;
634
-	}
635
-
636
-	public function writeBack($tmpFile, $path) {
637
-		try {
638
-			$source = fopen($tmpFile, 'r');
639
-			$this->writeObject($path, $source);
640
-			$this->invalidateCache($path);
641
-			fclose($source);
642
-
643
-			unlink($tmpFile);
644
-			return true;
645
-		} catch (S3Exception $e) {
646
-			\OC::$server->getLogger()->logException($e, ['app' => 'files_external']);
647
-			return false;
648
-		}
649
-	}
650
-
651
-	/**
652
-	 * check if curl is installed
653
-	 */
654
-	public static function checkDependencies() {
655
-		return true;
656
-	}
49
+    use S3ConnectionTrait;
50
+    use S3ObjectTrait;
51
+
52
+    public function needsPartFile() {
53
+        return false;
54
+    }
55
+
56
+    /**
57
+     * @var int in seconds
58
+     */
59
+    private $rescanDelay = 10;
60
+
61
+    /** @var CappedMemoryCache|Result[] */
62
+    private $objectCache;
63
+
64
+    /** @var CappedMemoryCache|array */
65
+    private $filesCache;
66
+
67
+    public function __construct($parameters) {
68
+        parent::__construct($parameters);
69
+        $this->parseParams($parameters);
70
+        $this->objectCache = new CappedMemoryCache();
71
+        $this->filesCache = new CappedMemoryCache();
72
+    }
73
+
74
+    /**
75
+     * @param string $path
76
+     * @return string correctly encoded path
77
+     */
78
+    private function normalizePath($path) {
79
+        $path = trim($path, '/');
80
+
81
+        if (!$path) {
82
+            $path = '.';
83
+        }
84
+
85
+        return $path;
86
+    }
87
+
88
+    private function isRoot($path) {
89
+        return $path === '.';
90
+    }
91
+
92
+    private function cleanKey($path) {
93
+        if ($this->isRoot($path)) {
94
+            return '/';
95
+        }
96
+        return $path;
97
+    }
98
+
99
+    private function clearCache() {
100
+        $this->objectCache = new CappedMemoryCache();
101
+        $this->filesCache = new CappedMemoryCache();
102
+    }
103
+
104
+    private function invalidateCache($key) {
105
+        unset($this->objectCache[$key]);
106
+        $keys = array_keys($this->objectCache->getData());
107
+        $keyLength = strlen($key);
108
+        foreach ($keys as $existingKey) {
109
+            if (substr($existingKey, 0, $keyLength) === $key) {
110
+                unset($this->objectCache[$existingKey]);
111
+            }
112
+        }
113
+        unset($this->filesCache[$key]);
114
+    }
115
+
116
+    /**
117
+     * @param $key
118
+     * @return Result|boolean
119
+     */
120
+    private function headObject($key) {
121
+        if (!isset($this->objectCache[$key])) {
122
+            try {
123
+                $this->objectCache[$key] = $this->getConnection()->headObject(array(
124
+                    'Bucket' => $this->bucket,
125
+                    'Key' => $key
126
+                ));
127
+            } catch (S3Exception $e) {
128
+                if ($e->getStatusCode() >= 500) {
129
+                    throw $e;
130
+                }
131
+                $this->objectCache[$key] = false;
132
+            }
133
+        }
134
+
135
+        return $this->objectCache[$key];
136
+    }
137
+
138
+    /**
139
+     * Updates old storage ids (v0.2.1 and older) that are based on key and secret to new ones based on the bucket name.
140
+     * TODO Do this in an update.php. requires iterating over all users and loading the mount.json from their home
141
+     *
142
+     * @param array $params
143
+     */
144
+    public function updateLegacyId(array $params) {
145
+        $oldId = 'amazon::' . $params['key'] . md5($params['secret']);
146
+
147
+        // find by old id or bucket
148
+        $stmt = \OC::$server->getDatabaseConnection()->prepare(
149
+            'SELECT `numeric_id`, `id` FROM `*PREFIX*storages` WHERE `id` IN (?, ?)'
150
+        );
151
+        $stmt->execute(array($oldId, $this->id));
152
+        while ($row = $stmt->fetch()) {
153
+            $storages[$row['id']] = $row['numeric_id'];
154
+        }
155
+
156
+        if (isset($storages[$this->id]) && isset($storages[$oldId])) {
157
+            // if both ids exist, delete the old storage and corresponding filecache entries
158
+            \OC\Files\Cache\Storage::remove($oldId);
159
+        } else if (isset($storages[$oldId])) {
160
+            // if only the old id exists do an update
161
+            $stmt = \OC::$server->getDatabaseConnection()->prepare(
162
+                'UPDATE `*PREFIX*storages` SET `id` = ? WHERE `id` = ?'
163
+            );
164
+            $stmt->execute(array($this->id, $oldId));
165
+        }
166
+        // only the bucket based id may exist, do nothing
167
+    }
168
+
169
+    /**
170
+     * Remove a file or folder
171
+     *
172
+     * @param string $path
173
+     * @return bool
174
+     */
175
+    protected function remove($path) {
176
+        // remember fileType to reduce http calls
177
+        $fileType = $this->filetype($path);
178
+        if ($fileType === 'dir') {
179
+            return $this->rmdir($path);
180
+        } else if ($fileType === 'file') {
181
+            return $this->unlink($path);
182
+        } else {
183
+            return false;
184
+        }
185
+    }
186
+
187
+    public function mkdir($path) {
188
+        $path = $this->normalizePath($path);
189
+
190
+        if ($this->is_dir($path)) {
191
+            return false;
192
+        }
193
+
194
+        try {
195
+            $this->getConnection()->putObject(array(
196
+                'Bucket' => $this->bucket,
197
+                'Key' => $path . '/',
198
+                'Body' => '',
199
+                'ContentType' => 'httpd/unix-directory'
200
+            ));
201
+            $this->testTimeout();
202
+        } catch (S3Exception $e) {
203
+            \OC::$server->getLogger()->logException($e, ['app' => 'files_external']);
204
+            return false;
205
+        }
206
+
207
+        $this->invalidateCache($path);
208
+
209
+        return true;
210
+    }
211
+
212
+    public function file_exists($path) {
213
+        return $this->filetype($path) !== false;
214
+    }
215
+
216
+
217
+    public function rmdir($path) {
218
+        $path = $this->normalizePath($path);
219
+
220
+        if ($this->isRoot($path)) {
221
+            return $this->clearBucket();
222
+        }
223
+
224
+        if (!$this->file_exists($path)) {
225
+            return false;
226
+        }
227
+
228
+        $this->invalidateCache($path);
229
+        return $this->batchDelete($path);
230
+    }
231
+
232
+    protected function clearBucket() {
233
+        $this->clearCache();
234
+        try {
235
+            $this->getConnection()->clearBucket($this->bucket);
236
+            return true;
237
+            // clearBucket() is not working with Ceph, so if it fails we try the slower approach
238
+        } catch (\Exception $e) {
239
+            return $this->batchDelete();
240
+        }
241
+    }
242
+
243
+    private function batchDelete($path = null) {
244
+        $params = array(
245
+            'Bucket' => $this->bucket
246
+        );
247
+        if ($path !== null) {
248
+            $params['Prefix'] = $path . '/';
249
+        }
250
+        try {
251
+            $connection = $this->getConnection();
252
+            // Since there are no real directories on S3, we need
253
+            // to delete all objects prefixed with the path.
254
+            do {
255
+                // instead of the iterator, manually loop over the list ...
256
+                $objects = $connection->listObjects($params);
257
+                // ... so we can delete the files in batches
258
+                if (isset($objects['Contents'])) {
259
+                    $connection->deleteObjects([
260
+                        'Bucket' => $this->bucket,
261
+                        'Delete' => [
262
+                            'Objects' => $objects['Contents']
263
+                        ]
264
+                    ]);
265
+                    $this->testTimeout();
266
+                }
267
+                // we reached the end when the list is no longer truncated
268
+            } while ($objects['IsTruncated']);
269
+        } catch (S3Exception $e) {
270
+            \OC::$server->getLogger()->logException($e, ['app' => 'files_external']);
271
+            return false;
272
+        }
273
+        return true;
274
+    }
275
+
276
+    public function opendir($path) {
277
+        $path = $this->normalizePath($path);
278
+
279
+        if ($this->isRoot($path)) {
280
+            $path = '';
281
+        } else {
282
+            $path .= '/';
283
+        }
284
+
285
+        try {
286
+            $files = array();
287
+            $results = $this->getConnection()->getPaginator('ListObjects', [
288
+                'Bucket' => $this->bucket,
289
+                'Delimiter' => '/',
290
+                'Prefix' => $path,
291
+            ]);
292
+
293
+            foreach ($results as $result) {
294
+                // sub folders
295
+                if (is_array($result['CommonPrefixes'])) {
296
+                    foreach ($result['CommonPrefixes'] as $prefix) {
297
+                        $files[] = substr(trim($prefix['Prefix'], '/'), strlen($path));
298
+                    }
299
+                }
300
+                if (is_array($result['Contents'])) {
301
+                    foreach ($result['Contents'] as $object) {
302
+                        if (isset($object['Key']) && $object['Key'] === $path) {
303
+                            // it's the directory itself, skip
304
+                            continue;
305
+                        }
306
+                        $file = basename(
307
+                            isset($object['Key']) ? $object['Key'] : $object['Prefix']
308
+                        );
309
+                        $files[] = $file;
310
+
311
+                        // store this information for later usage
312
+                        $this->filesCache[$path . $file] = [
313
+                            'ContentLength' => $object['Size'],
314
+                            'LastModified' => (string)$object['LastModified'],
315
+                        ];
316
+                    }
317
+                }
318
+            }
319
+
320
+            return IteratorDirectory::wrap($files);
321
+        } catch (S3Exception $e) {
322
+            \OC::$server->getLogger()->logException($e, ['app' => 'files_external']);
323
+            return false;
324
+        }
325
+    }
326
+
327
+    public function stat($path) {
328
+        $path = $this->normalizePath($path);
329
+
330
+        try {
331
+            $stat = [];
332
+            if ($this->is_dir($path)) {
333
+                //folders don't really exist
334
+                $stat['size'] = -1; //unknown
335
+                $stat['mtime'] = time() - $this->rescanDelay * 1000;
336
+            } else {
337
+                $stat['size'] = $this->getContentLength($path);
338
+                $stat['mtime'] = strtotime($this->getLastModified($path));
339
+            }
340
+            $stat['atime'] = time();
341
+
342
+            return $stat;
343
+        } catch (S3Exception $e) {
344
+            \OC::$server->getLogger()->logException($e, ['app' => 'files_external']);
345
+            return false;
346
+        }
347
+    }
348
+
349
+    /**
350
+     * Return content length for object
351
+     *
352
+     * When the information is already present (e.g. opendir has been called before)
353
+     * this value is return. Otherwise a headObject is emitted.
354
+     *
355
+     * @param $path
356
+     * @return int|mixed
357
+     */
358
+    private function getContentLength($path) {
359
+        if (isset($this->filesCache[$path])) {
360
+            return $this->filesCache[$path]['ContentLength'];
361
+        }
362
+
363
+        $result = $this->headObject($path);
364
+        if (isset($result['ContentLength'])) {
365
+            return $result['ContentLength'];
366
+        }
367
+
368
+        return 0;
369
+    }
370
+
371
+    /**
372
+     * Return last modified for object
373
+     *
374
+     * When the information is already present (e.g. opendir has been called before)
375
+     * this value is return. Otherwise a headObject is emitted.
376
+     *
377
+     * @param $path
378
+     * @return mixed|string
379
+     */
380
+    private function getLastModified($path) {
381
+        if (isset($this->filesCache[$path])) {
382
+            return $this->filesCache[$path]['LastModified'];
383
+        }
384
+
385
+        $result = $this->headObject($path);
386
+        if (isset($result['LastModified'])) {
387
+            return $result['LastModified'];
388
+        }
389
+
390
+        return 'now';
391
+    }
392
+
393
+    public function is_dir($path) {
394
+        $path = $this->normalizePath($path);
395
+        try {
396
+            return $this->isRoot($path) || $this->headObject($path . '/');
397
+        } catch (S3Exception $e) {
398
+            \OC::$server->getLogger()->logException($e, ['app' => 'files_external']);
399
+            return false;
400
+        }
401
+    }
402
+
403
+    public function filetype($path) {
404
+        $path = $this->normalizePath($path);
405
+
406
+        if ($this->isRoot($path)) {
407
+            return 'dir';
408
+        }
409
+
410
+        try {
411
+            if (isset($this->filesCache[$path]) || $this->headObject($path)) {
412
+                return 'file';
413
+            }
414
+            if ($this->headObject($path . '/')) {
415
+                return 'dir';
416
+            }
417
+        } catch (S3Exception $e) {
418
+            \OC::$server->getLogger()->logException($e, ['app' => 'files_external']);
419
+            return false;
420
+        }
421
+
422
+        return false;
423
+    }
424
+
425
+    public function getPermissions($path) {
426
+        $type = $this->filetype($path);
427
+        if (!$type) {
428
+            return 0;
429
+        }
430
+        return $type === 'dir' ? Constants::PERMISSION_ALL : Constants::PERMISSION_ALL - Constants::PERMISSION_CREATE;
431
+    }
432
+
433
+    public function unlink($path) {
434
+        $path = $this->normalizePath($path);
435
+
436
+        if ($this->is_dir($path)) {
437
+            return $this->rmdir($path);
438
+        }
439
+
440
+        try {
441
+            $this->deleteObject($path);
442
+            $this->invalidateCache($path);
443
+        } catch (S3Exception $e) {
444
+            \OC::$server->getLogger()->logException($e, ['app' => 'files_external']);
445
+            return false;
446
+        }
447
+
448
+        return true;
449
+    }
450
+
451
+    public function fopen($path, $mode) {
452
+        $path = $this->normalizePath($path);
453
+
454
+        switch ($mode) {
455
+            case 'r':
456
+            case 'rb':
457
+                try {
458
+                    return $this->readObject($path);
459
+                } catch (S3Exception $e) {
460
+                    \OC::$server->getLogger()->logException($e, ['app' => 'files_external']);
461
+                    return false;
462
+                }
463
+            case 'w':
464
+            case 'wb':
465
+                $tmpFile = \OC::$server->getTempManager()->getTemporaryFile();
466
+
467
+                $handle = fopen($tmpFile, 'w');
468
+                return CallbackWrapper::wrap($handle, null, null, function () use ($path, $tmpFile) {
469
+                    $this->writeBack($tmpFile, $path);
470
+                });
471
+            case 'a':
472
+            case 'ab':
473
+            case 'r+':
474
+            case 'w+':
475
+            case 'wb+':
476
+            case 'a+':
477
+            case 'x':
478
+            case 'x+':
479
+            case 'c':
480
+            case 'c+':
481
+                if (strrpos($path, '.') !== false) {
482
+                    $ext = substr($path, strrpos($path, '.'));
483
+                } else {
484
+                    $ext = '';
485
+                }
486
+                $tmpFile = \OC::$server->getTempManager()->getTemporaryFile($ext);
487
+                if ($this->file_exists($path)) {
488
+                    $source = $this->readObject($path);
489
+                    file_put_contents($tmpFile, $source);
490
+                }
491
+
492
+                $handle = fopen($tmpFile, $mode);
493
+                return CallbackWrapper::wrap($handle, null, null, function () use ($path, $tmpFile) {
494
+                    $this->writeBack($tmpFile, $path);
495
+                });
496
+        }
497
+        return false;
498
+    }
499
+
500
+    public function touch($path, $mtime = null) {
501
+        $path = $this->normalizePath($path);
502
+
503
+        $metadata = array();
504
+        if (is_null($mtime)) {
505
+            $mtime = time();
506
+        }
507
+        $metadata = [
508
+            'lastmodified' => gmdate(\DateTime::RFC1123, $mtime)
509
+        ];
510
+
511
+        $fileType = $this->filetype($path);
512
+        try {
513
+            if ($fileType !== false) {
514
+                if ($fileType === 'dir' && !$this->isRoot($path)) {
515
+                    $path .= '/';
516
+                }
517
+                $this->getConnection()->copyObject([
518
+                    'Bucket' => $this->bucket,
519
+                    'Key' => $this->cleanKey($path),
520
+                    'Metadata' => $metadata,
521
+                    'CopySource' => $this->bucket . '/' . $path,
522
+                    'MetadataDirective' => 'REPLACE',
523
+                ]);
524
+                $this->testTimeout();
525
+            } else {
526
+                $mimeType = \OC::$server->getMimeTypeDetector()->detectPath($path);
527
+                $this->getConnection()->putObject([
528
+                    'Bucket' => $this->bucket,
529
+                    'Key' => $this->cleanKey($path),
530
+                    'Metadata' => $metadata,
531
+                    'Body' => '',
532
+                    'ContentType' => $mimeType,
533
+                    'MetadataDirective' => 'REPLACE',
534
+                ]);
535
+                $this->testTimeout();
536
+            }
537
+        } catch (S3Exception $e) {
538
+            \OC::$server->getLogger()->logException($e, ['app' => 'files_external']);
539
+            return false;
540
+        }
541
+
542
+        $this->invalidateCache($path);
543
+        return true;
544
+    }
545
+
546
+    public function copy($path1, $path2) {
547
+        $path1 = $this->normalizePath($path1);
548
+        $path2 = $this->normalizePath($path2);
549
+
550
+        if ($this->is_file($path1)) {
551
+            try {
552
+                $this->getConnection()->copyObject(array(
553
+                    'Bucket' => $this->bucket,
554
+                    'Key' => $this->cleanKey($path2),
555
+                    'CopySource' => S3Client::encodeKey($this->bucket . '/' . $path1)
556
+                ));
557
+                $this->testTimeout();
558
+            } catch (S3Exception $e) {
559
+                \OC::$server->getLogger()->logException($e, ['app' => 'files_external']);
560
+                return false;
561
+            }
562
+        } else {
563
+            $this->remove($path2);
564
+
565
+            try {
566
+                $this->getConnection()->copyObject(array(
567
+                    'Bucket' => $this->bucket,
568
+                    'Key' => $path2 . '/',
569
+                    'CopySource' => S3Client::encodeKey($this->bucket . '/' . $path1 . '/')
570
+                ));
571
+                $this->testTimeout();
572
+            } catch (S3Exception $e) {
573
+                \OC::$server->getLogger()->logException($e, ['app' => 'files_external']);
574
+                return false;
575
+            }
576
+
577
+            $dh = $this->opendir($path1);
578
+            if (is_resource($dh)) {
579
+                while (($file = readdir($dh)) !== false) {
580
+                    if (\OC\Files\Filesystem::isIgnoredDir($file)) {
581
+                        continue;
582
+                    }
583
+
584
+                    $source = $path1 . '/' . $file;
585
+                    $target = $path2 . '/' . $file;
586
+                    $this->copy($source, $target);
587
+                }
588
+            }
589
+        }
590
+
591
+        $this->invalidateCache($path2);
592
+
593
+        return true;
594
+    }
595
+
596
+    public function rename($path1, $path2) {
597
+        $path1 = $this->normalizePath($path1);
598
+        $path2 = $this->normalizePath($path2);
599
+
600
+        if ($this->is_file($path1)) {
601
+
602
+            if ($this->copy($path1, $path2) === false) {
603
+                return false;
604
+            }
605
+
606
+            if ($this->unlink($path1) === false) {
607
+                $this->unlink($path2);
608
+                return false;
609
+            }
610
+        } else {
611
+
612
+            if ($this->copy($path1, $path2) === false) {
613
+                return false;
614
+            }
615
+
616
+            if ($this->rmdir($path1) === false) {
617
+                $this->rmdir($path2);
618
+                return false;
619
+            }
620
+        }
621
+
622
+        return true;
623
+    }
624
+
625
+    public function test() {
626
+        $this->getConnection()->headBucket([
627
+            'Bucket' => $this->bucket
628
+        ]);
629
+        return true;
630
+    }
631
+
632
+    public function getId() {
633
+        return $this->id;
634
+    }
635
+
636
+    public function writeBack($tmpFile, $path) {
637
+        try {
638
+            $source = fopen($tmpFile, 'r');
639
+            $this->writeObject($path, $source);
640
+            $this->invalidateCache($path);
641
+            fclose($source);
642
+
643
+            unlink($tmpFile);
644
+            return true;
645
+        } catch (S3Exception $e) {
646
+            \OC::$server->getLogger()->logException($e, ['app' => 'files_external']);
647
+            return false;
648
+        }
649
+    }
650
+
651
+    /**
652
+     * check if curl is installed
653
+     */
654
+    public static function checkDependencies() {
655
+        return true;
656
+    }
657 657
 
658 658
 }
Please login to merge, or discard this patch.
Spacing   +15 added lines, -15 removed lines patch added patch discarded remove patch
@@ -142,7 +142,7 @@  discard block
 block discarded – undo
142 142
 	 * @param array $params
143 143
 	 */
144 144
 	public function updateLegacyId(array $params) {
145
-		$oldId = 'amazon::' . $params['key'] . md5($params['secret']);
145
+		$oldId = 'amazon::'.$params['key'].md5($params['secret']);
146 146
 
147 147
 		// find by old id or bucket
148 148
 		$stmt = \OC::$server->getDatabaseConnection()->prepare(
@@ -194,7 +194,7 @@  discard block
 block discarded – undo
194 194
 		try {
195 195
 			$this->getConnection()->putObject(array(
196 196
 				'Bucket' => $this->bucket,
197
-				'Key' => $path . '/',
197
+				'Key' => $path.'/',
198 198
 				'Body' => '',
199 199
 				'ContentType' => 'httpd/unix-directory'
200 200
 			));
@@ -245,7 +245,7 @@  discard block
 block discarded – undo
245 245
 			'Bucket' => $this->bucket
246 246
 		);
247 247
 		if ($path !== null) {
248
-			$params['Prefix'] = $path . '/';
248
+			$params['Prefix'] = $path.'/';
249 249
 		}
250 250
 		try {
251 251
 			$connection = $this->getConnection();
@@ -309,9 +309,9 @@  discard block
 block discarded – undo
309 309
 						$files[] = $file;
310 310
 
311 311
 						// store this information for later usage
312
-						$this->filesCache[$path . $file] = [
312
+						$this->filesCache[$path.$file] = [
313 313
 							'ContentLength' => $object['Size'],
314
-							'LastModified' => (string)$object['LastModified'],
314
+							'LastModified' => (string) $object['LastModified'],
315 315
 						];
316 316
 					}
317 317
 				}
@@ -393,7 +393,7 @@  discard block
 block discarded – undo
393 393
 	public function is_dir($path) {
394 394
 		$path = $this->normalizePath($path);
395 395
 		try {
396
-			return $this->isRoot($path) || $this->headObject($path . '/');
396
+			return $this->isRoot($path) || $this->headObject($path.'/');
397 397
 		} catch (S3Exception $e) {
398 398
 			\OC::$server->getLogger()->logException($e, ['app' => 'files_external']);
399 399
 			return false;
@@ -411,7 +411,7 @@  discard block
 block discarded – undo
411 411
 			if (isset($this->filesCache[$path]) || $this->headObject($path)) {
412 412
 				return 'file';
413 413
 			}
414
-			if ($this->headObject($path . '/')) {
414
+			if ($this->headObject($path.'/')) {
415 415
 				return 'dir';
416 416
 			}
417 417
 		} catch (S3Exception $e) {
@@ -465,7 +465,7 @@  discard block
 block discarded – undo
465 465
 				$tmpFile = \OC::$server->getTempManager()->getTemporaryFile();
466 466
 
467 467
 				$handle = fopen($tmpFile, 'w');
468
-				return CallbackWrapper::wrap($handle, null, null, function () use ($path, $tmpFile) {
468
+				return CallbackWrapper::wrap($handle, null, null, function() use ($path, $tmpFile) {
469 469
 					$this->writeBack($tmpFile, $path);
470 470
 				});
471 471
 			case 'a':
@@ -490,7 +490,7 @@  discard block
 block discarded – undo
490 490
 				}
491 491
 
492 492
 				$handle = fopen($tmpFile, $mode);
493
-				return CallbackWrapper::wrap($handle, null, null, function () use ($path, $tmpFile) {
493
+				return CallbackWrapper::wrap($handle, null, null, function() use ($path, $tmpFile) {
494 494
 					$this->writeBack($tmpFile, $path);
495 495
 				});
496 496
 		}
@@ -518,7 +518,7 @@  discard block
 block discarded – undo
518 518
 					'Bucket' => $this->bucket,
519 519
 					'Key' => $this->cleanKey($path),
520 520
 					'Metadata' => $metadata,
521
-					'CopySource' => $this->bucket . '/' . $path,
521
+					'CopySource' => $this->bucket.'/'.$path,
522 522
 					'MetadataDirective' => 'REPLACE',
523 523
 				]);
524 524
 				$this->testTimeout();
@@ -552,7 +552,7 @@  discard block
 block discarded – undo
552 552
 				$this->getConnection()->copyObject(array(
553 553
 					'Bucket' => $this->bucket,
554 554
 					'Key' => $this->cleanKey($path2),
555
-					'CopySource' => S3Client::encodeKey($this->bucket . '/' . $path1)
555
+					'CopySource' => S3Client::encodeKey($this->bucket.'/'.$path1)
556 556
 				));
557 557
 				$this->testTimeout();
558 558
 			} catch (S3Exception $e) {
@@ -565,8 +565,8 @@  discard block
 block discarded – undo
565 565
 			try {
566 566
 				$this->getConnection()->copyObject(array(
567 567
 					'Bucket' => $this->bucket,
568
-					'Key' => $path2 . '/',
569
-					'CopySource' => S3Client::encodeKey($this->bucket . '/' . $path1 . '/')
568
+					'Key' => $path2.'/',
569
+					'CopySource' => S3Client::encodeKey($this->bucket.'/'.$path1.'/')
570 570
 				));
571 571
 				$this->testTimeout();
572 572
 			} catch (S3Exception $e) {
@@ -581,8 +581,8 @@  discard block
 block discarded – undo
581 581
 						continue;
582 582
 					}
583 583
 
584
-					$source = $path1 . '/' . $file;
585
-					$target = $path2 . '/' . $file;
584
+					$source = $path1.'/'.$file;
585
+					$target = $path2.'/'.$file;
586 586
 					$this->copy($source, $target);
587 587
 				}
588 588
 			}
Please login to merge, or discard this patch.