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