|
1
|
|
|
<?php |
|
2
|
|
|
declare(strict_types=1); |
|
3
|
|
|
/** |
|
4
|
|
|
* @copyright Copyright (c) 2016, ownCloud, Inc. |
|
5
|
|
|
* |
|
6
|
|
|
* @author Arthur Schiwon <[email protected]> |
|
7
|
|
|
* @author Bart Visscher <[email protected]> |
|
8
|
|
|
* @author Benjamin Liles <[email protected]> |
|
9
|
|
|
* @author Christian Berendt <[email protected]> |
|
10
|
|
|
* @author Christopher Bartz <[email protected]> |
|
11
|
|
|
* @author Daniel Tosello <[email protected]> |
|
12
|
|
|
* @author Felix Moeller <[email protected]> |
|
13
|
|
|
* @author Joas Schilling <[email protected]> |
|
14
|
|
|
* @author Jörn Friedrich Dreyer <[email protected]> |
|
15
|
|
|
* @author Lukas Reschke <[email protected]> |
|
16
|
|
|
* @author Martin Mattel <[email protected]> |
|
17
|
|
|
* @author Morris Jobke <[email protected]> |
|
18
|
|
|
* @author Philipp Kapfer <[email protected]> |
|
19
|
|
|
* @author Robin Appelman <[email protected]> |
|
20
|
|
|
* @author Robin McCorkell <[email protected]> |
|
21
|
|
|
* @author Thomas Müller <[email protected]> |
|
22
|
|
|
* @author Tim Dettrick <[email protected]> |
|
23
|
|
|
* @author Vincent Petry <[email protected]> |
|
24
|
|
|
* |
|
25
|
|
|
* @license AGPL-3.0 |
|
26
|
|
|
* |
|
27
|
|
|
* This code is free software: you can redistribute it and/or modify |
|
28
|
|
|
* it under the terms of the GNU Affero General Public License, version 3, |
|
29
|
|
|
* as published by the Free Software Foundation. |
|
30
|
|
|
* |
|
31
|
|
|
* This program is distributed in the hope that it will be useful, |
|
32
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
33
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
34
|
|
|
* GNU Affero General Public License for more details. |
|
35
|
|
|
* |
|
36
|
|
|
* You should have received a copy of the GNU Affero General Public License, version 3, |
|
37
|
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/> |
|
38
|
|
|
* |
|
39
|
|
|
*/ |
|
40
|
|
|
|
|
41
|
|
|
namespace OCA\Files_External\Lib\Storage; |
|
42
|
|
|
|
|
43
|
|
|
use GuzzleHttp\Psr7\Uri; |
|
44
|
|
|
use Icewind\Streams\CallbackWrapper; |
|
45
|
|
|
use Icewind\Streams\IteratorDirectory; |
|
46
|
|
|
use OC\Files\ObjectStore\SwiftFactory; |
|
47
|
|
|
use OCP\Files\StorageBadConfigException; |
|
48
|
|
|
use OpenStack\Common\Error\BadResponseError; |
|
49
|
|
|
use OpenStack\ObjectStore\v1\Models\StorageObject; |
|
50
|
|
|
|
|
51
|
|
|
class Swift extends \OC\Files\Storage\Common { |
|
52
|
|
|
/** @var SwiftFactory */ |
|
53
|
|
|
private $connectionFactory; |
|
54
|
|
|
/** |
|
55
|
|
|
* @var \OpenStack\ObjectStore\v1\Models\Container |
|
56
|
|
|
*/ |
|
57
|
|
|
private $container; |
|
58
|
|
|
/** |
|
59
|
|
|
* @var string |
|
60
|
|
|
*/ |
|
61
|
|
|
private $bucket; |
|
62
|
|
|
/** |
|
63
|
|
|
* Connection parameters |
|
64
|
|
|
* |
|
65
|
|
|
* @var array |
|
66
|
|
|
*/ |
|
67
|
|
|
private $params; |
|
68
|
|
|
|
|
69
|
|
|
/** @var string */ |
|
70
|
|
|
private $id; |
|
71
|
|
|
|
|
72
|
|
|
/** @var \OC\Files\ObjectStore\Swift */ |
|
73
|
|
|
private $objectStore; |
|
74
|
|
|
|
|
75
|
|
|
/** |
|
76
|
|
|
* Key value cache mapping path to data object. Maps path to |
|
77
|
|
|
* \OpenCloud\OpenStack\ObjectStorage\Resource\DataObject for existing |
|
78
|
|
|
* paths and path to false for not existing paths. |
|
79
|
|
|
* |
|
80
|
|
|
* @var \OCP\ICache |
|
81
|
|
|
*/ |
|
82
|
|
|
private $objectCache; |
|
83
|
|
|
|
|
84
|
|
|
/** |
|
85
|
|
|
* @param string $path |
|
86
|
|
|
* @return mixed|string |
|
87
|
|
|
*/ |
|
88
|
|
View Code Duplication |
private function normalizePath(string $path) { |
|
89
|
|
|
$path = trim($path, '/'); |
|
90
|
|
|
|
|
91
|
|
|
if (!$path) { |
|
92
|
|
|
$path = '.'; |
|
93
|
|
|
} |
|
94
|
|
|
|
|
95
|
|
|
$path = str_replace('#', '%23', $path); |
|
96
|
|
|
|
|
97
|
|
|
return $path; |
|
98
|
|
|
} |
|
99
|
|
|
|
|
100
|
|
|
const SUBCONTAINER_FILE = '.subcontainers'; |
|
101
|
|
|
|
|
102
|
|
|
/** |
|
103
|
|
|
* translate directory path to container name |
|
104
|
|
|
* |
|
105
|
|
|
* @param string $path |
|
106
|
|
|
* @return string |
|
107
|
|
|
*/ |
|
108
|
|
|
|
|
109
|
|
|
/** |
|
110
|
|
|
* Fetches an object from the API. |
|
111
|
|
|
* If the object is cached already or a |
|
112
|
|
|
* failed "doesn't exist" response was cached, |
|
113
|
|
|
* that one will be returned. |
|
114
|
|
|
* |
|
115
|
|
|
* @param string $path |
|
116
|
|
|
* @return StorageObject|bool object |
|
117
|
|
|
* or false if the object did not exist |
|
118
|
|
|
* @throws \OCP\Files\StorageAuthException |
|
119
|
|
|
* @throws \OCP\Files\StorageNotAvailableException |
|
120
|
|
|
*/ |
|
121
|
|
|
private function fetchObject(string $path) { |
|
122
|
|
|
if ($this->objectCache->hasKey($path)) { |
|
123
|
|
|
// might be "false" if object did not exist from last check |
|
124
|
|
|
return $this->objectCache->get($path); |
|
125
|
|
|
} |
|
126
|
|
|
try { |
|
127
|
|
|
$object = $this->getContainer()->getObject($path); |
|
128
|
|
|
$object->retrieve(); |
|
129
|
|
|
$this->objectCache->set($path, $object); |
|
130
|
|
|
return $object; |
|
131
|
|
|
} catch (BadResponseError $e) { |
|
|
|
|
|
|
132
|
|
|
// Expected response is "404 Not Found", so only log if it isn't |
|
133
|
|
View Code Duplication |
if ($e->getResponse()->getStatusCode() !== 404) { |
|
134
|
|
|
\OC::$server->getLogger()->logException($e, [ |
|
135
|
|
|
'level' => \OCP\Util::ERROR, |
|
136
|
|
|
'app' => 'files_external', |
|
137
|
|
|
]); |
|
138
|
|
|
} |
|
139
|
|
|
$this->objectCache->set($path, false); |
|
140
|
|
|
return false; |
|
141
|
|
|
} |
|
142
|
|
|
} |
|
143
|
|
|
|
|
144
|
|
|
/** |
|
145
|
|
|
* Returns whether the given path exists. |
|
146
|
|
|
* |
|
147
|
|
|
* @param string $path |
|
148
|
|
|
* |
|
149
|
|
|
* @return bool true if the object exist, false otherwise |
|
150
|
|
|
* @throws \OCP\Files\StorageAuthException |
|
151
|
|
|
* @throws \OCP\Files\StorageNotAvailableException |
|
152
|
|
|
*/ |
|
153
|
|
|
private function doesObjectExist($path) { |
|
154
|
|
|
return $this->fetchObject($path) !== false; |
|
155
|
|
|
} |
|
156
|
|
|
|
|
157
|
|
|
public function __construct($params) { |
|
158
|
|
|
if ((empty($params['key']) and empty($params['password'])) |
|
|
|
|
|
|
159
|
|
|
or (empty($params['user']) && empty($params['userid'])) or empty($params['bucket']) |
|
|
|
|
|
|
160
|
|
|
or empty($params['region']) |
|
|
|
|
|
|
161
|
|
|
) { |
|
162
|
|
|
throw new StorageBadConfigException("API Key or password, Username, Bucket and Region have to be configured."); |
|
163
|
|
|
} |
|
164
|
|
|
|
|
165
|
|
|
$user = $params['user']; |
|
166
|
|
|
$this->id = 'swift::' . $user . md5($params['bucket']); |
|
167
|
|
|
|
|
168
|
|
|
$bucketUrl = new Uri($params['bucket']); |
|
169
|
|
|
if ($bucketUrl->getHost()) { |
|
170
|
|
|
$params['bucket'] = basename($bucketUrl->getPath()); |
|
171
|
|
|
$params['endpoint_url'] = (string)$bucketUrl->withPath(dirname($bucketUrl->getPath())); |
|
172
|
|
|
} |
|
173
|
|
|
|
|
174
|
|
|
if (empty($params['url'])) { |
|
175
|
|
|
$params['url'] = 'https://identity.api.rackspacecloud.com/v2.0/'; |
|
176
|
|
|
} |
|
177
|
|
|
|
|
178
|
|
|
if (empty($params['service_name'])) { |
|
179
|
|
|
$params['service_name'] = 'cloudFiles'; |
|
180
|
|
|
} |
|
181
|
|
|
|
|
182
|
|
|
$params['autocreate'] = true; |
|
183
|
|
|
|
|
184
|
|
|
if (isset($params['domain'])) { |
|
185
|
|
|
$params['user'] = [ |
|
186
|
|
|
'name' => $params['user'], |
|
187
|
|
|
'password' => $params['password'], |
|
188
|
|
|
'domain' => [ |
|
189
|
|
|
'name' => $params['domain'], |
|
190
|
|
|
] |
|
191
|
|
|
]; |
|
192
|
|
|
} |
|
193
|
|
|
|
|
194
|
|
|
$this->params = $params; |
|
195
|
|
|
// FIXME: private class... |
|
|
|
|
|
|
196
|
|
|
$this->objectCache = new \OC\Cache\CappedMemoryCache(); |
|
197
|
|
|
$this->connectionFactory = new SwiftFactory(\OC::$server->getMemCacheFactory()->createDistributed('swift/'), $this->params); |
|
198
|
|
|
$this->objectStore = new \OC\Files\ObjectStore\Swift($this->params, $this->connectionFactory); |
|
199
|
|
|
$this->bucket = $params['bucket']; |
|
200
|
|
|
} |
|
201
|
|
|
|
|
202
|
|
|
public function mkdir($path) { |
|
203
|
|
|
$path = $this->normalizePath($path); |
|
204
|
|
|
|
|
205
|
|
|
if ($this->is_dir($path)) { |
|
206
|
|
|
return false; |
|
207
|
|
|
} |
|
208
|
|
|
|
|
209
|
|
|
if ($path !== '.') { |
|
210
|
|
|
$path .= '/'; |
|
211
|
|
|
} |
|
212
|
|
|
|
|
213
|
|
|
try { |
|
214
|
|
|
$this->getContainer()->createObject([ |
|
215
|
|
|
'name' => $path, |
|
216
|
|
|
'content' => '', |
|
217
|
|
|
'headers' => ['content-type' => 'httpd/unix-directory'] |
|
218
|
|
|
]); |
|
219
|
|
|
// invalidate so that the next access gets the real object |
|
220
|
|
|
// with all properties |
|
221
|
|
|
$this->objectCache->remove($path); |
|
222
|
|
|
} catch (BadResponseError $e) { |
|
|
|
|
|
|
223
|
|
|
\OC::$server->getLogger()->logException($e, [ |
|
224
|
|
|
'level' => \OCP\Util::ERROR, |
|
225
|
|
|
'app' => 'files_external', |
|
226
|
|
|
]); |
|
227
|
|
|
return false; |
|
228
|
|
|
} |
|
229
|
|
|
|
|
230
|
|
|
return true; |
|
231
|
|
|
} |
|
232
|
|
|
|
|
233
|
|
|
public function file_exists($path) { |
|
234
|
|
|
$path = $this->normalizePath($path); |
|
235
|
|
|
|
|
236
|
|
|
if ($path !== '.' && $this->is_dir($path)) { |
|
237
|
|
|
$path .= '/'; |
|
238
|
|
|
} |
|
239
|
|
|
|
|
240
|
|
|
return $this->doesObjectExist($path); |
|
241
|
|
|
} |
|
242
|
|
|
|
|
243
|
|
|
public function rmdir($path) { |
|
244
|
|
|
$path = $this->normalizePath($path); |
|
245
|
|
|
|
|
246
|
|
|
if (!$this->is_dir($path) || !$this->isDeletable($path)) { |
|
247
|
|
|
return false; |
|
248
|
|
|
} |
|
249
|
|
|
|
|
250
|
|
|
$dh = $this->opendir($path); |
|
251
|
|
View Code Duplication |
while ($file = readdir($dh)) { |
|
252
|
|
|
if (\OC\Files\Filesystem::isIgnoredDir($file)) { |
|
253
|
|
|
continue; |
|
254
|
|
|
} |
|
255
|
|
|
|
|
256
|
|
|
if ($this->is_dir($path . '/' . $file)) { |
|
257
|
|
|
$this->rmdir($path . '/' . $file); |
|
258
|
|
|
} else { |
|
259
|
|
|
$this->unlink($path . '/' . $file); |
|
260
|
|
|
} |
|
261
|
|
|
} |
|
262
|
|
|
|
|
263
|
|
|
try { |
|
264
|
|
|
$this->objectStore->deleteObject($path . '/'); |
|
265
|
|
|
$this->objectCache->remove($path . '/'); |
|
266
|
|
|
} catch (BadResponseError $e) { |
|
|
|
|
|
|
267
|
|
|
\OC::$server->getLogger()->logException($e, [ |
|
268
|
|
|
'level' => \OCP\Util::ERROR, |
|
269
|
|
|
'app' => 'files_external', |
|
270
|
|
|
]); |
|
271
|
|
|
return false; |
|
272
|
|
|
} |
|
273
|
|
|
|
|
274
|
|
|
return true; |
|
275
|
|
|
} |
|
276
|
|
|
|
|
277
|
|
|
public function opendir($path) { |
|
278
|
|
|
$path = $this->normalizePath($path); |
|
279
|
|
|
|
|
280
|
|
|
if ($path === '.') { |
|
281
|
|
|
$path = ''; |
|
282
|
|
|
} else { |
|
283
|
|
|
$path .= '/'; |
|
284
|
|
|
} |
|
285
|
|
|
|
|
286
|
|
|
// $path = str_replace('%23', '#', $path); // the prefix is sent as a query param, so revert the encoding of # |
|
|
|
|
|
|
287
|
|
|
|
|
288
|
|
|
try { |
|
289
|
|
|
$files = []; |
|
290
|
|
|
$objects = $this->getContainer()->listObjects([ |
|
291
|
|
|
'prefix' => $path, |
|
292
|
|
|
'delimiter' => '/' |
|
293
|
|
|
]); |
|
294
|
|
|
|
|
295
|
|
|
/** @var StorageObject $object */ |
|
296
|
|
|
foreach ($objects as $object) { |
|
297
|
|
|
$file = basename($object->name); |
|
298
|
|
|
if ($file !== basename($path) && $file !== '.') { |
|
299
|
|
|
$files[] = $file; |
|
300
|
|
|
} |
|
301
|
|
|
} |
|
302
|
|
|
|
|
303
|
|
|
return IteratorDirectory::wrap($files); |
|
304
|
|
|
} catch (\Exception $e) { |
|
305
|
|
|
\OC::$server->getLogger()->logException($e, [ |
|
306
|
|
|
'level' => \OCP\Util::ERROR, |
|
307
|
|
|
'app' => 'files_external', |
|
308
|
|
|
]); |
|
309
|
|
|
return false; |
|
310
|
|
|
} |
|
311
|
|
|
|
|
312
|
|
|
} |
|
313
|
|
|
|
|
314
|
|
|
public function stat($path) { |
|
315
|
|
|
$path = $this->normalizePath($path); |
|
316
|
|
|
|
|
317
|
|
|
if ($path === '.') { |
|
318
|
|
|
$path = ''; |
|
319
|
|
|
} else if ($this->is_dir($path)) { |
|
320
|
|
|
$path .= '/'; |
|
321
|
|
|
} |
|
322
|
|
|
|
|
323
|
|
|
try { |
|
324
|
|
|
$object = $this->fetchObject($path); |
|
325
|
|
|
if (!$object) { |
|
326
|
|
|
return false; |
|
327
|
|
|
} |
|
328
|
|
|
} catch (BadResponseError $e) { |
|
|
|
|
|
|
329
|
|
|
\OC::$server->getLogger()->logException($e, [ |
|
330
|
|
|
'level' => \OCP\Util::ERROR, |
|
331
|
|
|
'app' => 'files_external', |
|
332
|
|
|
]); |
|
333
|
|
|
return false; |
|
334
|
|
|
} |
|
335
|
|
|
|
|
336
|
|
|
$dateTime = $object->lastModified ? \DateTime::createFromFormat(\DateTime::RFC1123, $object->lastModified) : false; |
|
337
|
|
|
$mtime = $dateTime ? $dateTime->getTimestamp() : null; |
|
338
|
|
|
$objectMetadata = $object->getMetadata(); |
|
339
|
|
|
if (isset($objectMetadata['timestamp'])) { |
|
340
|
|
|
$mtime = $objectMetadata['timestamp']; |
|
341
|
|
|
} |
|
342
|
|
|
|
|
343
|
|
|
if (!empty($mtime)) { |
|
344
|
|
|
$mtime = floor($mtime); |
|
345
|
|
|
} |
|
346
|
|
|
|
|
347
|
|
|
$stat = array(); |
|
348
|
|
|
$stat['size'] = (int)$object->contentLength; |
|
349
|
|
|
$stat['mtime'] = $mtime; |
|
350
|
|
|
$stat['atime'] = time(); |
|
351
|
|
|
return $stat; |
|
352
|
|
|
} |
|
353
|
|
|
|
|
354
|
|
|
public function filetype($path) { |
|
355
|
|
|
$path = $this->normalizePath($path); |
|
356
|
|
|
|
|
357
|
|
|
if ($path !== '.' && $this->doesObjectExist($path)) { |
|
358
|
|
|
return 'file'; |
|
359
|
|
|
} |
|
360
|
|
|
|
|
361
|
|
|
if ($path !== '.') { |
|
362
|
|
|
$path .= '/'; |
|
363
|
|
|
} |
|
364
|
|
|
|
|
365
|
|
|
if ($this->doesObjectExist($path)) { |
|
366
|
|
|
return 'dir'; |
|
367
|
|
|
} |
|
368
|
|
|
} |
|
369
|
|
|
|
|
370
|
|
|
public function unlink($path) { |
|
371
|
|
|
$path = $this->normalizePath($path); |
|
372
|
|
|
|
|
373
|
|
|
if ($this->is_dir($path)) { |
|
374
|
|
|
return $this->rmdir($path); |
|
375
|
|
|
} |
|
376
|
|
|
|
|
377
|
|
|
try { |
|
378
|
|
|
$this->objectStore->deleteObject($path); |
|
379
|
|
|
$this->objectCache->remove($path); |
|
380
|
|
|
$this->objectCache->remove($path . '/'); |
|
381
|
|
|
} catch (BadResponseError $e) { |
|
|
|
|
|
|
382
|
|
View Code Duplication |
if ($e->getResponse()->getStatusCode() !== 404) { |
|
383
|
|
|
\OC::$server->getLogger()->logException($e, [ |
|
384
|
|
|
'level' => \OCP\Util::ERROR, |
|
385
|
|
|
'app' => 'files_external', |
|
386
|
|
|
]); |
|
387
|
|
|
throw $e; |
|
388
|
|
|
} |
|
389
|
|
|
} |
|
390
|
|
|
|
|
391
|
|
|
return true; |
|
392
|
|
|
} |
|
393
|
|
|
|
|
394
|
|
|
public function fopen($path, $mode) { |
|
395
|
|
|
$path = $this->normalizePath($path); |
|
396
|
|
|
|
|
397
|
|
|
switch ($mode) { |
|
398
|
|
|
case 'a': |
|
399
|
|
|
case 'ab': |
|
400
|
|
|
case 'a+': |
|
401
|
|
|
return false; |
|
402
|
|
|
case 'r': |
|
403
|
|
|
case 'rb': |
|
|
|
|
|
|
404
|
|
|
try { |
|
405
|
|
|
return $this->objectStore->readObject($path); |
|
406
|
|
|
} catch (BadResponseError $e) { |
|
|
|
|
|
|
407
|
|
|
\OC::$server->getLogger()->logException($e, [ |
|
408
|
|
|
'level' => \OCP\Util::ERROR, |
|
409
|
|
|
'app' => 'files_external', |
|
410
|
|
|
]); |
|
411
|
|
|
return false; |
|
412
|
|
|
} |
|
413
|
|
|
case 'w': |
|
414
|
|
|
case 'wb': |
|
415
|
|
|
case 'r+': |
|
416
|
|
|
case 'w+': |
|
417
|
|
|
case 'wb+': |
|
418
|
|
|
case 'x': |
|
419
|
|
|
case 'x+': |
|
420
|
|
|
case 'c': |
|
421
|
|
|
case 'c+': |
|
422
|
|
|
if (strrpos($path, '.') !== false) { |
|
423
|
|
|
$ext = substr($path, strrpos($path, '.')); |
|
424
|
|
|
} else { |
|
425
|
|
|
$ext = ''; |
|
426
|
|
|
} |
|
427
|
|
|
$tmpFile = \OCP\Files::tmpFile($ext); |
|
428
|
|
|
// Fetch existing file if required |
|
429
|
|
|
if ($mode[0] !== 'w' && $this->file_exists($path)) { |
|
430
|
|
|
if ($mode[0] === 'x') { |
|
431
|
|
|
// File cannot already exist |
|
432
|
|
|
return false; |
|
433
|
|
|
} |
|
434
|
|
|
$source = $this->fopen($path, 'r'); |
|
435
|
|
|
file_put_contents($tmpFile, $source); |
|
436
|
|
|
} |
|
437
|
|
|
$handle = fopen($tmpFile, $mode); |
|
438
|
|
|
return CallbackWrapper::wrap($handle, null, null, function () use ($path, $tmpFile) { |
|
439
|
|
|
$this->writeBack($tmpFile, $path); |
|
440
|
|
|
}); |
|
441
|
|
|
} |
|
442
|
|
|
} |
|
443
|
|
|
|
|
444
|
|
|
public function touch($path, $mtime = null) { |
|
445
|
|
|
$path = $this->normalizePath($path); |
|
446
|
|
|
if (is_null($mtime)) { |
|
447
|
|
|
$mtime = time(); |
|
448
|
|
|
} |
|
449
|
|
|
$metadata = ['timestamp' => $mtime]; |
|
450
|
|
|
if ($this->file_exists($path)) { |
|
451
|
|
|
if ($this->is_dir($path) && $path !== '.') { |
|
452
|
|
|
$path .= '/'; |
|
453
|
|
|
} |
|
454
|
|
|
|
|
455
|
|
|
$object = $this->fetchObject($path); |
|
456
|
|
|
if ($object->mergeMetadata($metadata)) { |
|
457
|
|
|
// invalidate target object to force repopulation on fetch |
|
458
|
|
|
$this->objectCache->remove($path); |
|
459
|
|
|
} |
|
460
|
|
|
return true; |
|
461
|
|
|
} else { |
|
462
|
|
|
$mimeType = \OC::$server->getMimeTypeDetector()->detectPath($path); |
|
|
|
|
|
|
463
|
|
|
$this->getContainer()->createObject([ |
|
464
|
|
|
'name' => $path, |
|
465
|
|
|
'content' => '', |
|
466
|
|
|
'headers' => ['content-type' => 'httpd/unix-directory'] |
|
467
|
|
|
]); |
|
468
|
|
|
// invalidate target object to force repopulation on fetch |
|
469
|
|
|
$this->objectCache->remove($path); |
|
470
|
|
|
return true; |
|
471
|
|
|
} |
|
472
|
|
|
} |
|
473
|
|
|
|
|
474
|
|
|
public function copy($path1, $path2) { |
|
475
|
|
|
$path1 = $this->normalizePath($path1); |
|
476
|
|
|
$path2 = $this->normalizePath($path2); |
|
477
|
|
|
|
|
478
|
|
|
$fileType = $this->filetype($path1); |
|
479
|
|
|
if ($fileType) { |
|
|
|
|
|
|
480
|
|
|
// make way |
|
481
|
|
|
$this->unlink($path2); |
|
482
|
|
|
} |
|
483
|
|
|
|
|
484
|
|
|
if ($fileType === 'file') { |
|
485
|
|
|
try { |
|
486
|
|
|
$source = $this->fetchObject($path1); |
|
487
|
|
|
$source->copy([ |
|
488
|
|
|
'destination' => $this->bucket . '/' . $path2 |
|
489
|
|
|
]); |
|
490
|
|
|
// invalidate target object to force repopulation on fetch |
|
491
|
|
|
$this->objectCache->remove($path2); |
|
492
|
|
|
$this->objectCache->remove($path2 . '/'); |
|
493
|
|
|
} catch (BadResponseError $e) { |
|
|
|
|
|
|
494
|
|
|
\OC::$server->getLogger()->logException($e, [ |
|
495
|
|
|
'level' => \OCP\Util::ERROR, |
|
496
|
|
|
'app' => 'files_external', |
|
497
|
|
|
]); |
|
498
|
|
|
return false; |
|
499
|
|
|
} |
|
500
|
|
|
|
|
501
|
|
|
} else if ($fileType === 'dir') { |
|
502
|
|
|
try { |
|
503
|
|
|
$source = $this->fetchObject($path1 . '/'); |
|
504
|
|
|
$source->copy([ |
|
505
|
|
|
'destination' => $this->bucket . '/' . $path2 . '/' |
|
506
|
|
|
]); |
|
507
|
|
|
// invalidate target object to force repopulation on fetch |
|
508
|
|
|
$this->objectCache->remove($path2); |
|
509
|
|
|
$this->objectCache->remove($path2 . '/'); |
|
510
|
|
|
} catch (BadResponseError $e) { |
|
|
|
|
|
|
511
|
|
|
\OC::$server->getLogger()->logException($e, [ |
|
512
|
|
|
'level' => \OCP\Util::ERROR, |
|
513
|
|
|
'app' => 'files_external', |
|
514
|
|
|
]); |
|
515
|
|
|
return false; |
|
516
|
|
|
} |
|
517
|
|
|
|
|
518
|
|
|
$dh = $this->opendir($path1); |
|
519
|
|
|
while ($file = readdir($dh)) { |
|
520
|
|
|
if (\OC\Files\Filesystem::isIgnoredDir($file)) { |
|
521
|
|
|
continue; |
|
522
|
|
|
} |
|
523
|
|
|
|
|
524
|
|
|
$source = $path1 . '/' . $file; |
|
525
|
|
|
$target = $path2 . '/' . $file; |
|
526
|
|
|
$this->copy($source, $target); |
|
527
|
|
|
} |
|
528
|
|
|
|
|
529
|
|
|
} else { |
|
530
|
|
|
//file does not exist |
|
531
|
|
|
return false; |
|
532
|
|
|
} |
|
533
|
|
|
|
|
534
|
|
|
return true; |
|
535
|
|
|
} |
|
536
|
|
|
|
|
537
|
|
|
public function rename($path1, $path2) { |
|
538
|
|
|
$path1 = $this->normalizePath($path1); |
|
539
|
|
|
$path2 = $this->normalizePath($path2); |
|
540
|
|
|
|
|
541
|
|
|
$fileType = $this->filetype($path1); |
|
542
|
|
|
|
|
543
|
|
|
if ($fileType === 'dir' || $fileType === 'file') { |
|
544
|
|
|
// copy |
|
545
|
|
|
if ($this->copy($path1, $path2) === false) { |
|
546
|
|
|
return false; |
|
547
|
|
|
} |
|
548
|
|
|
|
|
549
|
|
|
// cleanup |
|
550
|
|
|
if ($this->unlink($path1) === false) { |
|
551
|
|
|
throw new \Exception('failed to remove original'); |
|
552
|
|
|
$this->unlink($path2); |
|
|
|
|
|
|
553
|
|
|
return false; |
|
554
|
|
|
} |
|
555
|
|
|
|
|
556
|
|
|
return true; |
|
557
|
|
|
} |
|
558
|
|
|
|
|
559
|
|
|
return false; |
|
560
|
|
|
} |
|
561
|
|
|
|
|
562
|
|
|
public function getId() { |
|
563
|
|
|
return $this->id; |
|
564
|
|
|
} |
|
565
|
|
|
|
|
566
|
|
|
/** |
|
567
|
|
|
* Returns the initialized object store container. |
|
568
|
|
|
* |
|
569
|
|
|
* @return \OpenStack\ObjectStore\v1\Models\Container |
|
570
|
|
|
* @throws \OCP\Files\StorageAuthException |
|
571
|
|
|
* @throws \OCP\Files\StorageNotAvailableException |
|
572
|
|
|
*/ |
|
573
|
|
|
public function getContainer() { |
|
574
|
|
|
if (is_null($this->container)) { |
|
575
|
|
|
$this->container = $this->connectionFactory->getContainer(); |
|
576
|
|
|
|
|
577
|
|
|
if (!$this->file_exists('.')) { |
|
578
|
|
|
$this->mkdir('.'); |
|
579
|
|
|
} |
|
580
|
|
|
} |
|
581
|
|
|
return $this->container; |
|
582
|
|
|
} |
|
583
|
|
|
|
|
584
|
|
|
public function writeBack($tmpFile, $path) { |
|
585
|
|
|
$fileData = fopen($tmpFile, 'r'); |
|
586
|
|
|
$this->objectStore->writeObject($path, $fileData); |
|
587
|
|
|
// invalidate target object to force repopulation on fetch |
|
588
|
|
|
$this->objectCache->remove($path); |
|
589
|
|
|
unlink($tmpFile); |
|
590
|
|
|
} |
|
591
|
|
|
|
|
592
|
|
|
public function hasUpdated($path, $time) { |
|
593
|
|
|
if ($this->is_file($path)) { |
|
594
|
|
|
return parent::hasUpdated($path, $time); |
|
595
|
|
|
} |
|
596
|
|
|
$path = $this->normalizePath($path); |
|
597
|
|
|
$dh = $this->opendir($path); |
|
598
|
|
|
$content = array(); |
|
599
|
|
|
while (($file = readdir($dh)) !== false) { |
|
600
|
|
|
$content[] = $file; |
|
601
|
|
|
} |
|
602
|
|
|
if ($path === '.') { |
|
603
|
|
|
$path = ''; |
|
604
|
|
|
} |
|
605
|
|
|
$cachedContent = $this->getCache()->getFolderContents($path); |
|
606
|
|
|
$cachedNames = array_map(function ($content) { |
|
607
|
|
|
return $content['name']; |
|
608
|
|
|
}, $cachedContent); |
|
609
|
|
|
sort($cachedNames); |
|
610
|
|
|
sort($content); |
|
611
|
|
|
return $cachedNames !== $content; |
|
612
|
|
|
} |
|
613
|
|
|
|
|
614
|
|
|
/** |
|
615
|
|
|
* check if curl is installed |
|
616
|
|
|
*/ |
|
617
|
|
|
public static function checkDependencies() { |
|
618
|
|
|
return true; |
|
619
|
|
|
} |
|
620
|
|
|
|
|
621
|
|
|
} |
|
622
|
|
|
|
Scrutinizer analyzes your
composer.json/composer.lockfile if available to determine the classes, and functions that are defined by your dependencies.It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.