1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* @author Bart Visscher <[email protected]> |
4
|
|
|
* @author Benjamin Liles <[email protected]> |
5
|
|
|
* @author Christian Berendt <[email protected]> |
6
|
|
|
* @author Daniel Tosello <[email protected]> |
7
|
|
|
* @author Felix Moeller <[email protected]> |
8
|
|
|
* @author Jörn Friedrich Dreyer <[email protected]> |
9
|
|
|
* @author Martin Mattel <[email protected]> |
10
|
|
|
* @author Morris Jobke <[email protected]> |
11
|
|
|
* @author Philipp Kapfer <[email protected]> |
12
|
|
|
* @author Robin Appelman <[email protected]> |
13
|
|
|
* @author Robin McCorkell <[email protected]> |
14
|
|
|
* @author Thomas Müller <[email protected]> |
15
|
|
|
* @author Tim Dettrick <[email protected]> |
16
|
|
|
* @author Vincent Petry <[email protected]> |
17
|
|
|
* |
18
|
|
|
* @copyright Copyright (c) 2018, ownCloud GmbH |
19
|
|
|
* @license AGPL-3.0 |
20
|
|
|
* |
21
|
|
|
* This code is free software: you can redistribute it and/or modify |
22
|
|
|
* it under the terms of the GNU Affero General Public License, version 3, |
23
|
|
|
* as published by the Free Software Foundation. |
24
|
|
|
* |
25
|
|
|
* This program is distributed in the hope that it will be useful, |
26
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
27
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
28
|
|
|
* GNU Affero General Public License for more details. |
29
|
|
|
* |
30
|
|
|
* You should have received a copy of the GNU Affero General Public License, version 3, |
31
|
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/> |
32
|
|
|
* |
33
|
|
|
*/ |
34
|
|
|
|
35
|
|
|
namespace OCA\Files_External\Lib\Storage; |
36
|
|
|
|
37
|
|
|
use Guzzle\Http\Exception\ClientErrorResponseException; |
38
|
|
|
use Guzzle\Http\Url; |
39
|
|
|
use function GuzzleHttp\Psr7\stream_for; |
40
|
|
|
use Icewind\Streams\IteratorDirectory; |
41
|
|
|
use OCP\Files\Storage\StorageAdapter; |
42
|
|
|
use OpenCloud; |
43
|
|
|
use OpenCloud\Common\Exceptions; |
44
|
|
|
use OpenCloud\ObjectStore\Resource\DataObject; |
45
|
|
|
use OpenCloud\OpenStack; |
46
|
|
|
use OpenCloud\Rackspace; |
47
|
|
|
use Psr\Http\Message\StreamInterface; |
48
|
|
|
|
49
|
|
|
class Swift extends StorageAdapter { |
50
|
|
|
|
51
|
|
|
/** |
52
|
|
|
* @var \OpenCloud\ObjectStore\Service |
53
|
|
|
*/ |
54
|
|
|
private $connection; |
55
|
|
|
/** |
56
|
|
|
* @var \OpenCloud\ObjectStore\Resource\Container |
57
|
|
|
*/ |
58
|
|
|
private $container; |
59
|
|
|
/** |
60
|
|
|
* @var \OpenCloud\OpenStack |
61
|
|
|
*/ |
62
|
|
|
private $anchor; |
63
|
|
|
/** |
64
|
|
|
* @var string |
65
|
|
|
*/ |
66
|
|
|
private $bucket; |
67
|
|
|
/** |
68
|
|
|
* Connection parameters |
69
|
|
|
* |
70
|
|
|
* @var array |
71
|
|
|
*/ |
72
|
|
|
private $params; |
73
|
|
|
/** |
74
|
|
|
* @var array |
75
|
|
|
*/ |
76
|
|
|
private static $tmpFiles = []; |
77
|
|
|
|
78
|
|
|
/** |
79
|
|
|
* Key value cache mapping path to data object. Maps path to |
80
|
|
|
* \OpenCloud\OpenStack\ObjectStorage\Resource\DataObject for existing |
81
|
|
|
* paths and path to false for not existing paths. |
82
|
|
|
* @var \OCP\ICache |
83
|
|
|
*/ |
84
|
|
|
private $objectCache; |
85
|
|
|
|
86
|
|
|
/** |
87
|
|
|
* @param string $path |
88
|
|
|
*/ |
89
|
|
View Code Duplication |
private function normalizePath($path) { |
90
|
|
|
$path = \trim($path, '/'); |
91
|
|
|
|
92
|
|
|
if (!$path) { |
93
|
|
|
$path = '.'; |
94
|
|
|
} |
95
|
|
|
|
96
|
|
|
$path = \str_replace('#', '%23', $path); |
97
|
|
|
|
98
|
|
|
return $path; |
99
|
|
|
} |
100
|
|
|
|
101
|
|
|
const SUBCONTAINER_FILE = '.subcontainers'; |
102
|
|
|
|
103
|
|
|
/** |
104
|
|
|
* translate directory path to container name |
105
|
|
|
* |
106
|
|
|
* @param string $path |
107
|
|
|
* @return string |
108
|
|
|
*/ |
109
|
|
|
|
110
|
|
|
/** |
111
|
|
|
* Fetches an object from the API. |
112
|
|
|
* If the object is cached already or a |
113
|
|
|
* failed "doesn't exist" response was cached, |
114
|
|
|
* that one will be returned. |
115
|
|
|
* |
116
|
|
|
* @param string $path |
117
|
|
|
* @return DataObject | bool object |
118
|
|
|
* or false if the object did not exist |
119
|
|
|
*/ |
120
|
|
|
private function fetchObject($path) { |
121
|
|
|
if ($this->objectCache->hasKey($path)) { |
122
|
|
|
// might be "false" if object did not exist from last check |
123
|
|
|
return $this->objectCache->get($path); |
124
|
|
|
} |
125
|
|
|
try { |
126
|
|
|
$object = $this->getContainer()->getPartialObject($path); |
127
|
|
|
$this->objectCache->set($path, $object); |
128
|
|
|
return $object; |
129
|
|
|
} catch (ClientErrorResponseException $e) { |
130
|
|
|
// this exception happens when the object does not exist, which |
131
|
|
|
// is expected in most cases |
132
|
|
|
$this->objectCache->set($path, false); |
133
|
|
|
return false; |
|
|
|
|
134
|
|
|
} catch (ClientErrorResponseException $e) { |
135
|
|
|
// Expected response is "404 Not Found", so only log if it isn't |
136
|
|
|
if ($e->getResponse()->getStatusCode() !== 404) { |
|
|
|
|
137
|
|
|
\OCP\Util::writeLog('files_external', $e->getMessage(), \OCP\Util::ERROR); |
138
|
|
|
} |
139
|
|
|
return false; |
|
|
|
|
140
|
|
|
} |
141
|
|
|
} |
142
|
|
|
|
143
|
|
|
/** |
144
|
|
|
* Returns whether the given path exists. |
145
|
|
|
* |
146
|
|
|
* @param string $path |
147
|
|
|
* |
148
|
|
|
* @return bool true if the object exist, false otherwise |
149
|
|
|
*/ |
150
|
|
|
private function doesObjectExist($path) { |
151
|
|
|
return $this->fetchObject($path) !== false; |
152
|
|
|
} |
153
|
|
|
|
154
|
|
|
public function __construct($params) { |
155
|
|
|
if ((empty($params['key']) and empty($params['password'])) |
156
|
|
|
or empty($params['user']) or empty($params['bucket']) |
157
|
|
|
or empty($params['region']) |
158
|
|
|
) { |
159
|
|
|
throw new \Exception("API Key or password, Username, Bucket and Region have to be configured."); |
160
|
|
|
} |
161
|
|
|
|
162
|
|
|
$this->id = 'swift::' . $params['user'] . \md5($params['bucket']); |
|
|
|
|
163
|
|
|
|
164
|
|
|
$bucketUrl = Url::factory($params['bucket']); |
165
|
|
|
if ($bucketUrl->isAbsolute()) { |
166
|
|
|
$this->bucket = \end(($bucketUrl->getPathSegments())); |
|
|
|
|
167
|
|
|
$params['endpoint_url'] = $bucketUrl->addPath('..')->normalizePath(); |
168
|
|
|
} else { |
169
|
|
|
$this->bucket = $params['bucket']; |
170
|
|
|
} |
171
|
|
|
|
172
|
|
|
if (empty($params['url'])) { |
173
|
|
|
$params['url'] = 'https://identity.api.rackspacecloud.com/v2.0/'; |
174
|
|
|
} |
175
|
|
|
|
176
|
|
|
if (empty($params['service_name'])) { |
177
|
|
|
$params['service_name'] = 'cloudFiles'; |
178
|
|
|
} |
179
|
|
|
|
180
|
|
|
$this->params = $params; |
181
|
|
|
// FIXME: private class... |
182
|
|
|
$this->objectCache = new \OC\Cache\CappedMemoryCache(); |
183
|
|
|
} |
184
|
|
|
|
185
|
|
|
public function mkdir($path) { |
186
|
|
|
$path = $this->normalizePath($path); |
187
|
|
|
|
188
|
|
|
if ($this->is_dir($path)) { |
189
|
|
|
return false; |
190
|
|
|
} |
191
|
|
|
|
192
|
|
|
if ($path !== '.') { |
193
|
|
|
$path .= '/'; |
194
|
|
|
} |
195
|
|
|
|
196
|
|
|
try { |
197
|
|
|
$customHeaders = ['content-type' => 'httpd/unix-directory']; |
198
|
|
|
$metadataHeaders = DataObject::stockHeaders([]); |
199
|
|
|
$allHeaders = $customHeaders + $metadataHeaders; |
200
|
|
|
$this->getContainer()->uploadObject($path, '', $allHeaders); |
201
|
|
|
// invalidate so that the next access gets the real object |
202
|
|
|
// with all properties |
203
|
|
|
$this->objectCache->remove($path); |
204
|
|
|
} catch (Exceptions\CreateUpdateError $e) { |
205
|
|
|
\OCP\Util::writeLog('files_external', $e->getMessage(), \OCP\Util::ERROR); |
206
|
|
|
return false; |
207
|
|
|
} |
208
|
|
|
|
209
|
|
|
return true; |
210
|
|
|
} |
211
|
|
|
|
212
|
|
|
public function file_exists($path) { |
213
|
|
|
$path = $this->normalizePath($path); |
214
|
|
|
|
215
|
|
|
if ($path !== '.' && $this->is_dir($path)) { |
216
|
|
|
$path .= '/'; |
217
|
|
|
} |
218
|
|
|
|
219
|
|
|
return $this->doesObjectExist($path); |
220
|
|
|
} |
221
|
|
|
|
222
|
|
|
public function rmdir($path) { |
223
|
|
|
$path = $this->normalizePath($path); |
224
|
|
|
|
225
|
|
|
if (!$this->is_dir($path) || !$this->isDeletable($path)) { |
226
|
|
|
return false; |
227
|
|
|
} |
228
|
|
|
|
229
|
|
|
$dh = $this->opendir($path); |
230
|
|
View Code Duplication |
while ($file = \readdir($dh)) { |
|
|
|
|
231
|
|
|
if (\OC\Files\Filesystem::isIgnoredDir($file)) { |
232
|
|
|
continue; |
233
|
|
|
} |
234
|
|
|
|
235
|
|
|
if ($this->is_dir($path . '/' . $file)) { |
236
|
|
|
$this->rmdir($path . '/' . $file); |
237
|
|
|
} else { |
238
|
|
|
$this->unlink($path . '/' . $file); |
239
|
|
|
} |
240
|
|
|
} |
241
|
|
|
|
242
|
|
|
try { |
243
|
|
|
$this->getContainer()->dataObject()->setName($path . '/')->delete(); |
244
|
|
|
$this->objectCache->remove($path . '/'); |
245
|
|
|
} catch (Exceptions\DeleteError $e) { |
246
|
|
|
\OCP\Util::writeLog('files_external', $e->getMessage(), \OCP\Util::ERROR); |
247
|
|
|
return false; |
248
|
|
|
} |
249
|
|
|
|
250
|
|
|
return true; |
251
|
|
|
} |
252
|
|
|
|
253
|
|
|
public function opendir($path) { |
254
|
|
|
$path = $this->normalizePath($path); |
255
|
|
|
|
256
|
|
|
if ($path === '.') { |
257
|
|
|
$path = ''; |
258
|
|
|
} else { |
259
|
|
|
$path .= '/'; |
260
|
|
|
} |
261
|
|
|
|
262
|
|
|
$path = \str_replace('%23', '#', $path); // the prefix is sent as a query param, so revert the encoding of # |
263
|
|
|
|
264
|
|
|
try { |
265
|
|
|
$files = []; |
266
|
|
|
/** @var OpenCloud\Common\Collection $objects */ |
267
|
|
|
$objects = $this->getContainer()->objectList([ |
268
|
|
|
'prefix' => $path, |
269
|
|
|
'delimiter' => '/' |
270
|
|
|
]); |
271
|
|
|
|
272
|
|
|
/** @var OpenCloud\ObjectStore\Resource\DataObject $object */ |
273
|
|
|
foreach ($objects as $object) { |
|
|
|
|
274
|
|
|
$file = \basename($object->getName()); |
275
|
|
|
if ($file !== \basename($path)) { |
276
|
|
|
$files[] = $file; |
277
|
|
|
} |
278
|
|
|
} |
279
|
|
|
|
280
|
|
|
return IteratorDirectory::wrap($files); |
281
|
|
|
} catch (\Exception $e) { |
282
|
|
|
\OCP\Util::writeLog('files_external', $e->getMessage(), \OCP\Util::ERROR); |
283
|
|
|
return false; |
284
|
|
|
} |
285
|
|
|
} |
286
|
|
|
|
287
|
|
|
public function stat($path) { |
288
|
|
|
$path = $this->normalizePath($path); |
289
|
|
|
|
290
|
|
|
if ($path === '.') { |
291
|
|
|
$path = ''; |
292
|
|
|
} elseif ($this->is_dir($path)) { |
293
|
|
|
$path .= '/'; |
294
|
|
|
} |
295
|
|
|
|
296
|
|
|
try { |
297
|
|
|
/** @var DataObject $object */ |
298
|
|
|
$object = $this->fetchObject($path); |
299
|
|
|
if (!$object) { |
300
|
|
|
return false; |
301
|
|
|
} |
302
|
|
|
} catch (ClientErrorResponseException $e) { |
303
|
|
|
\OCP\Util::writeLog('files_external', $e->getMessage(), \OCP\Util::ERROR); |
304
|
|
|
return false; |
305
|
|
|
} |
306
|
|
|
|
307
|
|
|
$dateTime = \DateTime::createFromFormat(\DateTime::RFC1123, $object->getLastModified()); |
308
|
|
|
if ($dateTime !== false) { |
309
|
|
|
$mtime = $dateTime->getTimestamp(); |
310
|
|
|
} else { |
311
|
|
|
$mtime = null; |
312
|
|
|
} |
313
|
|
|
$objectMetadata = $object->getMetadata(); |
314
|
|
|
$metaTimestamp = $objectMetadata->getProperty('timestamp'); |
315
|
|
|
if (isset($metaTimestamp)) { |
316
|
|
|
$mtime = $metaTimestamp; |
317
|
|
|
} |
318
|
|
|
|
319
|
|
|
if (!empty($mtime)) { |
320
|
|
|
$mtime = \floor($mtime); |
321
|
|
|
} |
322
|
|
|
|
323
|
|
|
$stat = []; |
324
|
|
|
$stat['size'] = (int)$object->getContentLength(); |
325
|
|
|
$stat['mtime'] = $mtime; |
326
|
|
|
$stat['atime'] = \time(); |
327
|
|
|
return $stat; |
328
|
|
|
} |
329
|
|
|
|
330
|
|
|
public function filetype($path) { |
331
|
|
|
$path = $this->normalizePath($path); |
332
|
|
|
|
333
|
|
|
if ($path !== '.' && $this->doesObjectExist($path)) { |
334
|
|
|
return 'file'; |
335
|
|
|
} |
336
|
|
|
|
337
|
|
|
if ($path !== '.') { |
338
|
|
|
$path .= '/'; |
339
|
|
|
} |
340
|
|
|
|
341
|
|
|
if ($this->doesObjectExist($path)) { |
342
|
|
|
return 'dir'; |
343
|
|
|
} |
344
|
|
|
} |
345
|
|
|
|
346
|
|
|
public function unlink($path) { |
347
|
|
|
$path = $this->normalizePath($path); |
348
|
|
|
|
349
|
|
|
if ($this->is_dir($path)) { |
350
|
|
|
return $this->rmdir($path); |
351
|
|
|
} |
352
|
|
|
|
353
|
|
|
try { |
354
|
|
|
$this->getContainer()->dataObject()->setName($path)->delete(); |
355
|
|
|
$this->objectCache->remove($path); |
356
|
|
|
$this->objectCache->remove($path . '/'); |
357
|
|
|
} catch (ClientErrorResponseException $e) { |
358
|
|
|
if ($e->getResponse()->getStatusCode() !== 404) { |
|
|
|
|
359
|
|
|
\OCP\Util::writeLog('files_external', $e->getMessage(), \OCP\Util::ERROR); |
360
|
|
|
} |
361
|
|
|
return false; |
362
|
|
|
} |
363
|
|
|
|
364
|
|
|
return true; |
365
|
|
|
} |
366
|
|
|
|
367
|
|
|
public function fopen($path, $mode) { |
368
|
|
|
throw new \BadMethodCallException('fopen is no longer allowed to be called'); |
369
|
|
|
} |
370
|
|
|
|
371
|
|
|
public function touch($path, $mtime = null) { |
372
|
|
|
$path = $this->normalizePath($path); |
373
|
|
|
if ($mtime === null) { |
374
|
|
|
$mtime = \time(); |
375
|
|
|
} |
376
|
|
|
$metadata = ['timestamp' => $mtime]; |
377
|
|
|
if ($this->file_exists($path)) { |
378
|
|
|
if ($this->is_dir($path) && $path != '.') { |
379
|
|
|
$path .= '/'; |
380
|
|
|
} |
381
|
|
|
|
382
|
|
|
$object = $this->fetchObject($path); |
383
|
|
|
if ($object->saveMetadata($metadata)) { |
384
|
|
|
// invalidate target object to force repopulation on fetch |
385
|
|
|
$this->objectCache->remove($path); |
386
|
|
|
} |
387
|
|
|
return true; |
388
|
|
|
} else { |
389
|
|
|
$mimeType = \OC::$server->getMimeTypeDetector()->detectPath($path); |
390
|
|
|
$customHeaders = ['content-type' => $mimeType]; |
391
|
|
|
$metadataHeaders = DataObject::stockHeaders($metadata); |
392
|
|
|
$allHeaders = $customHeaders + $metadataHeaders; |
393
|
|
|
$this->getContainer()->uploadObject($path, '', $allHeaders); |
394
|
|
|
// invalidate target object to force repopulation on fetch |
395
|
|
|
$this->objectCache->remove($path); |
396
|
|
|
return true; |
397
|
|
|
} |
398
|
|
|
} |
399
|
|
|
|
400
|
|
|
public function copy($path1, $path2) { |
401
|
|
|
$path1 = $this->normalizePath($path1); |
402
|
|
|
$path2 = $this->normalizePath($path2); |
403
|
|
|
|
404
|
|
|
$fileType = $this->filetype($path1); |
405
|
|
|
if ($fileType === 'file') { |
406
|
|
|
|
407
|
|
|
// make way |
408
|
|
|
$this->unlink($path2); |
409
|
|
|
|
410
|
|
|
try { |
411
|
|
|
$source = $this->fetchObject($path1); |
412
|
|
|
$source->copy($this->bucket . '/' . $path2); |
413
|
|
|
// invalidate target object to force repopulation on fetch |
414
|
|
|
$this->objectCache->remove($path2); |
415
|
|
|
$this->objectCache->remove($path2 . '/'); |
416
|
|
|
} catch (ClientErrorResponseException $e) { |
417
|
|
|
\OCP\Util::writeLog('files_external', $e->getMessage(), \OCP\Util::ERROR); |
418
|
|
|
return false; |
419
|
|
|
} |
420
|
|
|
} elseif ($fileType === 'dir') { |
421
|
|
|
|
422
|
|
|
// make way |
423
|
|
|
$this->unlink($path2); |
424
|
|
|
|
425
|
|
|
try { |
426
|
|
|
$source = $this->fetchObject($path1 . '/'); |
427
|
|
|
$source->copy($this->bucket . '/' . $path2 . '/'); |
428
|
|
|
// invalidate target object to force repopulation on fetch |
429
|
|
|
$this->objectCache->remove($path2); |
430
|
|
|
$this->objectCache->remove($path2 . '/'); |
431
|
|
|
} catch (ClientErrorResponseException $e) { |
432
|
|
|
\OCP\Util::writeLog('files_external', $e->getMessage(), \OCP\Util::ERROR); |
433
|
|
|
return false; |
434
|
|
|
} |
435
|
|
|
|
436
|
|
|
$dh = $this->opendir($path1); |
437
|
|
|
while ($file = \readdir($dh)) { |
438
|
|
|
if (\OC\Files\Filesystem::isIgnoredDir($file)) { |
439
|
|
|
continue; |
440
|
|
|
} |
441
|
|
|
|
442
|
|
|
$source = $path1 . '/' . $file; |
443
|
|
|
$target = $path2 . '/' . $file; |
444
|
|
|
$this->copy($source, $target); |
445
|
|
|
} |
446
|
|
|
} else { |
447
|
|
|
//file does not exist |
448
|
|
|
return false; |
449
|
|
|
} |
450
|
|
|
|
451
|
|
|
return true; |
452
|
|
|
} |
453
|
|
|
|
454
|
|
|
public function rename($path1, $path2) { |
455
|
|
|
$path1 = $this->normalizePath($path1); |
456
|
|
|
$path2 = $this->normalizePath($path2); |
457
|
|
|
|
458
|
|
|
$fileType = $this->filetype($path1); |
459
|
|
|
|
460
|
|
|
if ($fileType === 'dir' || $fileType === 'file') { |
461
|
|
|
// copy |
462
|
|
|
if ($this->copy($path1, $path2) === false) { |
463
|
|
|
return false; |
464
|
|
|
} |
465
|
|
|
|
466
|
|
|
// cleanup |
467
|
|
|
if ($this->unlink($path1) === false) { |
468
|
|
|
$this->unlink($path2); |
469
|
|
|
return false; |
470
|
|
|
} |
471
|
|
|
|
472
|
|
|
return true; |
473
|
|
|
} |
474
|
|
|
|
475
|
|
|
return false; |
476
|
|
|
} |
477
|
|
|
|
478
|
|
|
public function getId() { |
479
|
|
|
return $this->id; |
480
|
|
|
} |
481
|
|
|
|
482
|
|
|
/** |
483
|
|
|
* Returns the connection |
484
|
|
|
* |
485
|
|
|
* @return OpenCloud\ObjectStore\Service connected client |
486
|
|
|
* @throws \Exception if connection could not be made |
487
|
|
|
*/ |
488
|
|
|
public function getConnection() { |
489
|
|
|
if ($this->connection !== null) { |
490
|
|
|
return $this->connection; |
491
|
|
|
} |
492
|
|
|
|
493
|
|
|
$settings = [ |
494
|
|
|
'username' => $this->params['user'], |
495
|
|
|
]; |
496
|
|
|
|
497
|
|
|
if (!empty($this->params['password'])) { |
498
|
|
|
$settings['password'] = $this->params['password']; |
499
|
|
|
} elseif (!empty($this->params['key'])) { |
500
|
|
|
$settings['apiKey'] = $this->params['key']; |
501
|
|
|
} |
502
|
|
|
|
503
|
|
|
if (!empty($this->params['tenant'])) { |
504
|
|
|
$settings['tenantName'] = $this->params['tenant']; |
505
|
|
|
} |
506
|
|
|
|
507
|
|
|
if (!empty($this->params['timeout'])) { |
508
|
|
|
$settings['timeout'] = $this->params['timeout']; |
509
|
|
|
} |
510
|
|
|
|
511
|
|
|
if (isset($settings['apiKey'])) { |
512
|
|
|
$this->anchor = new Rackspace($this->params['url'], $settings); |
513
|
|
|
} else { |
514
|
|
|
$this->anchor = new OpenStack($this->params['url'], $settings); |
515
|
|
|
} |
516
|
|
|
|
517
|
|
|
$connection = $this->anchor->objectStoreService($this->params['service_name'], $this->params['region']); |
518
|
|
|
|
519
|
|
|
if (!empty($this->params['endpoint_url'])) { |
520
|
|
|
$endpoint = $connection->getEndpoint(); |
521
|
|
|
$endpoint->setPublicUrl($this->params['endpoint_url']); |
522
|
|
|
$endpoint->setPrivateUrl($this->params['endpoint_url']); |
523
|
|
|
$connection->setEndpoint($endpoint); |
524
|
|
|
} |
525
|
|
|
|
526
|
|
|
$this->connection = $connection; |
527
|
|
|
|
528
|
|
|
return $this->connection; |
529
|
|
|
} |
530
|
|
|
|
531
|
|
|
/** |
532
|
|
|
* Returns the initialized object store container. |
533
|
|
|
* |
534
|
|
|
* @return OpenCloud\ObjectStore\Resource\Container |
535
|
|
|
*/ |
536
|
|
|
public function getContainer() { |
537
|
|
|
if ($this->container !== null) { |
538
|
|
|
return $this->container; |
539
|
|
|
} |
540
|
|
|
|
541
|
|
|
try { |
542
|
|
|
$this->container = $this->getConnection()->getContainer($this->bucket); |
543
|
|
|
} catch (ClientErrorResponseException $e) { |
544
|
|
|
$this->container = $this->getConnection()->createContainer($this->bucket); |
|
|
|
|
545
|
|
|
} |
546
|
|
|
|
547
|
|
|
if (!$this->file_exists('.')) { |
548
|
|
|
$this->mkdir('.'); |
549
|
|
|
} |
550
|
|
|
|
551
|
|
|
return $this->container; |
|
|
|
|
552
|
|
|
} |
553
|
|
|
|
554
|
|
|
public function hasUpdated($path, $time) { |
555
|
|
|
if ($this->is_file($path)) { |
556
|
|
|
return parent::hasUpdated($path, $time); |
557
|
|
|
} |
558
|
|
|
$path = $this->normalizePath($path); |
559
|
|
|
$dh = $this->opendir($path); |
560
|
|
|
$content = []; |
561
|
|
|
while (($file = \readdir($dh)) !== false) { |
562
|
|
|
$content[] = $file; |
563
|
|
|
} |
564
|
|
|
if ($path === '.') { |
565
|
|
|
$path = ''; |
566
|
|
|
} |
567
|
|
|
$cachedContent = $this->getCache()->getFolderContents($path); |
568
|
|
|
$cachedNames = \array_map(function ($content) { |
569
|
|
|
return $content['name']; |
570
|
|
|
}, $cachedContent); |
571
|
|
|
\sort($cachedNames); |
572
|
|
|
\sort($content); |
573
|
|
|
return $cachedNames != $content; |
574
|
|
|
} |
575
|
|
|
|
576
|
|
|
/** |
577
|
|
|
* check if curl is installed |
578
|
|
|
*/ |
579
|
|
|
public static function checkDependencies() { |
580
|
|
|
return true; |
581
|
|
|
} |
582
|
|
|
|
583
|
|
|
/** |
584
|
|
|
* @param string $path |
585
|
|
|
* @param array $options |
586
|
|
|
* @return StreamInterface |
587
|
|
|
* @since 11.0.0 |
588
|
|
|
*/ |
589
|
|
|
public function readFile(string $path, array $options = []): StreamInterface { |
590
|
|
|
try { |
591
|
|
|
$path = $this->normalizePath($path); |
592
|
|
|
$c = $this->getContainer(); |
593
|
|
|
$streamFactory = new \Guzzle\Stream\PhpStreamRequestFactory(); |
594
|
|
|
$streamInterface = $streamFactory->fromRequest( |
595
|
|
|
$c->getClient() |
596
|
|
|
->get($c->getUrl($path))); |
597
|
|
|
$streamInterface->rewind(); |
598
|
|
|
$stream = $streamInterface->getStream(); |
599
|
|
|
\stream_context_set_option($stream, 'swift', 'content', $streamInterface); |
600
|
|
|
if (!\strrpos($streamInterface |
601
|
|
|
->getMetaData('wrapper_data')[0], '404 Not Found')) { |
602
|
|
|
return stream_for($stream); |
603
|
|
|
} |
604
|
|
|
throw new \BadFunctionCallException(); |
605
|
|
|
} catch (\Guzzle\Http\Exception\BadResponseException $e) { |
606
|
|
|
\OCP\Util::writeLog('files_external', $e->getMessage(), \OCP\Util::ERROR); |
607
|
|
|
throw $e; |
608
|
|
|
} |
609
|
|
|
} |
610
|
|
|
|
611
|
|
|
/** |
612
|
|
|
* @param string $path |
613
|
|
|
* @param StreamInterface $stream |
614
|
|
|
* @return int |
615
|
|
|
* @since 11.0.0 |
616
|
|
|
*/ |
617
|
|
|
public function writeFile(string $path, StreamInterface $stream): int { |
618
|
|
|
$this->getContainer()->uploadObject($path, $stream->detach()); |
619
|
|
|
// invalidate target object to force repopulation on fetch |
620
|
|
|
$this->objectCache->remove($path); |
621
|
|
|
} |
622
|
|
|
} |
623
|
|
|
|
If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.
Let’s take a look at an example:
Our function
my_function
expects aPost
object, and outputs the author of the post. The base classPost
returns a simple string and outputting a simple string will work just fine. However, the child classBlogPost
which is a sub-type ofPost
instead decided to return anobject
, and is therefore violating the SOLID principles. If aBlogPost
were passed tomy_function
, PHP would not complain, but ultimately fail when executing thestrtoupper
call in its body.