Completed
Push — master ( d34320...71c2e9 )
by
unknown
27:32
created
lib/public/Files/ObjectStore/IObjectStore.php 1 patch
Indentation   +48 added lines, -48 removed lines patch added patch discarded remove patch
@@ -15,58 +15,58 @@
 block discarded – undo
15 15
  * @since 7.0.0
16 16
  */
17 17
 interface IObjectStore {
18
-	/**
19
-	 * @return string the container or bucket name where objects are stored
20
-	 * @since 7.0.0
21
-	 */
22
-	public function getStorageId();
18
+    /**
19
+     * @return string the container or bucket name where objects are stored
20
+     * @since 7.0.0
21
+     */
22
+    public function getStorageId();
23 23
 
24
-	/**
25
-	 * @param string $urn the unified resource name used to identify the object
26
-	 * @return resource stream with the read data
27
-	 * @throws \Exception when something goes wrong, message will be logged
28
-	 * @throws NotFoundException if file does not exist
29
-	 * @since 7.0.0
30
-	 */
31
-	public function readObject($urn);
24
+    /**
25
+     * @param string $urn the unified resource name used to identify the object
26
+     * @return resource stream with the read data
27
+     * @throws \Exception when something goes wrong, message will be logged
28
+     * @throws NotFoundException if file does not exist
29
+     * @since 7.0.0
30
+     */
31
+    public function readObject($urn);
32 32
 
33
-	/**
34
-	 * @param string $urn the unified resource name used to identify the object
35
-	 * @param resource $stream stream with the data to write
36
-	 * @param string|null $mimetype the mimetype to set for the remove object @since 22.0.0
37
-	 * @throws \Exception when something goes wrong, message will be logged
38
-	 * @since 7.0.0
39
-	 */
40
-	public function writeObject($urn, $stream, ?string $mimetype = null);
33
+    /**
34
+     * @param string $urn the unified resource name used to identify the object
35
+     * @param resource $stream stream with the data to write
36
+     * @param string|null $mimetype the mimetype to set for the remove object @since 22.0.0
37
+     * @throws \Exception when something goes wrong, message will be logged
38
+     * @since 7.0.0
39
+     */
40
+    public function writeObject($urn, $stream, ?string $mimetype = null);
41 41
 
42
-	/**
43
-	 * @param string $urn the unified resource name used to identify the object
44
-	 * @return void
45
-	 * @throws \Exception when something goes wrong, message will be logged
46
-	 * @since 7.0.0
47
-	 */
48
-	public function deleteObject($urn);
42
+    /**
43
+     * @param string $urn the unified resource name used to identify the object
44
+     * @return void
45
+     * @throws \Exception when something goes wrong, message will be logged
46
+     * @since 7.0.0
47
+     */
48
+    public function deleteObject($urn);
49 49
 
50
-	/**
51
-	 * Check if an object exists in the object store
52
-	 *
53
-	 * @param string $urn
54
-	 * @return bool
55
-	 * @since 16.0.0
56
-	 */
57
-	public function objectExists($urn);
50
+    /**
51
+     * Check if an object exists in the object store
52
+     *
53
+     * @param string $urn
54
+     * @return bool
55
+     * @since 16.0.0
56
+     */
57
+    public function objectExists($urn);
58 58
 
59
-	/**
60
-	 * @param string $from the unified resource name used to identify the source object
61
-	 * @param string $to the unified resource name used to identify the target object
62
-	 * @return void
63
-	 * @since 21.0.0
64
-	 */
65
-	public function copyObject($from, $to);
59
+    /**
60
+     * @param string $from the unified resource name used to identify the source object
61
+     * @param string $to the unified resource name used to identify the target object
62
+     * @return void
63
+     * @since 21.0.0
64
+     */
65
+    public function copyObject($from, $to);
66 66
 
67
-	/**
68
-	 * Get pre signed url for an object
69
-	 * @since 33.0.0
70
-	 */
71
-	public function preSignedUrl(string $urn, \DateTimeInterface $expiration): ?string;
67
+    /**
68
+     * Get pre signed url for an object
69
+     * @since 33.0.0
70
+     */
71
+    public function preSignedUrl(string $urn, \DateTimeInterface $expiration): ?string;
72 72
 }
Please login to merge, or discard this patch.
lib/public/Files/Storage/IStorage.php 2 patches
Indentation   +391 added lines, -391 removed lines patch added patch discarded remove patch
@@ -26,395 +26,395 @@
 block discarded – undo
26 26
  * @since 31.0.0 Moved the constructor to IConstructableStorage so that wrappers can use DI
27 27
  */
28 28
 interface IStorage {
29
-	/**
30
-	 * Get the identifier for the storage,
31
-	 * the returned id should be the same for every storage object that is created with the same parameters
32
-	 * and two storage objects with the same id should refer to two storages that display the same files.
33
-	 *
34
-	 * @return string
35
-	 * @since 9.0.0
36
-	 */
37
-	public function getId();
38
-
39
-	/**
40
-	 * see https://www.php.net/manual/en/function.mkdir.php
41
-	 * implementations need to implement a recursive mkdir
42
-	 *
43
-	 * @return bool
44
-	 * @since 9.0.0
45
-	 */
46
-	public function mkdir(string $path);
47
-
48
-	/**
49
-	 * see https://www.php.net/manual/en/function.rmdir.php
50
-	 *
51
-	 * @return bool
52
-	 * @since 9.0.0
53
-	 */
54
-	public function rmdir(string $path);
55
-
56
-	/**
57
-	 * see https://www.php.net/manual/en/function.opendir.php
58
-	 *
59
-	 * @return resource|false
60
-	 * @since 9.0.0
61
-	 */
62
-	public function opendir(string $path);
63
-
64
-	/**
65
-	 * see https://www.php.net/manual/en/function.is-dir.php
66
-	 *
67
-	 * @return bool
68
-	 * @since 9.0.0
69
-	 */
70
-	public function is_dir(string $path);
71
-
72
-	/**
73
-	 * see https://www.php.net/manual/en/function.is-file.php
74
-	 *
75
-	 * @return bool
76
-	 * @since 9.0.0
77
-	 */
78
-	public function is_file(string $path);
79
-
80
-	/**
81
-	 * see https://www.php.net/manual/en/function.stat.php
82
-	 * only the following keys are required in the result: size and mtime
83
-	 *
84
-	 * @return array|false
85
-	 * @since 9.0.0
86
-	 */
87
-	public function stat(string $path);
88
-
89
-	/**
90
-	 * see https://www.php.net/manual/en/function.filetype.php
91
-	 *
92
-	 * @return string|false
93
-	 * @since 9.0.0
94
-	 */
95
-	public function filetype(string $path);
96
-
97
-	/**
98
-	 * see https://www.php.net/manual/en/function.filesize.php
99
-	 * The result for filesize when called on a folder is required to be 0
100
-	 *
101
-	 * @return int|float|false
102
-	 * @since 9.0.0
103
-	 */
104
-	public function filesize(string $path);
105
-
106
-	/**
107
-	 * check if a file can be created in $path
108
-	 *
109
-	 * @return bool
110
-	 * @since 9.0.0
111
-	 */
112
-	public function isCreatable(string $path);
113
-
114
-	/**
115
-	 * check if a file can be read
116
-	 *
117
-	 * @return bool
118
-	 * @since 9.0.0
119
-	 */
120
-	public function isReadable(string $path);
121
-
122
-	/**
123
-	 * check if a file can be written to
124
-	 *
125
-	 * @return bool
126
-	 * @since 9.0.0
127
-	 */
128
-	public function isUpdatable(string $path);
129
-
130
-	/**
131
-	 * check if a file can be deleted
132
-	 *
133
-	 * @return bool
134
-	 * @since 9.0.0
135
-	 */
136
-	public function isDeletable(string $path);
137
-
138
-	/**
139
-	 * check if a file can be shared
140
-	 *
141
-	 * @return bool
142
-	 * @since 9.0.0
143
-	 */
144
-	public function isSharable(string $path);
145
-
146
-	/**
147
-	 * get the full permissions of a path.
148
-	 * Should return a combination of the PERMISSION_ constants defined in lib/public/constants.php
149
-	 *
150
-	 * @return int
151
-	 * @since 9.0.0
152
-	 */
153
-	public function getPermissions(string $path);
154
-
155
-	/**
156
-	 * see https://www.php.net/manual/en/function.file-exists.php
157
-	 *
158
-	 * @return bool
159
-	 * @since 9.0.0
160
-	 */
161
-	public function file_exists(string $path);
162
-
163
-	/**
164
-	 * see https://www.php.net/manual/en/function.filemtime.php
165
-	 *
166
-	 * @return int|false
167
-	 * @since 9.0.0
168
-	 */
169
-	public function filemtime(string $path);
170
-
171
-	/**
172
-	 * see https://www.php.net/manual/en/function.file-get-contents.php
173
-	 *
174
-	 * @return string|false
175
-	 * @since 9.0.0
176
-	 */
177
-	public function file_get_contents(string $path);
178
-
179
-	/**
180
-	 * see https://www.php.net/manual/en/function.file-put-contents.php
181
-	 *
182
-	 * @return int|float|false
183
-	 * @since 9.0.0
184
-	 */
185
-	public function file_put_contents(string $path, mixed $data);
186
-
187
-	/**
188
-	 * see https://www.php.net/manual/en/function.unlink.php
189
-	 *
190
-	 * @return bool
191
-	 * @since 9.0.0
192
-	 */
193
-	public function unlink(string $path);
194
-
195
-	/**
196
-	 * see https://www.php.net/manual/en/function.rename.php
197
-	 *
198
-	 * @return bool
199
-	 * @since 9.0.0
200
-	 */
201
-	public function rename(string $source, string $target);
202
-
203
-	/**
204
-	 * see https://www.php.net/manual/en/function.copy.php
205
-	 *
206
-	 * @return bool
207
-	 * @since 9.0.0
208
-	 */
209
-	public function copy(string $source, string $target);
210
-
211
-	/**
212
-	 * see https://www.php.net/manual/en/function.fopen.php
213
-	 *
214
-	 * @return resource|false
215
-	 * @since 9.0.0
216
-	 */
217
-	public function fopen(string $path, string $mode);
218
-
219
-	/**
220
-	 * get the mimetype for a file or folder
221
-	 * The mimetype for a folder is required to be "httpd/unix-directory"
222
-	 *
223
-	 * @return string|false
224
-	 * @since 9.0.0
225
-	 */
226
-	public function getMimeType(string $path);
227
-
228
-	/**
229
-	 * see https://www.php.net/manual/en/function.hash-file.php
230
-	 *
231
-	 * @return string|false
232
-	 * @since 9.0.0
233
-	 */
234
-	public function hash(string $type, string $path, bool $raw = false);
235
-
236
-	/**
237
-	 * see https://www.php.net/manual/en/function.disk-free-space.php
238
-	 *
239
-	 * @return int|float|false
240
-	 * @since 9.0.0
241
-	 */
242
-	public function free_space(string $path);
243
-
244
-	/**
245
-	 * see https://www.php.net/manual/en/function.touch.php
246
-	 * If the backend does not support the operation, false should be returned
247
-	 *
248
-	 * @return bool
249
-	 * @since 9.0.0
250
-	 */
251
-	public function touch(string $path, ?int $mtime = null);
252
-
253
-	/**
254
-	 * get the path to a local version of the file.
255
-	 * The local version of the file can be temporary and doesn't have to be persistent across requests
256
-	 *
257
-	 * @return string|false
258
-	 * @since 9.0.0
259
-	 */
260
-	public function getLocalFile(string $path);
261
-
262
-	/**
263
-	 * check if a file or folder has been updated since $time
264
-	 *
265
-	 * @return bool
266
-	 * @since 9.0.0
267
-	 *
268
-	 * hasUpdated for folders should return at least true if a file inside the folder is add, removed or renamed.
269
-	 * returning true for other changes in the folder is optional
270
-	 */
271
-	public function hasUpdated(string $path, int $time);
272
-
273
-	/**
274
-	 * get the ETag for a file or folder
275
-	 *
276
-	 * @return string|false
277
-	 * @since 9.0.0
278
-	 */
279
-	public function getETag(string $path);
280
-
281
-	/**
282
-	 * Returns whether the storage is local, which means that files
283
-	 * are stored on the local filesystem instead of remotely.
284
-	 * Calling getLocalFile() for local storages should always
285
-	 * return the local files, whereas for non-local storages
286
-	 * it might return a temporary file.
287
-	 *
288
-	 * @return bool true if the files are stored locally, false otherwise
289
-	 * @since 9.0.0
290
-	 */
291
-	public function isLocal();
292
-
293
-	/**
294
-	 * Check if the storage is an instance of $class or is a wrapper for a storage that is an instance of $class
295
-	 *
296
-	 * @template T of IStorage
297
-	 * @psalm-param class-string<T> $class
298
-	 * @return bool
299
-	 * @since 9.0.0
300
-	 * @psalm-assert-if-true T $this
301
-	 */
302
-	public function instanceOfStorage(string $class);
303
-
304
-	/**
305
-	 * A custom storage implementation can return a url for direct download of a give file.
306
-	 *
307
-	 * For now the returned array can hold the parameter url and expiration - in future more attributes might follow.
308
-	 *
309
-	 * @param string $path Either the path or the fileId
310
-	 * @return array{url: ?string, expiration: ?int}|false
311
-	 * @since 9.0.0
312
-	 * @deprecated Use IStorage::getDirectDownloadById instead.
313
-	 */
314
-	public function getDirectDownload(string $path);
315
-
316
-	/**
317
-	 * A custom storage implementation can return a url for direct download of a give file.
318
-	 *
319
-	 * For now the returned array can hold the parameter url and expiration - in future more attributes might follow.
320
-	 *
321
-	 * @param string $fileId The fileId of the file.
322
-	 * @return array{url: ?string, expiration: ?int}|false
323
-	 * @since 33.0.0
324
-	 */
325
-	public function getDirectDownloadById(string $fileId): array|false;
326
-
327
-	/**
328
-	 * @return void
329
-	 * @throws InvalidPathException
330
-	 * @since 9.0.0
331
-	 */
332
-	public function verifyPath(string $path, string $fileName);
333
-
334
-	/**
335
-	 * @return bool
336
-	 * @since 9.0.0
337
-	 */
338
-	public function copyFromStorage(IStorage $sourceStorage, string $sourceInternalPath, string $targetInternalPath);
339
-
340
-	/**
341
-	 * @return bool
342
-	 * @since 9.0.0
343
-	 */
344
-	public function moveFromStorage(IStorage $sourceStorage, string $sourceInternalPath, string $targetInternalPath);
345
-
346
-	/**
347
-	 * Test a storage for availability
348
-	 *
349
-	 * @since 9.0.0
350
-	 * @return bool
351
-	 */
352
-	public function test();
353
-
354
-	/**
355
-	 * @since 9.0.0
356
-	 * @return array [ available, last_checked ]
357
-	 */
358
-	public function getAvailability();
359
-
360
-	/**
361
-	 * @since 9.0.0
362
-	 * @return void
363
-	 */
364
-	public function setAvailability(bool $isAvailable);
365
-
366
-	/**
367
-	 * @since 12.0.0
368
-	 * @since 31.0.0 moved from Storage to IStorage
369
-	 * @return bool
370
-	 */
371
-	public function needsPartFile();
372
-
373
-	/**
374
-	 * @return string|false
375
-	 * @since 9.0.0
376
-	 */
377
-	public function getOwner(string $path);
378
-
379
-	/**
380
-	 * @return ICache
381
-	 * @since 9.0.0
382
-	 */
383
-	public function getCache(string $path = '', ?IStorage $storage = null);
384
-
385
-	/**
386
-	 * @return IPropagator
387
-	 * @since 9.0.0
388
-	 */
389
-	public function getPropagator();
390
-
391
-	/**
392
-	 * @return IScanner
393
-	 * @since 9.0.0
394
-	 */
395
-	public function getScanner();
396
-
397
-	/**
398
-	 * @return IUpdater
399
-	 * @since 9.0.0
400
-	 */
401
-	public function getUpdater();
402
-
403
-	/**
404
-	 * @return IWatcher
405
-	 * @since 9.0.0
406
-	 */
407
-	public function getWatcher();
408
-
409
-	/**
410
-	 * Allow setting the storage owner
411
-	 *
412
-	 * This can be used for storages that do not have a dedicated owner, where we want to
413
-	 * pass the user that we setup the mountpoint for along to the storage layer
414
-	 *
415
-	 * @param ?string $user Owner user id
416
-	 * @return void
417
-	 * @since 30.0.0
418
-	 */
419
-	public function setOwner(?string $user): void;
29
+    /**
30
+     * Get the identifier for the storage,
31
+     * the returned id should be the same for every storage object that is created with the same parameters
32
+     * and two storage objects with the same id should refer to two storages that display the same files.
33
+     *
34
+     * @return string
35
+     * @since 9.0.0
36
+     */
37
+    public function getId();
38
+
39
+    /**
40
+     * see https://www.php.net/manual/en/function.mkdir.php
41
+     * implementations need to implement a recursive mkdir
42
+     *
43
+     * @return bool
44
+     * @since 9.0.0
45
+     */
46
+    public function mkdir(string $path);
47
+
48
+    /**
49
+     * see https://www.php.net/manual/en/function.rmdir.php
50
+     *
51
+     * @return bool
52
+     * @since 9.0.0
53
+     */
54
+    public function rmdir(string $path);
55
+
56
+    /**
57
+     * see https://www.php.net/manual/en/function.opendir.php
58
+     *
59
+     * @return resource|false
60
+     * @since 9.0.0
61
+     */
62
+    public function opendir(string $path);
63
+
64
+    /**
65
+     * see https://www.php.net/manual/en/function.is-dir.php
66
+     *
67
+     * @return bool
68
+     * @since 9.0.0
69
+     */
70
+    public function is_dir(string $path);
71
+
72
+    /**
73
+     * see https://www.php.net/manual/en/function.is-file.php
74
+     *
75
+     * @return bool
76
+     * @since 9.0.0
77
+     */
78
+    public function is_file(string $path);
79
+
80
+    /**
81
+     * see https://www.php.net/manual/en/function.stat.php
82
+     * only the following keys are required in the result: size and mtime
83
+     *
84
+     * @return array|false
85
+     * @since 9.0.0
86
+     */
87
+    public function stat(string $path);
88
+
89
+    /**
90
+     * see https://www.php.net/manual/en/function.filetype.php
91
+     *
92
+     * @return string|false
93
+     * @since 9.0.0
94
+     */
95
+    public function filetype(string $path);
96
+
97
+    /**
98
+     * see https://www.php.net/manual/en/function.filesize.php
99
+     * The result for filesize when called on a folder is required to be 0
100
+     *
101
+     * @return int|float|false
102
+     * @since 9.0.0
103
+     */
104
+    public function filesize(string $path);
105
+
106
+    /**
107
+     * check if a file can be created in $path
108
+     *
109
+     * @return bool
110
+     * @since 9.0.0
111
+     */
112
+    public function isCreatable(string $path);
113
+
114
+    /**
115
+     * check if a file can be read
116
+     *
117
+     * @return bool
118
+     * @since 9.0.0
119
+     */
120
+    public function isReadable(string $path);
121
+
122
+    /**
123
+     * check if a file can be written to
124
+     *
125
+     * @return bool
126
+     * @since 9.0.0
127
+     */
128
+    public function isUpdatable(string $path);
129
+
130
+    /**
131
+     * check if a file can be deleted
132
+     *
133
+     * @return bool
134
+     * @since 9.0.0
135
+     */
136
+    public function isDeletable(string $path);
137
+
138
+    /**
139
+     * check if a file can be shared
140
+     *
141
+     * @return bool
142
+     * @since 9.0.0
143
+     */
144
+    public function isSharable(string $path);
145
+
146
+    /**
147
+     * get the full permissions of a path.
148
+     * Should return a combination of the PERMISSION_ constants defined in lib/public/constants.php
149
+     *
150
+     * @return int
151
+     * @since 9.0.0
152
+     */
153
+    public function getPermissions(string $path);
154
+
155
+    /**
156
+     * see https://www.php.net/manual/en/function.file-exists.php
157
+     *
158
+     * @return bool
159
+     * @since 9.0.0
160
+     */
161
+    public function file_exists(string $path);
162
+
163
+    /**
164
+     * see https://www.php.net/manual/en/function.filemtime.php
165
+     *
166
+     * @return int|false
167
+     * @since 9.0.0
168
+     */
169
+    public function filemtime(string $path);
170
+
171
+    /**
172
+     * see https://www.php.net/manual/en/function.file-get-contents.php
173
+     *
174
+     * @return string|false
175
+     * @since 9.0.0
176
+     */
177
+    public function file_get_contents(string $path);
178
+
179
+    /**
180
+     * see https://www.php.net/manual/en/function.file-put-contents.php
181
+     *
182
+     * @return int|float|false
183
+     * @since 9.0.0
184
+     */
185
+    public function file_put_contents(string $path, mixed $data);
186
+
187
+    /**
188
+     * see https://www.php.net/manual/en/function.unlink.php
189
+     *
190
+     * @return bool
191
+     * @since 9.0.0
192
+     */
193
+    public function unlink(string $path);
194
+
195
+    /**
196
+     * see https://www.php.net/manual/en/function.rename.php
197
+     *
198
+     * @return bool
199
+     * @since 9.0.0
200
+     */
201
+    public function rename(string $source, string $target);
202
+
203
+    /**
204
+     * see https://www.php.net/manual/en/function.copy.php
205
+     *
206
+     * @return bool
207
+     * @since 9.0.0
208
+     */
209
+    public function copy(string $source, string $target);
210
+
211
+    /**
212
+     * see https://www.php.net/manual/en/function.fopen.php
213
+     *
214
+     * @return resource|false
215
+     * @since 9.0.0
216
+     */
217
+    public function fopen(string $path, string $mode);
218
+
219
+    /**
220
+     * get the mimetype for a file or folder
221
+     * The mimetype for a folder is required to be "httpd/unix-directory"
222
+     *
223
+     * @return string|false
224
+     * @since 9.0.0
225
+     */
226
+    public function getMimeType(string $path);
227
+
228
+    /**
229
+     * see https://www.php.net/manual/en/function.hash-file.php
230
+     *
231
+     * @return string|false
232
+     * @since 9.0.0
233
+     */
234
+    public function hash(string $type, string $path, bool $raw = false);
235
+
236
+    /**
237
+     * see https://www.php.net/manual/en/function.disk-free-space.php
238
+     *
239
+     * @return int|float|false
240
+     * @since 9.0.0
241
+     */
242
+    public function free_space(string $path);
243
+
244
+    /**
245
+     * see https://www.php.net/manual/en/function.touch.php
246
+     * If the backend does not support the operation, false should be returned
247
+     *
248
+     * @return bool
249
+     * @since 9.0.0
250
+     */
251
+    public function touch(string $path, ?int $mtime = null);
252
+
253
+    /**
254
+     * get the path to a local version of the file.
255
+     * The local version of the file can be temporary and doesn't have to be persistent across requests
256
+     *
257
+     * @return string|false
258
+     * @since 9.0.0
259
+     */
260
+    public function getLocalFile(string $path);
261
+
262
+    /**
263
+     * check if a file or folder has been updated since $time
264
+     *
265
+     * @return bool
266
+     * @since 9.0.0
267
+     *
268
+     * hasUpdated for folders should return at least true if a file inside the folder is add, removed or renamed.
269
+     * returning true for other changes in the folder is optional
270
+     */
271
+    public function hasUpdated(string $path, int $time);
272
+
273
+    /**
274
+     * get the ETag for a file or folder
275
+     *
276
+     * @return string|false
277
+     * @since 9.0.0
278
+     */
279
+    public function getETag(string $path);
280
+
281
+    /**
282
+     * Returns whether the storage is local, which means that files
283
+     * are stored on the local filesystem instead of remotely.
284
+     * Calling getLocalFile() for local storages should always
285
+     * return the local files, whereas for non-local storages
286
+     * it might return a temporary file.
287
+     *
288
+     * @return bool true if the files are stored locally, false otherwise
289
+     * @since 9.0.0
290
+     */
291
+    public function isLocal();
292
+
293
+    /**
294
+     * Check if the storage is an instance of $class or is a wrapper for a storage that is an instance of $class
295
+     *
296
+     * @template T of IStorage
297
+     * @psalm-param class-string<T> $class
298
+     * @return bool
299
+     * @since 9.0.0
300
+     * @psalm-assert-if-true T $this
301
+     */
302
+    public function instanceOfStorage(string $class);
303
+
304
+    /**
305
+     * A custom storage implementation can return a url for direct download of a give file.
306
+     *
307
+     * For now the returned array can hold the parameter url and expiration - in future more attributes might follow.
308
+     *
309
+     * @param string $path Either the path or the fileId
310
+     * @return array{url: ?string, expiration: ?int}|false
311
+     * @since 9.0.0
312
+     * @deprecated Use IStorage::getDirectDownloadById instead.
313
+     */
314
+    public function getDirectDownload(string $path);
315
+
316
+    /**
317
+     * A custom storage implementation can return a url for direct download of a give file.
318
+     *
319
+     * For now the returned array can hold the parameter url and expiration - in future more attributes might follow.
320
+     *
321
+     * @param string $fileId The fileId of the file.
322
+     * @return array{url: ?string, expiration: ?int}|false
323
+     * @since 33.0.0
324
+     */
325
+    public function getDirectDownloadById(string $fileId): array|false;
326
+
327
+    /**
328
+     * @return void
329
+     * @throws InvalidPathException
330
+     * @since 9.0.0
331
+     */
332
+    public function verifyPath(string $path, string $fileName);
333
+
334
+    /**
335
+     * @return bool
336
+     * @since 9.0.0
337
+     */
338
+    public function copyFromStorage(IStorage $sourceStorage, string $sourceInternalPath, string $targetInternalPath);
339
+
340
+    /**
341
+     * @return bool
342
+     * @since 9.0.0
343
+     */
344
+    public function moveFromStorage(IStorage $sourceStorage, string $sourceInternalPath, string $targetInternalPath);
345
+
346
+    /**
347
+     * Test a storage for availability
348
+     *
349
+     * @since 9.0.0
350
+     * @return bool
351
+     */
352
+    public function test();
353
+
354
+    /**
355
+     * @since 9.0.0
356
+     * @return array [ available, last_checked ]
357
+     */
358
+    public function getAvailability();
359
+
360
+    /**
361
+     * @since 9.0.0
362
+     * @return void
363
+     */
364
+    public function setAvailability(bool $isAvailable);
365
+
366
+    /**
367
+     * @since 12.0.0
368
+     * @since 31.0.0 moved from Storage to IStorage
369
+     * @return bool
370
+     */
371
+    public function needsPartFile();
372
+
373
+    /**
374
+     * @return string|false
375
+     * @since 9.0.0
376
+     */
377
+    public function getOwner(string $path);
378
+
379
+    /**
380
+     * @return ICache
381
+     * @since 9.0.0
382
+     */
383
+    public function getCache(string $path = '', ?IStorage $storage = null);
384
+
385
+    /**
386
+     * @return IPropagator
387
+     * @since 9.0.0
388
+     */
389
+    public function getPropagator();
390
+
391
+    /**
392
+     * @return IScanner
393
+     * @since 9.0.0
394
+     */
395
+    public function getScanner();
396
+
397
+    /**
398
+     * @return IUpdater
399
+     * @since 9.0.0
400
+     */
401
+    public function getUpdater();
402
+
403
+    /**
404
+     * @return IWatcher
405
+     * @since 9.0.0
406
+     */
407
+    public function getWatcher();
408
+
409
+    /**
410
+     * Allow setting the storage owner
411
+     *
412
+     * This can be used for storages that do not have a dedicated owner, where we want to
413
+     * pass the user that we setup the mountpoint for along to the storage layer
414
+     *
415
+     * @param ?string $user Owner user id
416
+     * @return void
417
+     * @since 30.0.0
418
+     */
419
+    public function setOwner(?string $user): void;
420 420
 }
Please login to merge, or discard this patch.
Spacing   +1 added lines, -1 removed lines patch added patch discarded remove patch
@@ -322,7 +322,7 @@
 block discarded – undo
322 322
 	 * @return array{url: ?string, expiration: ?int}|false
323 323
 	 * @since 33.0.0
324 324
 	 */
325
-	public function getDirectDownloadById(string $fileId): array|false;
325
+	public function getDirectDownloadById(string $fileId): array | false;
326 326
 
327 327
 	/**
328 328
 	 * @return void
Please login to merge, or discard this patch.
lib/private/Lockdown/Filesystem/NullStorage.php 2 patches
Indentation   +117 added lines, -117 removed lines patch added patch discarded remove patch
@@ -13,159 +13,159 @@
 block discarded – undo
13 13
 use OCP\Files\Storage\IStorage;
14 14
 
15 15
 class NullStorage extends Common {
16
-	public function __construct(array $parameters) {
17
-		parent::__construct($parameters);
18
-	}
16
+    public function __construct(array $parameters) {
17
+        parent::__construct($parameters);
18
+    }
19 19
 
20
-	public function getId(): string {
21
-		return 'null';
22
-	}
20
+    public function getId(): string {
21
+        return 'null';
22
+    }
23 23
 
24
-	public function mkdir(string $path): never {
25
-		throw new \OC\ForbiddenException('This request is not allowed to access the filesystem');
26
-	}
24
+    public function mkdir(string $path): never {
25
+        throw new \OC\ForbiddenException('This request is not allowed to access the filesystem');
26
+    }
27 27
 
28
-	public function rmdir(string $path): never {
29
-		throw new \OC\ForbiddenException('This request is not allowed to access the filesystem');
30
-	}
28
+    public function rmdir(string $path): never {
29
+        throw new \OC\ForbiddenException('This request is not allowed to access the filesystem');
30
+    }
31 31
 
32
-	public function opendir(string $path): IteratorDirectory {
33
-		return new IteratorDirectory();
34
-	}
32
+    public function opendir(string $path): IteratorDirectory {
33
+        return new IteratorDirectory();
34
+    }
35 35
 
36
-	public function is_dir(string $path): bool {
37
-		return $path === '';
38
-	}
36
+    public function is_dir(string $path): bool {
37
+        return $path === '';
38
+    }
39 39
 
40
-	public function is_file(string $path): bool {
41
-		return false;
42
-	}
40
+    public function is_file(string $path): bool {
41
+        return false;
42
+    }
43 43
 
44
-	public function stat(string $path): never {
45
-		throw new \OC\ForbiddenException('This request is not allowed to access the filesystem');
46
-	}
44
+    public function stat(string $path): never {
45
+        throw new \OC\ForbiddenException('This request is not allowed to access the filesystem');
46
+    }
47 47
 
48
-	public function filetype(string $path): string|false {
49
-		return ($path === '') ? 'dir' : false;
50
-	}
48
+    public function filetype(string $path): string|false {
49
+        return ($path === '') ? 'dir' : false;
50
+    }
51 51
 
52
-	public function filesize(string $path): never {
53
-		throw new \OC\ForbiddenException('This request is not allowed to access the filesystem');
54
-	}
52
+    public function filesize(string $path): never {
53
+        throw new \OC\ForbiddenException('This request is not allowed to access the filesystem');
54
+    }
55 55
 
56
-	public function isCreatable(string $path): bool {
57
-		return false;
58
-	}
56
+    public function isCreatable(string $path): bool {
57
+        return false;
58
+    }
59 59
 
60
-	public function isReadable(string $path): bool {
61
-		return $path === '';
62
-	}
60
+    public function isReadable(string $path): bool {
61
+        return $path === '';
62
+    }
63 63
 
64
-	public function isUpdatable(string $path): bool {
65
-		return false;
66
-	}
64
+    public function isUpdatable(string $path): bool {
65
+        return false;
66
+    }
67 67
 
68
-	public function isDeletable(string $path): bool {
69
-		return false;
70
-	}
68
+    public function isDeletable(string $path): bool {
69
+        return false;
70
+    }
71 71
 
72
-	public function isSharable(string $path): bool {
73
-		return false;
74
-	}
72
+    public function isSharable(string $path): bool {
73
+        return false;
74
+    }
75 75
 
76
-	public function getPermissions(string $path): int {
77
-		return 0;
78
-	}
76
+    public function getPermissions(string $path): int {
77
+        return 0;
78
+    }
79 79
 
80
-	public function file_exists(string $path): bool {
81
-		return $path === '';
82
-	}
80
+    public function file_exists(string $path): bool {
81
+        return $path === '';
82
+    }
83 83
 
84
-	public function filemtime(string $path): int|false {
85
-		return ($path === '') ? time() : false;
86
-	}
84
+    public function filemtime(string $path): int|false {
85
+        return ($path === '') ? time() : false;
86
+    }
87 87
 
88
-	public function file_get_contents(string $path): never {
89
-		throw new \OC\ForbiddenException('This request is not allowed to access the filesystem');
90
-	}
88
+    public function file_get_contents(string $path): never {
89
+        throw new \OC\ForbiddenException('This request is not allowed to access the filesystem');
90
+    }
91 91
 
92
-	public function file_put_contents(string $path, mixed $data): never {
93
-		throw new \OC\ForbiddenException('This request is not allowed to access the filesystem');
94
-	}
92
+    public function file_put_contents(string $path, mixed $data): never {
93
+        throw new \OC\ForbiddenException('This request is not allowed to access the filesystem');
94
+    }
95 95
 
96
-	public function unlink(string $path): never {
97
-		throw new \OC\ForbiddenException('This request is not allowed to access the filesystem');
98
-	}
96
+    public function unlink(string $path): never {
97
+        throw new \OC\ForbiddenException('This request is not allowed to access the filesystem');
98
+    }
99 99
 
100
-	public function rename(string $source, string $target): never {
101
-		throw new \OC\ForbiddenException('This request is not allowed to access the filesystem');
102
-	}
100
+    public function rename(string $source, string $target): never {
101
+        throw new \OC\ForbiddenException('This request is not allowed to access the filesystem');
102
+    }
103 103
 
104
-	public function copy(string $source, string $target): never {
105
-		throw new \OC\ForbiddenException('This request is not allowed to access the filesystem');
106
-	}
104
+    public function copy(string $source, string $target): never {
105
+        throw new \OC\ForbiddenException('This request is not allowed to access the filesystem');
106
+    }
107 107
 
108
-	public function fopen(string $path, string $mode): never {
109
-		throw new \OC\ForbiddenException('This request is not allowed to access the filesystem');
110
-	}
108
+    public function fopen(string $path, string $mode): never {
109
+        throw new \OC\ForbiddenException('This request is not allowed to access the filesystem');
110
+    }
111 111
 
112
-	public function getMimeType(string $path): never {
113
-		throw new \OC\ForbiddenException('This request is not allowed to access the filesystem');
114
-	}
112
+    public function getMimeType(string $path): never {
113
+        throw new \OC\ForbiddenException('This request is not allowed to access the filesystem');
114
+    }
115 115
 
116
-	public function hash(string $type, string $path, bool $raw = false): never {
117
-		throw new \OC\ForbiddenException('This request is not allowed to access the filesystem');
118
-	}
116
+    public function hash(string $type, string $path, bool $raw = false): never {
117
+        throw new \OC\ForbiddenException('This request is not allowed to access the filesystem');
118
+    }
119 119
 
120
-	public function free_space(string $path): int {
121
-		return FileInfo::SPACE_UNKNOWN;
122
-	}
120
+    public function free_space(string $path): int {
121
+        return FileInfo::SPACE_UNKNOWN;
122
+    }
123 123
 
124
-	public function touch(string $path, ?int $mtime = null): never {
125
-		throw new \OC\ForbiddenException('This request is not allowed to access the filesystem');
126
-	}
124
+    public function touch(string $path, ?int $mtime = null): never {
125
+        throw new \OC\ForbiddenException('This request is not allowed to access the filesystem');
126
+    }
127 127
 
128
-	public function getLocalFile(string $path): string|false {
129
-		return false;
130
-	}
128
+    public function getLocalFile(string $path): string|false {
129
+        return false;
130
+    }
131 131
 
132
-	public function hasUpdated(string $path, int $time): bool {
133
-		return false;
134
-	}
132
+    public function hasUpdated(string $path, int $time): bool {
133
+        return false;
134
+    }
135 135
 
136
-	public function getETag(string $path): string {
137
-		return '';
138
-	}
136
+    public function getETag(string $path): string {
137
+        return '';
138
+    }
139 139
 
140
-	public function isLocal(): bool {
141
-		return false;
142
-	}
140
+    public function isLocal(): bool {
141
+        return false;
142
+    }
143 143
 
144
-	public function getDirectDownload(string $path): array|false {
145
-		return false;
146
-	}
144
+    public function getDirectDownload(string $path): array|false {
145
+        return false;
146
+    }
147 147
 
148
-	public function getDirectDownloadById(string $fileId): array|false {
149
-		return false;
150
-	}
148
+    public function getDirectDownloadById(string $fileId): array|false {
149
+        return false;
150
+    }
151 151
 
152
-	public function copyFromStorage(IStorage $sourceStorage, string $sourceInternalPath, string $targetInternalPath, bool $preserveMtime = false): never {
153
-		throw new \OC\ForbiddenException('This request is not allowed to access the filesystem');
154
-	}
152
+    public function copyFromStorage(IStorage $sourceStorage, string $sourceInternalPath, string $targetInternalPath, bool $preserveMtime = false): never {
153
+        throw new \OC\ForbiddenException('This request is not allowed to access the filesystem');
154
+    }
155 155
 
156
-	public function moveFromStorage(IStorage $sourceStorage, string $sourceInternalPath, string $targetInternalPath): never {
157
-		throw new \OC\ForbiddenException('This request is not allowed to access the filesystem');
158
-	}
156
+    public function moveFromStorage(IStorage $sourceStorage, string $sourceInternalPath, string $targetInternalPath): never {
157
+        throw new \OC\ForbiddenException('This request is not allowed to access the filesystem');
158
+    }
159 159
 
160
-	public function test(): bool {
161
-		return true;
162
-	}
160
+    public function test(): bool {
161
+        return true;
162
+    }
163 163
 
164
-	public function getOwner(string $path): string|false {
165
-		return false;
166
-	}
164
+    public function getOwner(string $path): string|false {
165
+        return false;
166
+    }
167 167
 
168
-	public function getCache(string $path = '', ?IStorage $storage = null): ICache {
169
-		return new NullCache();
170
-	}
168
+    public function getCache(string $path = '', ?IStorage $storage = null): ICache {
169
+        return new NullCache();
170
+    }
171 171
 }
Please login to merge, or discard this patch.
Spacing   +6 added lines, -6 removed lines patch added patch discarded remove patch
@@ -45,7 +45,7 @@  discard block
 block discarded – undo
45 45
 		throw new \OC\ForbiddenException('This request is not allowed to access the filesystem');
46 46
 	}
47 47
 
48
-	public function filetype(string $path): string|false {
48
+	public function filetype(string $path): string | false {
49 49
 		return ($path === '') ? 'dir' : false;
50 50
 	}
51 51
 
@@ -81,7 +81,7 @@  discard block
 block discarded – undo
81 81
 		return $path === '';
82 82
 	}
83 83
 
84
-	public function filemtime(string $path): int|false {
84
+	public function filemtime(string $path): int | false {
85 85
 		return ($path === '') ? time() : false;
86 86
 	}
87 87
 
@@ -125,7 +125,7 @@  discard block
 block discarded – undo
125 125
 		throw new \OC\ForbiddenException('This request is not allowed to access the filesystem');
126 126
 	}
127 127
 
128
-	public function getLocalFile(string $path): string|false {
128
+	public function getLocalFile(string $path): string | false {
129 129
 		return false;
130 130
 	}
131 131
 
@@ -141,11 +141,11 @@  discard block
 block discarded – undo
141 141
 		return false;
142 142
 	}
143 143
 
144
-	public function getDirectDownload(string $path): array|false {
144
+	public function getDirectDownload(string $path): array | false {
145 145
 		return false;
146 146
 	}
147 147
 
148
-	public function getDirectDownloadById(string $fileId): array|false {
148
+	public function getDirectDownloadById(string $fileId): array | false {
149 149
 		return false;
150 150
 	}
151 151
 
@@ -161,7 +161,7 @@  discard block
 block discarded – undo
161 161
 		return true;
162 162
 	}
163 163
 
164
-	public function getOwner(string $path): string|false {
164
+	public function getOwner(string $path): string | false {
165 165
 		return false;
166 166
 	}
167 167
 
Please login to merge, or discard this patch.
lib/private/Files/ObjectStore/S3ObjectTrait.php 2 patches
Indentation   +296 added lines, -296 removed lines patch added patch discarded remove patch
@@ -19,300 +19,300 @@
 block discarded – undo
19 19
 use Psr\Http\Message\StreamInterface;
20 20
 
21 21
 trait S3ObjectTrait {
22
-	use S3ConfigTrait;
23
-
24
-	/**
25
-	 * Returns the connection
26
-	 *
27
-	 * @return S3Client connected client
28
-	 * @throws \Exception if connection could not be made
29
-	 */
30
-	abstract protected function getConnection();
31
-
32
-	abstract protected function getCertificateBundlePath(): ?string;
33
-	abstract protected function getSSECParameters(bool $copy = false): array;
34
-
35
-	/**
36
-	 * @param string $urn the unified resource name used to identify the object
37
-	 *
38
-	 * @return resource stream with the read data
39
-	 * @throws \Exception when something goes wrong, message will be logged
40
-	 * @since 7.0.0
41
-	 */
42
-	public function readObject($urn) {
43
-		$fh = SeekableHttpStream::open(function ($range) use ($urn) {
44
-			$command = $this->getConnection()->getCommand('GetObject', [
45
-				'Bucket' => $this->bucket,
46
-				'Key' => $urn,
47
-				'Range' => 'bytes=' . $range,
48
-			] + $this->getSSECParameters());
49
-			$request = \Aws\serialize($command);
50
-			$headers = [];
51
-			foreach ($request->getHeaders() as $key => $values) {
52
-				foreach ($values as $value) {
53
-					$headers[] = "$key: $value";
54
-				}
55
-			}
56
-			$opts = [
57
-				'http' => [
58
-					'protocol_version' => $request->getProtocolVersion(),
59
-					'header' => $headers,
60
-				]
61
-			];
62
-			$bundle = $this->getCertificateBundlePath();
63
-			if ($bundle) {
64
-				$opts['ssl'] = [
65
-					'cafile' => $bundle
66
-				];
67
-			}
68
-
69
-			if ($this->getProxy()) {
70
-				$opts['http']['proxy'] = $this->getProxy();
71
-				$opts['http']['request_fulluri'] = true;
72
-			}
73
-
74
-			$context = stream_context_create($opts);
75
-			return fopen($request->getUri(), 'r', false, $context);
76
-		});
77
-		if (!$fh) {
78
-			throw new \Exception("Failed to read object $urn");
79
-		}
80
-		return $fh;
81
-	}
82
-
83
-	private function buildS3Metadata(array $metadata): array {
84
-		$result = [];
85
-		foreach ($metadata as $key => $value) {
86
-			if (mb_check_encoding($value, 'ASCII')) {
87
-				$result['x-amz-meta-' . $key] = $value;
88
-			} else {
89
-				$result['x-amz-meta-' . $key] = 'base64:' . base64_encode($value);
90
-			}
91
-		}
92
-		return $result;
93
-	}
94
-
95
-	/**
96
-	 * Single object put helper
97
-	 *
98
-	 * @param string $urn the unified resource name used to identify the object
99
-	 * @param StreamInterface $stream stream with the data to write
100
-	 * @param array $metaData the metadata to set for the object
101
-	 * @throws \Exception when something goes wrong, message will be logged
102
-	 */
103
-	protected function writeSingle(string $urn, StreamInterface $stream, array $metaData): void {
104
-		$mimetype = $metaData['mimetype'] ?? null;
105
-		unset($metaData['mimetype']);
106
-		unset($metaData['size']);
107
-
108
-		$args = [
109
-			'Bucket' => $this->bucket,
110
-			'Key' => $urn,
111
-			'Body' => $stream,
112
-			'ACL' => 'private',
113
-			'ContentType' => $mimetype,
114
-			'Metadata' => $this->buildS3Metadata($metaData),
115
-			'StorageClass' => $this->storageClass,
116
-		] + $this->getSSECParameters();
117
-
118
-		if ($size = $stream->getSize()) {
119
-			$args['ContentLength'] = $size;
120
-		}
121
-
122
-		$this->getConnection()->putObject($args);
123
-	}
124
-
125
-
126
-	/**
127
-	 * Multipart upload helper that tries to avoid orphaned fragments in S3
128
-	 *
129
-	 * @param string $urn the unified resource name used to identify the object
130
-	 * @param StreamInterface $stream stream with the data to write
131
-	 * @param array $metaData the metadata to set for the object
132
-	 * @throws \Exception when something goes wrong, message will be logged
133
-	 */
134
-	protected function writeMultiPart(string $urn, StreamInterface $stream, array $metaData): void {
135
-		$mimetype = $metaData['mimetype'] ?? null;
136
-		unset($metaData['mimetype']);
137
-		unset($metaData['size']);
138
-
139
-		$attempts = 0;
140
-		$uploaded = false;
141
-		$concurrency = $this->concurrency;
142
-		$exception = null;
143
-		$state = null;
144
-		$size = $stream->getSize();
145
-		$totalWritten = 0;
146
-
147
-		// retry multipart upload once with concurrency at half on failure
148
-		while (!$uploaded && $attempts <= 1) {
149
-			$uploader = new MultipartUploader($this->getConnection(), $stream, [
150
-				'bucket' => $this->bucket,
151
-				'concurrency' => $concurrency,
152
-				'key' => $urn,
153
-				'part_size' => $this->uploadPartSize,
154
-				'state' => $state,
155
-				'params' => [
156
-					'ContentType' => $mimetype,
157
-					'Metadata' => $this->buildS3Metadata($metaData),
158
-					'StorageClass' => $this->storageClass,
159
-				] + $this->getSSECParameters(),
160
-				'before_upload' => function (Command $command) use (&$totalWritten) {
161
-					$totalWritten += $command['ContentLength'];
162
-				},
163
-				'before_complete' => function ($_command) use (&$totalWritten, $size, &$uploader, &$attempts) {
164
-					if ($size !== null && $totalWritten != $size) {
165
-						$e = new \Exception('Incomplete multi part upload, expected ' . $size . ' bytes, wrote ' . $totalWritten);
166
-						throw new MultipartUploadException($uploader->getState(), $e);
167
-					}
168
-				},
169
-			]);
170
-
171
-			try {
172
-				$uploader->upload();
173
-				$uploaded = true;
174
-			} catch (S3MultipartUploadException $e) {
175
-				$exception = $e;
176
-				$attempts++;
177
-
178
-				if ($concurrency > 1) {
179
-					$concurrency = round($concurrency / 2);
180
-				}
181
-
182
-				if ($stream->isSeekable()) {
183
-					$stream->rewind();
184
-				}
185
-			} catch (MultipartUploadException $e) {
186
-				$exception = $e;
187
-				break;
188
-			}
189
-		}
190
-
191
-		if (!$uploaded) {
192
-			// if anything goes wrong with multipart, make sure that you don´t poison and
193
-			// slow down s3 bucket with orphaned fragments
194
-			$uploadInfo = $exception->getState()->getId();
195
-			if ($exception->getState()->isInitiated() && (array_key_exists('UploadId', $uploadInfo))) {
196
-				$this->getConnection()->abortMultipartUpload($uploadInfo);
197
-			}
198
-
199
-			throw new \OCA\DAV\Connector\Sabre\Exception\BadGateway('Error while uploading to S3 bucket', 0, $exception);
200
-		}
201
-	}
202
-
203
-	public function writeObject($urn, $stream, ?string $mimetype = null) {
204
-		$metaData = [];
205
-		if ($mimetype) {
206
-			$metaData['mimetype'] = $mimetype;
207
-		}
208
-		$this->writeObjectWithMetaData($urn, $stream, $metaData);
209
-	}
210
-
211
-	public function writeObjectWithMetaData(string $urn, $stream, array $metaData): void {
212
-		$canSeek = fseek($stream, 0, SEEK_CUR) === 0;
213
-		$psrStream = Utils::streamFor($stream, [
214
-			'size' => $metaData['size'] ?? null,
215
-		]);
216
-
217
-
218
-		$size = $psrStream->getSize();
219
-		if ($size === null || !$canSeek) {
220
-			// The s3 single-part upload requires the size to be known for the stream.
221
-			// So for input streams that don't have a known size, we need to copy (part of)
222
-			// the input into a temporary stream so the size can be determined
223
-			$buffer = new Psr7\Stream(fopen('php://temp', 'rw+'));
224
-			Utils::copyToStream($psrStream, $buffer, $this->putSizeLimit);
225
-			$buffer->seek(0);
226
-			if ($buffer->getSize() < $this->putSizeLimit) {
227
-				// buffer is fully seekable, so use it directly for the small upload
228
-				$this->writeSingle($urn, $buffer, $metaData);
229
-			} else {
230
-				if ($psrStream->isSeekable()) {
231
-					// If the body is seekable, just rewind the body.
232
-					$psrStream->rewind();
233
-					$loadStream = $psrStream;
234
-				} else {
235
-					// If the body is non-seekable, stitch the rewind the buffer and
236
-					// the partially read body together into one stream. This avoids
237
-					// unnecessary disk usage and does not require seeking on the
238
-					// original stream.
239
-					$buffer->rewind();
240
-					$loadStream = new Psr7\AppendStream([$buffer, $psrStream]);
241
-				}
242
-
243
-				$this->writeMultiPart($urn, $loadStream, $metaData);
244
-			}
245
-		} else {
246
-			if ($size < $this->putSizeLimit) {
247
-				$this->writeSingle($urn, $psrStream, $metaData);
248
-			} else {
249
-				$this->writeMultiPart($urn, $psrStream, $metaData);
250
-			}
251
-		}
252
-		$psrStream->close();
253
-	}
254
-
255
-	/**
256
-	 * @param string $urn the unified resource name used to identify the object
257
-	 * @return void
258
-	 * @throws \Exception when something goes wrong, message will be logged
259
-	 * @since 7.0.0
260
-	 */
261
-	public function deleteObject($urn) {
262
-		$this->getConnection()->deleteObject([
263
-			'Bucket' => $this->bucket,
264
-			'Key' => $urn,
265
-		]);
266
-	}
267
-
268
-	public function objectExists($urn) {
269
-		return $this->getConnection()->doesObjectExist($this->bucket, $urn, $this->getSSECParameters());
270
-	}
271
-
272
-	public function copyObject($from, $to, array $options = []) {
273
-		$sourceMetadata = $this->getConnection()->headObject([
274
-			'Bucket' => $this->getBucket(),
275
-			'Key' => $from,
276
-		] + $this->getSSECParameters());
277
-
278
-		$size = (int)($sourceMetadata->get('Size') ?? $sourceMetadata->get('ContentLength'));
279
-
280
-		if ($this->useMultipartCopy && $size > $this->copySizeLimit) {
281
-			$copy = new MultipartCopy($this->getConnection(), [
282
-				'source_bucket' => $this->getBucket(),
283
-				'source_key' => $from
284
-			], array_merge([
285
-				'bucket' => $this->getBucket(),
286
-				'key' => $to,
287
-				'acl' => 'private',
288
-				'params' => $this->getSSECParameters() + $this->getSSECParameters(true),
289
-				'source_metadata' => $sourceMetadata
290
-			], $options));
291
-			$copy->copy();
292
-		} else {
293
-			$this->getConnection()->copy($this->getBucket(), $from, $this->getBucket(), $to, 'private', array_merge([
294
-				'params' => $this->getSSECParameters() + $this->getSSECParameters(true),
295
-				'mup_threshold' => PHP_INT_MAX,
296
-			], $options));
297
-		}
298
-	}
299
-
300
-	public function preSignedUrl(string $urn, \DateTimeInterface $expiration): ?string {
301
-		$command = $this->getConnection()->getCommand('GetObject', [
302
-			'Bucket' => $this->getBucket(),
303
-			'Key' => $urn,
304
-		]);
305
-
306
-		if (!$this->isUsePresignedUrl()) {
307
-			return null;
308
-		}
309
-
310
-		try {
311
-			return (string)$this->getConnection()->createPresignedRequest($command, $expiration, [
312
-				'signPayload' => true,
313
-			])->getUri();
314
-		} catch (AwsException) {
315
-			return null;
316
-		}
317
-	}
22
+    use S3ConfigTrait;
23
+
24
+    /**
25
+     * Returns the connection
26
+     *
27
+     * @return S3Client connected client
28
+     * @throws \Exception if connection could not be made
29
+     */
30
+    abstract protected function getConnection();
31
+
32
+    abstract protected function getCertificateBundlePath(): ?string;
33
+    abstract protected function getSSECParameters(bool $copy = false): array;
34
+
35
+    /**
36
+     * @param string $urn the unified resource name used to identify the object
37
+     *
38
+     * @return resource stream with the read data
39
+     * @throws \Exception when something goes wrong, message will be logged
40
+     * @since 7.0.0
41
+     */
42
+    public function readObject($urn) {
43
+        $fh = SeekableHttpStream::open(function ($range) use ($urn) {
44
+            $command = $this->getConnection()->getCommand('GetObject', [
45
+                'Bucket' => $this->bucket,
46
+                'Key' => $urn,
47
+                'Range' => 'bytes=' . $range,
48
+            ] + $this->getSSECParameters());
49
+            $request = \Aws\serialize($command);
50
+            $headers = [];
51
+            foreach ($request->getHeaders() as $key => $values) {
52
+                foreach ($values as $value) {
53
+                    $headers[] = "$key: $value";
54
+                }
55
+            }
56
+            $opts = [
57
+                'http' => [
58
+                    'protocol_version' => $request->getProtocolVersion(),
59
+                    'header' => $headers,
60
+                ]
61
+            ];
62
+            $bundle = $this->getCertificateBundlePath();
63
+            if ($bundle) {
64
+                $opts['ssl'] = [
65
+                    'cafile' => $bundle
66
+                ];
67
+            }
68
+
69
+            if ($this->getProxy()) {
70
+                $opts['http']['proxy'] = $this->getProxy();
71
+                $opts['http']['request_fulluri'] = true;
72
+            }
73
+
74
+            $context = stream_context_create($opts);
75
+            return fopen($request->getUri(), 'r', false, $context);
76
+        });
77
+        if (!$fh) {
78
+            throw new \Exception("Failed to read object $urn");
79
+        }
80
+        return $fh;
81
+    }
82
+
83
+    private function buildS3Metadata(array $metadata): array {
84
+        $result = [];
85
+        foreach ($metadata as $key => $value) {
86
+            if (mb_check_encoding($value, 'ASCII')) {
87
+                $result['x-amz-meta-' . $key] = $value;
88
+            } else {
89
+                $result['x-amz-meta-' . $key] = 'base64:' . base64_encode($value);
90
+            }
91
+        }
92
+        return $result;
93
+    }
94
+
95
+    /**
96
+     * Single object put helper
97
+     *
98
+     * @param string $urn the unified resource name used to identify the object
99
+     * @param StreamInterface $stream stream with the data to write
100
+     * @param array $metaData the metadata to set for the object
101
+     * @throws \Exception when something goes wrong, message will be logged
102
+     */
103
+    protected function writeSingle(string $urn, StreamInterface $stream, array $metaData): void {
104
+        $mimetype = $metaData['mimetype'] ?? null;
105
+        unset($metaData['mimetype']);
106
+        unset($metaData['size']);
107
+
108
+        $args = [
109
+            'Bucket' => $this->bucket,
110
+            'Key' => $urn,
111
+            'Body' => $stream,
112
+            'ACL' => 'private',
113
+            'ContentType' => $mimetype,
114
+            'Metadata' => $this->buildS3Metadata($metaData),
115
+            'StorageClass' => $this->storageClass,
116
+        ] + $this->getSSECParameters();
117
+
118
+        if ($size = $stream->getSize()) {
119
+            $args['ContentLength'] = $size;
120
+        }
121
+
122
+        $this->getConnection()->putObject($args);
123
+    }
124
+
125
+
126
+    /**
127
+     * Multipart upload helper that tries to avoid orphaned fragments in S3
128
+     *
129
+     * @param string $urn the unified resource name used to identify the object
130
+     * @param StreamInterface $stream stream with the data to write
131
+     * @param array $metaData the metadata to set for the object
132
+     * @throws \Exception when something goes wrong, message will be logged
133
+     */
134
+    protected function writeMultiPart(string $urn, StreamInterface $stream, array $metaData): void {
135
+        $mimetype = $metaData['mimetype'] ?? null;
136
+        unset($metaData['mimetype']);
137
+        unset($metaData['size']);
138
+
139
+        $attempts = 0;
140
+        $uploaded = false;
141
+        $concurrency = $this->concurrency;
142
+        $exception = null;
143
+        $state = null;
144
+        $size = $stream->getSize();
145
+        $totalWritten = 0;
146
+
147
+        // retry multipart upload once with concurrency at half on failure
148
+        while (!$uploaded && $attempts <= 1) {
149
+            $uploader = new MultipartUploader($this->getConnection(), $stream, [
150
+                'bucket' => $this->bucket,
151
+                'concurrency' => $concurrency,
152
+                'key' => $urn,
153
+                'part_size' => $this->uploadPartSize,
154
+                'state' => $state,
155
+                'params' => [
156
+                    'ContentType' => $mimetype,
157
+                    'Metadata' => $this->buildS3Metadata($metaData),
158
+                    'StorageClass' => $this->storageClass,
159
+                ] + $this->getSSECParameters(),
160
+                'before_upload' => function (Command $command) use (&$totalWritten) {
161
+                    $totalWritten += $command['ContentLength'];
162
+                },
163
+                'before_complete' => function ($_command) use (&$totalWritten, $size, &$uploader, &$attempts) {
164
+                    if ($size !== null && $totalWritten != $size) {
165
+                        $e = new \Exception('Incomplete multi part upload, expected ' . $size . ' bytes, wrote ' . $totalWritten);
166
+                        throw new MultipartUploadException($uploader->getState(), $e);
167
+                    }
168
+                },
169
+            ]);
170
+
171
+            try {
172
+                $uploader->upload();
173
+                $uploaded = true;
174
+            } catch (S3MultipartUploadException $e) {
175
+                $exception = $e;
176
+                $attempts++;
177
+
178
+                if ($concurrency > 1) {
179
+                    $concurrency = round($concurrency / 2);
180
+                }
181
+
182
+                if ($stream->isSeekable()) {
183
+                    $stream->rewind();
184
+                }
185
+            } catch (MultipartUploadException $e) {
186
+                $exception = $e;
187
+                break;
188
+            }
189
+        }
190
+
191
+        if (!$uploaded) {
192
+            // if anything goes wrong with multipart, make sure that you don´t poison and
193
+            // slow down s3 bucket with orphaned fragments
194
+            $uploadInfo = $exception->getState()->getId();
195
+            if ($exception->getState()->isInitiated() && (array_key_exists('UploadId', $uploadInfo))) {
196
+                $this->getConnection()->abortMultipartUpload($uploadInfo);
197
+            }
198
+
199
+            throw new \OCA\DAV\Connector\Sabre\Exception\BadGateway('Error while uploading to S3 bucket', 0, $exception);
200
+        }
201
+    }
202
+
203
+    public function writeObject($urn, $stream, ?string $mimetype = null) {
204
+        $metaData = [];
205
+        if ($mimetype) {
206
+            $metaData['mimetype'] = $mimetype;
207
+        }
208
+        $this->writeObjectWithMetaData($urn, $stream, $metaData);
209
+    }
210
+
211
+    public function writeObjectWithMetaData(string $urn, $stream, array $metaData): void {
212
+        $canSeek = fseek($stream, 0, SEEK_CUR) === 0;
213
+        $psrStream = Utils::streamFor($stream, [
214
+            'size' => $metaData['size'] ?? null,
215
+        ]);
216
+
217
+
218
+        $size = $psrStream->getSize();
219
+        if ($size === null || !$canSeek) {
220
+            // The s3 single-part upload requires the size to be known for the stream.
221
+            // So for input streams that don't have a known size, we need to copy (part of)
222
+            // the input into a temporary stream so the size can be determined
223
+            $buffer = new Psr7\Stream(fopen('php://temp', 'rw+'));
224
+            Utils::copyToStream($psrStream, $buffer, $this->putSizeLimit);
225
+            $buffer->seek(0);
226
+            if ($buffer->getSize() < $this->putSizeLimit) {
227
+                // buffer is fully seekable, so use it directly for the small upload
228
+                $this->writeSingle($urn, $buffer, $metaData);
229
+            } else {
230
+                if ($psrStream->isSeekable()) {
231
+                    // If the body is seekable, just rewind the body.
232
+                    $psrStream->rewind();
233
+                    $loadStream = $psrStream;
234
+                } else {
235
+                    // If the body is non-seekable, stitch the rewind the buffer and
236
+                    // the partially read body together into one stream. This avoids
237
+                    // unnecessary disk usage and does not require seeking on the
238
+                    // original stream.
239
+                    $buffer->rewind();
240
+                    $loadStream = new Psr7\AppendStream([$buffer, $psrStream]);
241
+                }
242
+
243
+                $this->writeMultiPart($urn, $loadStream, $metaData);
244
+            }
245
+        } else {
246
+            if ($size < $this->putSizeLimit) {
247
+                $this->writeSingle($urn, $psrStream, $metaData);
248
+            } else {
249
+                $this->writeMultiPart($urn, $psrStream, $metaData);
250
+            }
251
+        }
252
+        $psrStream->close();
253
+    }
254
+
255
+    /**
256
+     * @param string $urn the unified resource name used to identify the object
257
+     * @return void
258
+     * @throws \Exception when something goes wrong, message will be logged
259
+     * @since 7.0.0
260
+     */
261
+    public function deleteObject($urn) {
262
+        $this->getConnection()->deleteObject([
263
+            'Bucket' => $this->bucket,
264
+            'Key' => $urn,
265
+        ]);
266
+    }
267
+
268
+    public function objectExists($urn) {
269
+        return $this->getConnection()->doesObjectExist($this->bucket, $urn, $this->getSSECParameters());
270
+    }
271
+
272
+    public function copyObject($from, $to, array $options = []) {
273
+        $sourceMetadata = $this->getConnection()->headObject([
274
+            'Bucket' => $this->getBucket(),
275
+            'Key' => $from,
276
+        ] + $this->getSSECParameters());
277
+
278
+        $size = (int)($sourceMetadata->get('Size') ?? $sourceMetadata->get('ContentLength'));
279
+
280
+        if ($this->useMultipartCopy && $size > $this->copySizeLimit) {
281
+            $copy = new MultipartCopy($this->getConnection(), [
282
+                'source_bucket' => $this->getBucket(),
283
+                'source_key' => $from
284
+            ], array_merge([
285
+                'bucket' => $this->getBucket(),
286
+                'key' => $to,
287
+                'acl' => 'private',
288
+                'params' => $this->getSSECParameters() + $this->getSSECParameters(true),
289
+                'source_metadata' => $sourceMetadata
290
+            ], $options));
291
+            $copy->copy();
292
+        } else {
293
+            $this->getConnection()->copy($this->getBucket(), $from, $this->getBucket(), $to, 'private', array_merge([
294
+                'params' => $this->getSSECParameters() + $this->getSSECParameters(true),
295
+                'mup_threshold' => PHP_INT_MAX,
296
+            ], $options));
297
+        }
298
+    }
299
+
300
+    public function preSignedUrl(string $urn, \DateTimeInterface $expiration): ?string {
301
+        $command = $this->getConnection()->getCommand('GetObject', [
302
+            'Bucket' => $this->getBucket(),
303
+            'Key' => $urn,
304
+        ]);
305
+
306
+        if (!$this->isUsePresignedUrl()) {
307
+            return null;
308
+        }
309
+
310
+        try {
311
+            return (string)$this->getConnection()->createPresignedRequest($command, $expiration, [
312
+                'signPayload' => true,
313
+            ])->getUri();
314
+        } catch (AwsException) {
315
+            return null;
316
+        }
317
+    }
318 318
 }
Please login to merge, or discard this patch.
Spacing   +9 added lines, -9 removed lines patch added patch discarded remove patch
@@ -40,11 +40,11 @@  discard block
 block discarded – undo
40 40
 	 * @since 7.0.0
41 41
 	 */
42 42
 	public function readObject($urn) {
43
-		$fh = SeekableHttpStream::open(function ($range) use ($urn) {
43
+		$fh = SeekableHttpStream::open(function($range) use ($urn) {
44 44
 			$command = $this->getConnection()->getCommand('GetObject', [
45 45
 				'Bucket' => $this->bucket,
46 46
 				'Key' => $urn,
47
-				'Range' => 'bytes=' . $range,
47
+				'Range' => 'bytes='.$range,
48 48
 			] + $this->getSSECParameters());
49 49
 			$request = \Aws\serialize($command);
50 50
 			$headers = [];
@@ -84,9 +84,9 @@  discard block
 block discarded – undo
84 84
 		$result = [];
85 85
 		foreach ($metadata as $key => $value) {
86 86
 			if (mb_check_encoding($value, 'ASCII')) {
87
-				$result['x-amz-meta-' . $key] = $value;
87
+				$result['x-amz-meta-'.$key] = $value;
88 88
 			} else {
89
-				$result['x-amz-meta-' . $key] = 'base64:' . base64_encode($value);
89
+				$result['x-amz-meta-'.$key] = 'base64:'.base64_encode($value);
90 90
 			}
91 91
 		}
92 92
 		return $result;
@@ -157,12 +157,12 @@  discard block
 block discarded – undo
157 157
 					'Metadata' => $this->buildS3Metadata($metaData),
158 158
 					'StorageClass' => $this->storageClass,
159 159
 				] + $this->getSSECParameters(),
160
-				'before_upload' => function (Command $command) use (&$totalWritten) {
160
+				'before_upload' => function(Command $command) use (&$totalWritten) {
161 161
 					$totalWritten += $command['ContentLength'];
162 162
 				},
163
-				'before_complete' => function ($_command) use (&$totalWritten, $size, &$uploader, &$attempts) {
163
+				'before_complete' => function($_command) use (&$totalWritten, $size, &$uploader, &$attempts) {
164 164
 					if ($size !== null && $totalWritten != $size) {
165
-						$e = new \Exception('Incomplete multi part upload, expected ' . $size . ' bytes, wrote ' . $totalWritten);
165
+						$e = new \Exception('Incomplete multi part upload, expected '.$size.' bytes, wrote '.$totalWritten);
166 166
 						throw new MultipartUploadException($uploader->getState(), $e);
167 167
 					}
168 168
 				},
@@ -275,7 +275,7 @@  discard block
 block discarded – undo
275 275
 			'Key' => $from,
276 276
 		] + $this->getSSECParameters());
277 277
 
278
-		$size = (int)($sourceMetadata->get('Size') ?? $sourceMetadata->get('ContentLength'));
278
+		$size = (int) ($sourceMetadata->get('Size') ?? $sourceMetadata->get('ContentLength'));
279 279
 
280 280
 		if ($this->useMultipartCopy && $size > $this->copySizeLimit) {
281 281
 			$copy = new MultipartCopy($this->getConnection(), [
@@ -308,7 +308,7 @@  discard block
 block discarded – undo
308 308
 		}
309 309
 
310 310
 		try {
311
-			return (string)$this->getConnection()->createPresignedRequest($command, $expiration, [
311
+			return (string) $this->getConnection()->createPresignedRequest($command, $expiration, [
312 312
 				'signPayload' => true,
313 313
 			])->getUri();
314 314
 		} catch (AwsException) {
Please login to merge, or discard this patch.
lib/private/Files/ObjectStore/ObjectStoreStorage.php 2 patches
Indentation   +827 added lines, -827 removed lines patch added patch discarded remove patch
@@ -33,834 +33,834 @@
 block discarded – undo
33 33
 use Psr\Log\LoggerInterface;
34 34
 
35 35
 class ObjectStoreStorage extends \OC\Files\Storage\Common implements IChunkedFileWrite {
36
-	use CopyDirectory;
37
-
38
-	protected IObjectStore $objectStore;
39
-	protected string $id;
40
-	private string $objectPrefix = 'urn:oid:';
41
-
42
-	private LoggerInterface $logger;
43
-
44
-	private bool $handleCopiesAsOwned;
45
-	protected bool $validateWrites = true;
46
-	private bool $preserveCacheItemsOnDelete = false;
47
-	private ?int $totalSizeLimit = null;
48
-
49
-	/**
50
-	 * @param array $parameters
51
-	 * @throws \Exception
52
-	 */
53
-	public function __construct(array $parameters) {
54
-		if (isset($parameters['objectstore']) && $parameters['objectstore'] instanceof IObjectStore) {
55
-			$this->objectStore = $parameters['objectstore'];
56
-		} else {
57
-			throw new \Exception('missing IObjectStore instance');
58
-		}
59
-		if (isset($parameters['storageid'])) {
60
-			$this->id = 'object::store:' . $parameters['storageid'];
61
-		} else {
62
-			$this->id = 'object::store:' . $this->objectStore->getStorageId();
63
-		}
64
-		if (isset($parameters['objectPrefix'])) {
65
-			$this->objectPrefix = $parameters['objectPrefix'];
66
-		}
67
-		if (isset($parameters['validateWrites'])) {
68
-			$this->validateWrites = (bool)$parameters['validateWrites'];
69
-		}
70
-		$this->handleCopiesAsOwned = (bool)($parameters['handleCopiesAsOwned'] ?? false);
71
-		if (isset($parameters['totalSizeLimit'])) {
72
-			$this->totalSizeLimit = $parameters['totalSizeLimit'];
73
-		}
74
-
75
-		$this->logger = \OCP\Server::get(LoggerInterface::class);
76
-	}
77
-
78
-	public function mkdir(string $path, bool $force = false, array $metadata = []): bool {
79
-		$path = $this->normalizePath($path);
80
-		if (!$force && $this->file_exists($path)) {
81
-			$this->logger->warning("Tried to create an object store folder that already exists: $path");
82
-			return false;
83
-		}
84
-
85
-		$mTime = time();
86
-		$data = [
87
-			'mimetype' => 'httpd/unix-directory',
88
-			'size' => $metadata['size'] ?? 0,
89
-			'mtime' => $mTime,
90
-			'storage_mtime' => $mTime,
91
-			'permissions' => \OCP\Constants::PERMISSION_ALL,
92
-		];
93
-		if ($path === '') {
94
-			//create root on the fly
95
-			$data['etag'] = $this->getETag('');
96
-			$this->getCache()->put('', $data);
97
-			return true;
98
-		} else {
99
-			// if parent does not exist, create it
100
-			$parent = $this->normalizePath(dirname($path));
101
-			$parentType = $this->filetype($parent);
102
-			if ($parentType === false) {
103
-				if (!$this->mkdir($parent)) {
104
-					// something went wrong
105
-					$this->logger->warning("Parent folder ($parent) doesn't exist and couldn't be created");
106
-					return false;
107
-				}
108
-			} elseif ($parentType === 'file') {
109
-				// parent is a file
110
-				$this->logger->warning("Parent ($parent) is a file");
111
-				return false;
112
-			}
113
-			// finally create the new dir
114
-			$mTime = time(); // update mtime
115
-			$data['mtime'] = $mTime;
116
-			$data['storage_mtime'] = $mTime;
117
-			$data['etag'] = $this->getETag($path);
118
-			$this->getCache()->put($path, $data);
119
-			return true;
120
-		}
121
-	}
122
-
123
-	private function normalizePath(string $path): string {
124
-		$path = trim($path, '/');
125
-		//FIXME why do we sometimes get a path like 'files//username'?
126
-		$path = str_replace('//', '/', $path);
127
-
128
-		// dirname('/folder') returns '.' but internally (in the cache) we store the root as ''
129
-		if (!$path || $path === '.') {
130
-			$path = '';
131
-		}
132
-
133
-		return $path;
134
-	}
135
-
136
-	/**
137
-	 * Object Stores use a NoopScanner because metadata is directly stored in
138
-	 * the file cache and cannot really scan the filesystem. The storage passed in is not used anywhere.
139
-	 */
140
-	public function getScanner(string $path = '', ?IStorage $storage = null): IScanner {
141
-		if (!$storage) {
142
-			$storage = $this;
143
-		}
144
-		if (!isset($this->scanner)) {
145
-			$this->scanner = new ObjectStoreScanner($storage);
146
-		}
147
-		/** @var \OC\Files\ObjectStore\ObjectStoreScanner */
148
-		return $this->scanner;
149
-	}
150
-
151
-	public function getId(): string {
152
-		return $this->id;
153
-	}
154
-
155
-	public function rmdir(string $path): bool {
156
-		$path = $this->normalizePath($path);
157
-		$entry = $this->getCache()->get($path);
158
-
159
-		if (!$entry || $entry->getMimeType() !== ICacheEntry::DIRECTORY_MIMETYPE) {
160
-			return false;
161
-		}
162
-
163
-		return $this->rmObjects($entry);
164
-	}
165
-
166
-	private function rmObjects(ICacheEntry $entry): bool {
167
-		$children = $this->getCache()->getFolderContentsById($entry->getId());
168
-		foreach ($children as $child) {
169
-			if ($child->getMimeType() === ICacheEntry::DIRECTORY_MIMETYPE) {
170
-				if (!$this->rmObjects($child)) {
171
-					return false;
172
-				}
173
-			} else {
174
-				if (!$this->rmObject($child)) {
175
-					return false;
176
-				}
177
-			}
178
-		}
179
-
180
-		if (!$this->preserveCacheItemsOnDelete) {
181
-			$this->getCache()->remove($entry->getPath());
182
-		}
183
-
184
-		return true;
185
-	}
186
-
187
-	public function unlink(string $path): bool {
188
-		$path = $this->normalizePath($path);
189
-		$entry = $this->getCache()->get($path);
190
-
191
-		if ($entry instanceof ICacheEntry) {
192
-			if ($entry->getMimeType() === ICacheEntry::DIRECTORY_MIMETYPE) {
193
-				return $this->rmObjects($entry);
194
-			} else {
195
-				return $this->rmObject($entry);
196
-			}
197
-		}
198
-		return false;
199
-	}
200
-
201
-	public function rmObject(ICacheEntry $entry): bool {
202
-		try {
203
-			$this->objectStore->deleteObject($this->getURN($entry->getId()));
204
-		} catch (\Exception $ex) {
205
-			if ($ex->getCode() !== 404) {
206
-				$this->logger->error(
207
-					'Could not delete object ' . $this->getURN($entry->getId()) . ' for ' . $entry->getPath(),
208
-					[
209
-						'app' => 'objectstore',
210
-						'exception' => $ex,
211
-					]
212
-				);
213
-				return false;
214
-			}
215
-			//removing from cache is ok as it does not exist in the objectstore anyway
216
-		}
217
-		if (!$this->preserveCacheItemsOnDelete) {
218
-			$this->getCache()->remove($entry->getPath());
219
-		}
220
-		return true;
221
-	}
222
-
223
-	public function stat(string $path): array|false {
224
-		$path = $this->normalizePath($path);
225
-		$cacheEntry = $this->getCache()->get($path);
226
-		if ($cacheEntry instanceof CacheEntry) {
227
-			return $cacheEntry->getData();
228
-		} else {
229
-			if ($path === '') {
230
-				$this->mkdir('', true);
231
-				$cacheEntry = $this->getCache()->get($path);
232
-				if ($cacheEntry instanceof CacheEntry) {
233
-					return $cacheEntry->getData();
234
-				}
235
-			}
236
-			return false;
237
-		}
238
-	}
239
-
240
-	public function getPermissions(string $path): int {
241
-		$stat = $this->stat($path);
242
-
243
-		if (is_array($stat) && isset($stat['permissions'])) {
244
-			return $stat['permissions'];
245
-		}
246
-
247
-		return parent::getPermissions($path);
248
-	}
249
-
250
-	/**
251
-	 * Override this method if you need a different unique resource identifier for your object storage implementation.
252
-	 * The default implementations just appends the fileId to 'urn:oid:'. Make sure the URN is unique over all users.
253
-	 * You may need a mapping table to store your URN if it cannot be generated from the fileid.
254
-	 *
255
-	 * @return string the unified resource name used to identify the object
256
-	 */
257
-	public function getURN(int $fileId): string {
258
-		return $this->objectPrefix . $fileId;
259
-	}
260
-
261
-	public function opendir(string $path) {
262
-		$path = $this->normalizePath($path);
263
-
264
-		try {
265
-			$files = [];
266
-			$folderContents = $this->getCache()->getFolderContents($path);
267
-			foreach ($folderContents as $file) {
268
-				$files[] = $file['name'];
269
-			}
270
-
271
-			return IteratorDirectory::wrap($files);
272
-		} catch (\Exception $e) {
273
-			$this->logger->error($e->getMessage(), ['exception' => $e]);
274
-			return false;
275
-		}
276
-	}
277
-
278
-	public function filetype(string $path): string|false {
279
-		$path = $this->normalizePath($path);
280
-		$stat = $this->stat($path);
281
-		if ($stat) {
282
-			if ($stat['mimetype'] === 'httpd/unix-directory') {
283
-				return 'dir';
284
-			}
285
-			return 'file';
286
-		} else {
287
-			return false;
288
-		}
289
-	}
290
-
291
-	public function fopen(string $path, string $mode) {
292
-		$path = $this->normalizePath($path);
293
-
294
-		if (strrpos($path, '.') !== false) {
295
-			$ext = substr($path, strrpos($path, '.'));
296
-		} else {
297
-			$ext = '';
298
-		}
299
-
300
-		switch ($mode) {
301
-			case 'r':
302
-			case 'rb':
303
-				$stat = $this->stat($path);
304
-				if (is_array($stat)) {
305
-					$filesize = $stat['size'] ?? 0;
306
-					// Reading 0 sized files is a waste of time
307
-					if ($filesize === 0) {
308
-						return fopen('php://memory', $mode);
309
-					}
310
-
311
-					try {
312
-						$handle = $this->objectStore->readObject($this->getURN($stat['fileid']));
313
-						if ($handle === false) {
314
-							return false; // keep backward compatibility
315
-						}
316
-						$streamStat = fstat($handle);
317
-						$actualSize = $streamStat['size'] ?? -1;
318
-						if ($actualSize > -1 && $actualSize !== $filesize) {
319
-							$this->getCache()->update((int)$stat['fileid'], ['size' => $actualSize]);
320
-						}
321
-						return $handle;
322
-					} catch (NotFoundException $e) {
323
-						$this->logger->error(
324
-							'Could not get object ' . $this->getURN($stat['fileid']) . ' for file ' . $path,
325
-							[
326
-								'app' => 'objectstore',
327
-								'exception' => $e,
328
-							]
329
-						);
330
-						throw $e;
331
-					} catch (\Exception $e) {
332
-						$this->logger->error(
333
-							'Could not get object ' . $this->getURN($stat['fileid']) . ' for file ' . $path,
334
-							[
335
-								'app' => 'objectstore',
336
-								'exception' => $e,
337
-							]
338
-						);
339
-						return false;
340
-					}
341
-				} else {
342
-					return false;
343
-				}
344
-				// no break
345
-			case 'w':
346
-			case 'wb':
347
-			case 'w+':
348
-			case 'wb+':
349
-				$dirName = dirname($path);
350
-				$parentExists = $this->is_dir($dirName);
351
-				if (!$parentExists) {
352
-					return false;
353
-				}
354
-
355
-				$tmpFile = \OC::$server->getTempManager()->getTemporaryFile($ext);
356
-				$handle = fopen($tmpFile, $mode);
357
-				return CallbackWrapper::wrap($handle, null, null, function () use ($path, $tmpFile) {
358
-					$this->writeBack($tmpFile, $path);
359
-					unlink($tmpFile);
360
-				});
361
-			case 'a':
362
-			case 'ab':
363
-			case 'r+':
364
-			case 'a+':
365
-			case 'x':
366
-			case 'x+':
367
-			case 'c':
368
-			case 'c+':
369
-				$tmpFile = \OC::$server->getTempManager()->getTemporaryFile($ext);
370
-				if ($this->file_exists($path)) {
371
-					$source = $this->fopen($path, 'r');
372
-					file_put_contents($tmpFile, $source);
373
-				}
374
-				$handle = fopen($tmpFile, $mode);
375
-				return CallbackWrapper::wrap($handle, null, null, function () use ($path, $tmpFile) {
376
-					$this->writeBack($tmpFile, $path);
377
-					unlink($tmpFile);
378
-				});
379
-		}
380
-		return false;
381
-	}
382
-
383
-	public function file_exists(string $path): bool {
384
-		$path = $this->normalizePath($path);
385
-		return (bool)$this->stat($path);
386
-	}
387
-
388
-	public function rename(string $source, string $target): bool {
389
-		$source = $this->normalizePath($source);
390
-		$target = $this->normalizePath($target);
391
-		$this->remove($target);
392
-		$this->getCache()->move($source, $target);
393
-		$this->touch(dirname($target));
394
-		return true;
395
-	}
396
-
397
-	public function getMimeType(string $path): string|false {
398
-		$path = $this->normalizePath($path);
399
-		return parent::getMimeType($path);
400
-	}
401
-
402
-	public function touch(string $path, ?int $mtime = null): bool {
403
-		if (is_null($mtime)) {
404
-			$mtime = time();
405
-		}
406
-
407
-		$path = $this->normalizePath($path);
408
-		$dirName = dirname($path);
409
-		$parentExists = $this->is_dir($dirName);
410
-		if (!$parentExists) {
411
-			return false;
412
-		}
413
-
414
-		$stat = $this->stat($path);
415
-		if (is_array($stat)) {
416
-			// update existing mtime in db
417
-			$stat['mtime'] = $mtime;
418
-			$this->getCache()->update($stat['fileid'], $stat);
419
-		} else {
420
-			try {
421
-				//create a empty file, need to have at least on char to make it
422
-				// work with all object storage implementations
423
-				$this->file_put_contents($path, ' ');
424
-			} catch (\Exception $ex) {
425
-				$this->logger->error(
426
-					'Could not create object for ' . $path,
427
-					[
428
-						'app' => 'objectstore',
429
-						'exception' => $ex,
430
-					]
431
-				);
432
-				throw $ex;
433
-			}
434
-		}
435
-		return true;
436
-	}
437
-
438
-	public function writeBack(string $tmpFile, string $path) {
439
-		$size = filesize($tmpFile);
440
-		$this->writeStream($path, fopen($tmpFile, 'r'), $size);
441
-	}
442
-
443
-	public function hasUpdated(string $path, int $time): bool {
444
-		return false;
445
-	}
446
-
447
-	public function needsPartFile(): bool {
448
-		return false;
449
-	}
450
-
451
-	public function file_put_contents(string $path, mixed $data): int {
452
-		$fh = fopen('php://temp', 'w+');
453
-		fwrite($fh, $data);
454
-		rewind($fh);
455
-		return $this->writeStream($path, $fh, strlen($data));
456
-	}
457
-
458
-	public function writeStream(string $path, $stream, ?int $size = null): int {
459
-		if ($size === null) {
460
-			$stats = fstat($stream);
461
-			if (is_array($stats) && isset($stats['size'])) {
462
-				$size = $stats['size'];
463
-			}
464
-		}
465
-
466
-		$stat = $this->stat($path);
467
-		if (empty($stat)) {
468
-			// create new file
469
-			$stat = [
470
-				'permissions' => \OCP\Constants::PERMISSION_ALL - \OCP\Constants::PERMISSION_CREATE,
471
-			];
472
-		}
473
-		// update stat with new data
474
-		$mTime = time();
475
-		$stat['size'] = (int)$size;
476
-		$stat['mtime'] = $mTime;
477
-		$stat['storage_mtime'] = $mTime;
478
-
479
-		$mimetypeDetector = \OC::$server->getMimeTypeDetector();
480
-		$mimetype = $mimetypeDetector->detectPath($path);
481
-		$metadata = [
482
-			'mimetype' => $mimetype,
483
-			'original-storage' => $this->getId(),
484
-			'original-path' => $path,
485
-		];
486
-		if ($size) {
487
-			$metadata['size'] = $size;
488
-		}
489
-
490
-		$stat['mimetype'] = $mimetype;
491
-		$stat['etag'] = $this->getETag($path);
492
-		$stat['checksum'] = '';
493
-
494
-		$exists = $this->getCache()->inCache($path);
495
-		$uploadPath = $exists ? $path : $path . '.part';
496
-
497
-		if ($exists) {
498
-			$fileId = $stat['fileid'];
499
-		} else {
500
-			$parent = $this->normalizePath(dirname($path));
501
-			if (!$this->is_dir($parent)) {
502
-				throw new \InvalidArgumentException("trying to upload a file ($path) inside a non-directory ($parent)");
503
-			}
504
-			$fileId = $this->getCache()->put($uploadPath, $stat);
505
-		}
506
-
507
-		$urn = $this->getURN($fileId);
508
-		try {
509
-			//upload to object storage
510
-
511
-			$totalWritten = 0;
512
-			$countStream = CountWrapper::wrap($stream, function ($writtenSize) use ($fileId, $size, $exists, &$totalWritten) {
513
-				if (is_null($size) && !$exists) {
514
-					$this->getCache()->update($fileId, [
515
-						'size' => $writtenSize,
516
-					]);
517
-				}
518
-				$totalWritten = $writtenSize;
519
-			});
520
-
521
-			if ($this->objectStore instanceof IObjectStoreMetaData) {
522
-				$this->objectStore->writeObjectWithMetaData($urn, $countStream, $metadata);
523
-			} else {
524
-				$this->objectStore->writeObject($urn, $countStream, $metadata['mimetype']);
525
-			}
526
-			if (is_resource($countStream)) {
527
-				fclose($countStream);
528
-			}
529
-
530
-			$stat['size'] = $totalWritten;
531
-		} catch (\Exception $ex) {
532
-			if (!$exists) {
533
-				/*
36
+    use CopyDirectory;
37
+
38
+    protected IObjectStore $objectStore;
39
+    protected string $id;
40
+    private string $objectPrefix = 'urn:oid:';
41
+
42
+    private LoggerInterface $logger;
43
+
44
+    private bool $handleCopiesAsOwned;
45
+    protected bool $validateWrites = true;
46
+    private bool $preserveCacheItemsOnDelete = false;
47
+    private ?int $totalSizeLimit = null;
48
+
49
+    /**
50
+     * @param array $parameters
51
+     * @throws \Exception
52
+     */
53
+    public function __construct(array $parameters) {
54
+        if (isset($parameters['objectstore']) && $parameters['objectstore'] instanceof IObjectStore) {
55
+            $this->objectStore = $parameters['objectstore'];
56
+        } else {
57
+            throw new \Exception('missing IObjectStore instance');
58
+        }
59
+        if (isset($parameters['storageid'])) {
60
+            $this->id = 'object::store:' . $parameters['storageid'];
61
+        } else {
62
+            $this->id = 'object::store:' . $this->objectStore->getStorageId();
63
+        }
64
+        if (isset($parameters['objectPrefix'])) {
65
+            $this->objectPrefix = $parameters['objectPrefix'];
66
+        }
67
+        if (isset($parameters['validateWrites'])) {
68
+            $this->validateWrites = (bool)$parameters['validateWrites'];
69
+        }
70
+        $this->handleCopiesAsOwned = (bool)($parameters['handleCopiesAsOwned'] ?? false);
71
+        if (isset($parameters['totalSizeLimit'])) {
72
+            $this->totalSizeLimit = $parameters['totalSizeLimit'];
73
+        }
74
+
75
+        $this->logger = \OCP\Server::get(LoggerInterface::class);
76
+    }
77
+
78
+    public function mkdir(string $path, bool $force = false, array $metadata = []): bool {
79
+        $path = $this->normalizePath($path);
80
+        if (!$force && $this->file_exists($path)) {
81
+            $this->logger->warning("Tried to create an object store folder that already exists: $path");
82
+            return false;
83
+        }
84
+
85
+        $mTime = time();
86
+        $data = [
87
+            'mimetype' => 'httpd/unix-directory',
88
+            'size' => $metadata['size'] ?? 0,
89
+            'mtime' => $mTime,
90
+            'storage_mtime' => $mTime,
91
+            'permissions' => \OCP\Constants::PERMISSION_ALL,
92
+        ];
93
+        if ($path === '') {
94
+            //create root on the fly
95
+            $data['etag'] = $this->getETag('');
96
+            $this->getCache()->put('', $data);
97
+            return true;
98
+        } else {
99
+            // if parent does not exist, create it
100
+            $parent = $this->normalizePath(dirname($path));
101
+            $parentType = $this->filetype($parent);
102
+            if ($parentType === false) {
103
+                if (!$this->mkdir($parent)) {
104
+                    // something went wrong
105
+                    $this->logger->warning("Parent folder ($parent) doesn't exist and couldn't be created");
106
+                    return false;
107
+                }
108
+            } elseif ($parentType === 'file') {
109
+                // parent is a file
110
+                $this->logger->warning("Parent ($parent) is a file");
111
+                return false;
112
+            }
113
+            // finally create the new dir
114
+            $mTime = time(); // update mtime
115
+            $data['mtime'] = $mTime;
116
+            $data['storage_mtime'] = $mTime;
117
+            $data['etag'] = $this->getETag($path);
118
+            $this->getCache()->put($path, $data);
119
+            return true;
120
+        }
121
+    }
122
+
123
+    private function normalizePath(string $path): string {
124
+        $path = trim($path, '/');
125
+        //FIXME why do we sometimes get a path like 'files//username'?
126
+        $path = str_replace('//', '/', $path);
127
+
128
+        // dirname('/folder') returns '.' but internally (in the cache) we store the root as ''
129
+        if (!$path || $path === '.') {
130
+            $path = '';
131
+        }
132
+
133
+        return $path;
134
+    }
135
+
136
+    /**
137
+     * Object Stores use a NoopScanner because metadata is directly stored in
138
+     * the file cache and cannot really scan the filesystem. The storage passed in is not used anywhere.
139
+     */
140
+    public function getScanner(string $path = '', ?IStorage $storage = null): IScanner {
141
+        if (!$storage) {
142
+            $storage = $this;
143
+        }
144
+        if (!isset($this->scanner)) {
145
+            $this->scanner = new ObjectStoreScanner($storage);
146
+        }
147
+        /** @var \OC\Files\ObjectStore\ObjectStoreScanner */
148
+        return $this->scanner;
149
+    }
150
+
151
+    public function getId(): string {
152
+        return $this->id;
153
+    }
154
+
155
+    public function rmdir(string $path): bool {
156
+        $path = $this->normalizePath($path);
157
+        $entry = $this->getCache()->get($path);
158
+
159
+        if (!$entry || $entry->getMimeType() !== ICacheEntry::DIRECTORY_MIMETYPE) {
160
+            return false;
161
+        }
162
+
163
+        return $this->rmObjects($entry);
164
+    }
165
+
166
+    private function rmObjects(ICacheEntry $entry): bool {
167
+        $children = $this->getCache()->getFolderContentsById($entry->getId());
168
+        foreach ($children as $child) {
169
+            if ($child->getMimeType() === ICacheEntry::DIRECTORY_MIMETYPE) {
170
+                if (!$this->rmObjects($child)) {
171
+                    return false;
172
+                }
173
+            } else {
174
+                if (!$this->rmObject($child)) {
175
+                    return false;
176
+                }
177
+            }
178
+        }
179
+
180
+        if (!$this->preserveCacheItemsOnDelete) {
181
+            $this->getCache()->remove($entry->getPath());
182
+        }
183
+
184
+        return true;
185
+    }
186
+
187
+    public function unlink(string $path): bool {
188
+        $path = $this->normalizePath($path);
189
+        $entry = $this->getCache()->get($path);
190
+
191
+        if ($entry instanceof ICacheEntry) {
192
+            if ($entry->getMimeType() === ICacheEntry::DIRECTORY_MIMETYPE) {
193
+                return $this->rmObjects($entry);
194
+            } else {
195
+                return $this->rmObject($entry);
196
+            }
197
+        }
198
+        return false;
199
+    }
200
+
201
+    public function rmObject(ICacheEntry $entry): bool {
202
+        try {
203
+            $this->objectStore->deleteObject($this->getURN($entry->getId()));
204
+        } catch (\Exception $ex) {
205
+            if ($ex->getCode() !== 404) {
206
+                $this->logger->error(
207
+                    'Could not delete object ' . $this->getURN($entry->getId()) . ' for ' . $entry->getPath(),
208
+                    [
209
+                        'app' => 'objectstore',
210
+                        'exception' => $ex,
211
+                    ]
212
+                );
213
+                return false;
214
+            }
215
+            //removing from cache is ok as it does not exist in the objectstore anyway
216
+        }
217
+        if (!$this->preserveCacheItemsOnDelete) {
218
+            $this->getCache()->remove($entry->getPath());
219
+        }
220
+        return true;
221
+    }
222
+
223
+    public function stat(string $path): array|false {
224
+        $path = $this->normalizePath($path);
225
+        $cacheEntry = $this->getCache()->get($path);
226
+        if ($cacheEntry instanceof CacheEntry) {
227
+            return $cacheEntry->getData();
228
+        } else {
229
+            if ($path === '') {
230
+                $this->mkdir('', true);
231
+                $cacheEntry = $this->getCache()->get($path);
232
+                if ($cacheEntry instanceof CacheEntry) {
233
+                    return $cacheEntry->getData();
234
+                }
235
+            }
236
+            return false;
237
+        }
238
+    }
239
+
240
+    public function getPermissions(string $path): int {
241
+        $stat = $this->stat($path);
242
+
243
+        if (is_array($stat) && isset($stat['permissions'])) {
244
+            return $stat['permissions'];
245
+        }
246
+
247
+        return parent::getPermissions($path);
248
+    }
249
+
250
+    /**
251
+     * Override this method if you need a different unique resource identifier for your object storage implementation.
252
+     * The default implementations just appends the fileId to 'urn:oid:'. Make sure the URN is unique over all users.
253
+     * You may need a mapping table to store your URN if it cannot be generated from the fileid.
254
+     *
255
+     * @return string the unified resource name used to identify the object
256
+     */
257
+    public function getURN(int $fileId): string {
258
+        return $this->objectPrefix . $fileId;
259
+    }
260
+
261
+    public function opendir(string $path) {
262
+        $path = $this->normalizePath($path);
263
+
264
+        try {
265
+            $files = [];
266
+            $folderContents = $this->getCache()->getFolderContents($path);
267
+            foreach ($folderContents as $file) {
268
+                $files[] = $file['name'];
269
+            }
270
+
271
+            return IteratorDirectory::wrap($files);
272
+        } catch (\Exception $e) {
273
+            $this->logger->error($e->getMessage(), ['exception' => $e]);
274
+            return false;
275
+        }
276
+    }
277
+
278
+    public function filetype(string $path): string|false {
279
+        $path = $this->normalizePath($path);
280
+        $stat = $this->stat($path);
281
+        if ($stat) {
282
+            if ($stat['mimetype'] === 'httpd/unix-directory') {
283
+                return 'dir';
284
+            }
285
+            return 'file';
286
+        } else {
287
+            return false;
288
+        }
289
+    }
290
+
291
+    public function fopen(string $path, string $mode) {
292
+        $path = $this->normalizePath($path);
293
+
294
+        if (strrpos($path, '.') !== false) {
295
+            $ext = substr($path, strrpos($path, '.'));
296
+        } else {
297
+            $ext = '';
298
+        }
299
+
300
+        switch ($mode) {
301
+            case 'r':
302
+            case 'rb':
303
+                $stat = $this->stat($path);
304
+                if (is_array($stat)) {
305
+                    $filesize = $stat['size'] ?? 0;
306
+                    // Reading 0 sized files is a waste of time
307
+                    if ($filesize === 0) {
308
+                        return fopen('php://memory', $mode);
309
+                    }
310
+
311
+                    try {
312
+                        $handle = $this->objectStore->readObject($this->getURN($stat['fileid']));
313
+                        if ($handle === false) {
314
+                            return false; // keep backward compatibility
315
+                        }
316
+                        $streamStat = fstat($handle);
317
+                        $actualSize = $streamStat['size'] ?? -1;
318
+                        if ($actualSize > -1 && $actualSize !== $filesize) {
319
+                            $this->getCache()->update((int)$stat['fileid'], ['size' => $actualSize]);
320
+                        }
321
+                        return $handle;
322
+                    } catch (NotFoundException $e) {
323
+                        $this->logger->error(
324
+                            'Could not get object ' . $this->getURN($stat['fileid']) . ' for file ' . $path,
325
+                            [
326
+                                'app' => 'objectstore',
327
+                                'exception' => $e,
328
+                            ]
329
+                        );
330
+                        throw $e;
331
+                    } catch (\Exception $e) {
332
+                        $this->logger->error(
333
+                            'Could not get object ' . $this->getURN($stat['fileid']) . ' for file ' . $path,
334
+                            [
335
+                                'app' => 'objectstore',
336
+                                'exception' => $e,
337
+                            ]
338
+                        );
339
+                        return false;
340
+                    }
341
+                } else {
342
+                    return false;
343
+                }
344
+                // no break
345
+            case 'w':
346
+            case 'wb':
347
+            case 'w+':
348
+            case 'wb+':
349
+                $dirName = dirname($path);
350
+                $parentExists = $this->is_dir($dirName);
351
+                if (!$parentExists) {
352
+                    return false;
353
+                }
354
+
355
+                $tmpFile = \OC::$server->getTempManager()->getTemporaryFile($ext);
356
+                $handle = fopen($tmpFile, $mode);
357
+                return CallbackWrapper::wrap($handle, null, null, function () use ($path, $tmpFile) {
358
+                    $this->writeBack($tmpFile, $path);
359
+                    unlink($tmpFile);
360
+                });
361
+            case 'a':
362
+            case 'ab':
363
+            case 'r+':
364
+            case 'a+':
365
+            case 'x':
366
+            case 'x+':
367
+            case 'c':
368
+            case 'c+':
369
+                $tmpFile = \OC::$server->getTempManager()->getTemporaryFile($ext);
370
+                if ($this->file_exists($path)) {
371
+                    $source = $this->fopen($path, 'r');
372
+                    file_put_contents($tmpFile, $source);
373
+                }
374
+                $handle = fopen($tmpFile, $mode);
375
+                return CallbackWrapper::wrap($handle, null, null, function () use ($path, $tmpFile) {
376
+                    $this->writeBack($tmpFile, $path);
377
+                    unlink($tmpFile);
378
+                });
379
+        }
380
+        return false;
381
+    }
382
+
383
+    public function file_exists(string $path): bool {
384
+        $path = $this->normalizePath($path);
385
+        return (bool)$this->stat($path);
386
+    }
387
+
388
+    public function rename(string $source, string $target): bool {
389
+        $source = $this->normalizePath($source);
390
+        $target = $this->normalizePath($target);
391
+        $this->remove($target);
392
+        $this->getCache()->move($source, $target);
393
+        $this->touch(dirname($target));
394
+        return true;
395
+    }
396
+
397
+    public function getMimeType(string $path): string|false {
398
+        $path = $this->normalizePath($path);
399
+        return parent::getMimeType($path);
400
+    }
401
+
402
+    public function touch(string $path, ?int $mtime = null): bool {
403
+        if (is_null($mtime)) {
404
+            $mtime = time();
405
+        }
406
+
407
+        $path = $this->normalizePath($path);
408
+        $dirName = dirname($path);
409
+        $parentExists = $this->is_dir($dirName);
410
+        if (!$parentExists) {
411
+            return false;
412
+        }
413
+
414
+        $stat = $this->stat($path);
415
+        if (is_array($stat)) {
416
+            // update existing mtime in db
417
+            $stat['mtime'] = $mtime;
418
+            $this->getCache()->update($stat['fileid'], $stat);
419
+        } else {
420
+            try {
421
+                //create a empty file, need to have at least on char to make it
422
+                // work with all object storage implementations
423
+                $this->file_put_contents($path, ' ');
424
+            } catch (\Exception $ex) {
425
+                $this->logger->error(
426
+                    'Could not create object for ' . $path,
427
+                    [
428
+                        'app' => 'objectstore',
429
+                        'exception' => $ex,
430
+                    ]
431
+                );
432
+                throw $ex;
433
+            }
434
+        }
435
+        return true;
436
+    }
437
+
438
+    public function writeBack(string $tmpFile, string $path) {
439
+        $size = filesize($tmpFile);
440
+        $this->writeStream($path, fopen($tmpFile, 'r'), $size);
441
+    }
442
+
443
+    public function hasUpdated(string $path, int $time): bool {
444
+        return false;
445
+    }
446
+
447
+    public function needsPartFile(): bool {
448
+        return false;
449
+    }
450
+
451
+    public function file_put_contents(string $path, mixed $data): int {
452
+        $fh = fopen('php://temp', 'w+');
453
+        fwrite($fh, $data);
454
+        rewind($fh);
455
+        return $this->writeStream($path, $fh, strlen($data));
456
+    }
457
+
458
+    public function writeStream(string $path, $stream, ?int $size = null): int {
459
+        if ($size === null) {
460
+            $stats = fstat($stream);
461
+            if (is_array($stats) && isset($stats['size'])) {
462
+                $size = $stats['size'];
463
+            }
464
+        }
465
+
466
+        $stat = $this->stat($path);
467
+        if (empty($stat)) {
468
+            // create new file
469
+            $stat = [
470
+                'permissions' => \OCP\Constants::PERMISSION_ALL - \OCP\Constants::PERMISSION_CREATE,
471
+            ];
472
+        }
473
+        // update stat with new data
474
+        $mTime = time();
475
+        $stat['size'] = (int)$size;
476
+        $stat['mtime'] = $mTime;
477
+        $stat['storage_mtime'] = $mTime;
478
+
479
+        $mimetypeDetector = \OC::$server->getMimeTypeDetector();
480
+        $mimetype = $mimetypeDetector->detectPath($path);
481
+        $metadata = [
482
+            'mimetype' => $mimetype,
483
+            'original-storage' => $this->getId(),
484
+            'original-path' => $path,
485
+        ];
486
+        if ($size) {
487
+            $metadata['size'] = $size;
488
+        }
489
+
490
+        $stat['mimetype'] = $mimetype;
491
+        $stat['etag'] = $this->getETag($path);
492
+        $stat['checksum'] = '';
493
+
494
+        $exists = $this->getCache()->inCache($path);
495
+        $uploadPath = $exists ? $path : $path . '.part';
496
+
497
+        if ($exists) {
498
+            $fileId = $stat['fileid'];
499
+        } else {
500
+            $parent = $this->normalizePath(dirname($path));
501
+            if (!$this->is_dir($parent)) {
502
+                throw new \InvalidArgumentException("trying to upload a file ($path) inside a non-directory ($parent)");
503
+            }
504
+            $fileId = $this->getCache()->put($uploadPath, $stat);
505
+        }
506
+
507
+        $urn = $this->getURN($fileId);
508
+        try {
509
+            //upload to object storage
510
+
511
+            $totalWritten = 0;
512
+            $countStream = CountWrapper::wrap($stream, function ($writtenSize) use ($fileId, $size, $exists, &$totalWritten) {
513
+                if (is_null($size) && !$exists) {
514
+                    $this->getCache()->update($fileId, [
515
+                        'size' => $writtenSize,
516
+                    ]);
517
+                }
518
+                $totalWritten = $writtenSize;
519
+            });
520
+
521
+            if ($this->objectStore instanceof IObjectStoreMetaData) {
522
+                $this->objectStore->writeObjectWithMetaData($urn, $countStream, $metadata);
523
+            } else {
524
+                $this->objectStore->writeObject($urn, $countStream, $metadata['mimetype']);
525
+            }
526
+            if (is_resource($countStream)) {
527
+                fclose($countStream);
528
+            }
529
+
530
+            $stat['size'] = $totalWritten;
531
+        } catch (\Exception $ex) {
532
+            if (!$exists) {
533
+                /*
534 534
 				 * Only remove the entry if we are dealing with a new file.
535 535
 				 * Else people lose access to existing files
536 536
 				 */
537
-				$this->getCache()->remove($uploadPath);
538
-				$this->logger->error(
539
-					'Could not create object ' . $urn . ' for ' . $path,
540
-					[
541
-						'app' => 'objectstore',
542
-						'exception' => $ex,
543
-					]
544
-				);
545
-			} else {
546
-				$this->logger->error(
547
-					'Could not update object ' . $urn . ' for ' . $path,
548
-					[
549
-						'app' => 'objectstore',
550
-						'exception' => $ex,
551
-					]
552
-				);
553
-			}
554
-			throw new GenericFileException('Error while writing stream to object store', 0, $ex);
555
-		}
556
-
557
-		if ($exists) {
558
-			// Always update the unencrypted size, for encryption the Encryption wrapper will update this afterwards anyways
559
-			$stat['unencrypted_size'] = $stat['size'];
560
-			$this->getCache()->update($fileId, $stat);
561
-		} else {
562
-			if (!$this->validateWrites || $this->objectStore->objectExists($urn)) {
563
-				$this->getCache()->move($uploadPath, $path);
564
-			} else {
565
-				$this->getCache()->remove($uploadPath);
566
-				throw new \Exception("Object not found after writing (urn: $urn, path: $path)", 404);
567
-			}
568
-		}
569
-
570
-		return $totalWritten;
571
-	}
572
-
573
-	public function getObjectStore(): IObjectStore {
574
-		return $this->objectStore;
575
-	}
576
-
577
-	public function copyFromStorage(
578
-		IStorage $sourceStorage,
579
-		string $sourceInternalPath,
580
-		string $targetInternalPath,
581
-		bool $preserveMtime = false,
582
-	): bool {
583
-		if ($sourceStorage->instanceOfStorage(ObjectStoreStorage::class)) {
584
-			/** @var ObjectStoreStorage $sourceStorage */
585
-			if ($sourceStorage->getObjectStore()->getStorageId() === $this->getObjectStore()->getStorageId()) {
586
-				/** @var CacheEntry $sourceEntry */
587
-				$sourceEntry = $sourceStorage->getCache()->get($sourceInternalPath);
588
-				$sourceEntryData = $sourceEntry->getData();
589
-				// $sourceEntry['permissions'] here is the permissions from the jailed storage for the current
590
-				// user. Instead we use $sourceEntryData['scan_permissions'] that are the permissions from the
591
-				// unjailed storage.
592
-				if (is_array($sourceEntryData) && array_key_exists('scan_permissions', $sourceEntryData)) {
593
-					$sourceEntry['permissions'] = $sourceEntryData['scan_permissions'];
594
-				}
595
-				$this->copyInner($sourceStorage->getCache(), $sourceEntry, $targetInternalPath);
596
-				return true;
597
-			}
598
-		}
599
-
600
-		return parent::copyFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
601
-	}
602
-
603
-	public function moveFromStorage(IStorage $sourceStorage, string $sourceInternalPath, string $targetInternalPath, ?ICacheEntry $sourceCacheEntry = null): bool {
604
-		$sourceCache = $sourceStorage->getCache();
605
-		if (
606
-			$sourceStorage->instanceOfStorage(ObjectStoreStorage::class)
607
-			&& $sourceStorage->getObjectStore()->getStorageId() === $this->getObjectStore()->getStorageId()
608
-		) {
609
-			if ($this->getCache()->get($targetInternalPath)) {
610
-				$this->unlink($targetInternalPath);
611
-				$this->getCache()->remove($targetInternalPath);
612
-			}
613
-			$this->getCache()->moveFromCache($sourceCache, $sourceInternalPath, $targetInternalPath);
614
-			// Do not import any data when source and target bucket are identical.
615
-			return true;
616
-		}
617
-		if (!$sourceCacheEntry) {
618
-			$sourceCacheEntry = $sourceCache->get($sourceInternalPath);
619
-		}
620
-
621
-		$this->copyObjects($sourceStorage, $sourceCache, $sourceCacheEntry);
622
-		if ($sourceStorage->instanceOfStorage(ObjectStoreStorage::class)) {
623
-			/** @var ObjectStoreStorage $sourceStorage */
624
-			$sourceStorage->setPreserveCacheOnDelete(true);
625
-		}
626
-		if ($sourceCacheEntry->getMimeType() === ICacheEntry::DIRECTORY_MIMETYPE) {
627
-			$sourceStorage->rmdir($sourceInternalPath);
628
-		} else {
629
-			$sourceStorage->unlink($sourceInternalPath);
630
-		}
631
-		if ($sourceStorage->instanceOfStorage(ObjectStoreStorage::class)) {
632
-			/** @var ObjectStoreStorage $sourceStorage */
633
-			$sourceStorage->setPreserveCacheOnDelete(false);
634
-		}
635
-		if ($this->getCache()->get($targetInternalPath)) {
636
-			$this->unlink($targetInternalPath);
637
-			$this->getCache()->remove($targetInternalPath);
638
-		}
639
-		$this->getCache()->moveFromCache($sourceCache, $sourceInternalPath, $targetInternalPath);
640
-
641
-		return true;
642
-	}
643
-
644
-	/**
645
-	 * Copy the object(s) of a file or folder into this storage, without touching the cache
646
-	 */
647
-	private function copyObjects(IStorage $sourceStorage, ICache $sourceCache, ICacheEntry $sourceCacheEntry) {
648
-		$copiedFiles = [];
649
-		try {
650
-			foreach ($this->getAllChildObjects($sourceCache, $sourceCacheEntry) as $file) {
651
-				$sourceStream = $sourceStorage->fopen($file->getPath(), 'r');
652
-				if (!$sourceStream) {
653
-					throw new \Exception("Failed to open source file {$file->getPath()} ({$file->getId()})");
654
-				}
655
-				$this->objectStore->writeObject($this->getURN($file->getId()), $sourceStream, $file->getMimeType());
656
-				if (is_resource($sourceStream)) {
657
-					fclose($sourceStream);
658
-				}
659
-				$copiedFiles[] = $file->getId();
660
-			}
661
-		} catch (\Exception $e) {
662
-			foreach ($copiedFiles as $fileId) {
663
-				try {
664
-					$this->objectStore->deleteObject($this->getURN($fileId));
665
-				} catch (\Exception $e) {
666
-					// ignore
667
-				}
668
-			}
669
-			throw $e;
670
-		}
671
-	}
672
-
673
-	/**
674
-	 * @return \Iterator<ICacheEntry>
675
-	 */
676
-	private function getAllChildObjects(ICache $cache, ICacheEntry $entry): \Iterator {
677
-		if ($entry->getMimeType() === FileInfo::MIMETYPE_FOLDER) {
678
-			foreach ($cache->getFolderContentsById($entry->getId()) as $child) {
679
-				yield from $this->getAllChildObjects($cache, $child);
680
-			}
681
-		} else {
682
-			yield $entry;
683
-		}
684
-	}
685
-
686
-	public function copy(string $source, string $target): bool {
687
-		$source = $this->normalizePath($source);
688
-		$target = $this->normalizePath($target);
689
-
690
-		$cache = $this->getCache();
691
-		$sourceEntry = $cache->get($source);
692
-		if (!$sourceEntry) {
693
-			throw new NotFoundException('Source object not found');
694
-		}
695
-
696
-		$this->copyInner($cache, $sourceEntry, $target);
697
-
698
-		return true;
699
-	}
700
-
701
-	private function copyInner(ICache $sourceCache, ICacheEntry $sourceEntry, string $to) {
702
-		$cache = $this->getCache();
703
-
704
-		if ($sourceEntry->getMimeType() === FileInfo::MIMETYPE_FOLDER) {
705
-			if ($cache->inCache($to)) {
706
-				$cache->remove($to);
707
-			}
708
-			$this->mkdir($to, false, ['size' => $sourceEntry->getSize()]);
709
-
710
-			foreach ($sourceCache->getFolderContentsById($sourceEntry->getId()) as $child) {
711
-				$this->copyInner($sourceCache, $child, $to . '/' . $child->getName());
712
-			}
713
-		} else {
714
-			$this->copyFile($sourceEntry, $to);
715
-		}
716
-	}
717
-
718
-	private function copyFile(ICacheEntry $sourceEntry, string $to) {
719
-		$cache = $this->getCache();
720
-
721
-		$sourceUrn = $this->getURN($sourceEntry->getId());
722
-
723
-		if (!$cache instanceof Cache) {
724
-			throw new \Exception('Invalid source cache for object store copy');
725
-		}
726
-
727
-		$targetId = $cache->copyFromCache($cache, $sourceEntry, $to);
728
-
729
-		$targetUrn = $this->getURN($targetId);
730
-
731
-		try {
732
-			$this->objectStore->copyObject($sourceUrn, $targetUrn);
733
-			if ($this->handleCopiesAsOwned) {
734
-				// Copied the file thus we gain all permissions as we are the owner now ! warning while this aligns with local storage it should not be used and instead fix local storage !
735
-				$cache->update($targetId, ['permissions' => \OCP\Constants::PERMISSION_ALL]);
736
-			}
737
-		} catch (\Exception $e) {
738
-			$cache->remove($to);
739
-
740
-			throw $e;
741
-		}
742
-	}
743
-
744
-	public function startChunkedWrite(string $targetPath): string {
745
-		if (!$this->objectStore instanceof IObjectStoreMultiPartUpload) {
746
-			throw new GenericFileException('Object store does not support multipart upload');
747
-		}
748
-		$cacheEntry = $this->getCache()->get($targetPath);
749
-		$urn = $this->getURN($cacheEntry->getId());
750
-		return $this->objectStore->initiateMultipartUpload($urn);
751
-	}
752
-
753
-	/**
754
-	 * @throws GenericFileException
755
-	 */
756
-	public function putChunkedWritePart(
757
-		string $targetPath,
758
-		string $writeToken,
759
-		string $chunkId,
760
-		$data,
761
-		$size = null,
762
-	): ?array {
763
-		if (!$this->objectStore instanceof IObjectStoreMultiPartUpload) {
764
-			throw new GenericFileException('Object store does not support multipart upload');
765
-		}
766
-
767
-		$cacheEntry = $this->getCache()->get($targetPath);
768
-		$urn = $this->getURN($cacheEntry->getId());
769
-
770
-		$result = $this->objectStore->uploadMultipartPart($urn, $writeToken, (int)$chunkId, $data, $size);
771
-
772
-		$parts[$chunkId] = [
773
-			'PartNumber' => $chunkId,
774
-			'ETag' => trim($result->get('ETag'), '"'),
775
-		];
776
-		return $parts[$chunkId];
777
-	}
778
-
779
-	public function completeChunkedWrite(string $targetPath, string $writeToken): int {
780
-		if (!$this->objectStore instanceof IObjectStoreMultiPartUpload) {
781
-			throw new GenericFileException('Object store does not support multipart upload');
782
-		}
783
-		$cacheEntry = $this->getCache()->get($targetPath);
784
-		$urn = $this->getURN($cacheEntry->getId());
785
-		$parts = $this->objectStore->getMultipartUploads($urn, $writeToken);
786
-		$sortedParts = array_values($parts);
787
-		sort($sortedParts);
788
-		try {
789
-			$size = $this->objectStore->completeMultipartUpload($urn, $writeToken, $sortedParts);
790
-			$stat = $this->stat($targetPath);
791
-			$mtime = time();
792
-			if (is_array($stat)) {
793
-				$stat['size'] = $size;
794
-				$stat['mtime'] = $mtime;
795
-				$stat['mimetype'] = $this->getMimeType($targetPath);
796
-				$this->getCache()->update($stat['fileid'], $stat);
797
-			}
798
-		} catch (S3MultipartUploadException|S3Exception $e) {
799
-			$this->objectStore->abortMultipartUpload($urn, $writeToken);
800
-			$this->logger->error(
801
-				'Could not complete multipart upload ' . $urn . ' with uploadId ' . $writeToken,
802
-				[
803
-					'app' => 'objectstore',
804
-					'exception' => $e,
805
-				]
806
-			);
807
-			throw new GenericFileException('Could not write chunked file');
808
-		}
809
-		return $size;
810
-	}
811
-
812
-	public function cancelChunkedWrite(string $targetPath, string $writeToken): void {
813
-		if (!$this->objectStore instanceof IObjectStoreMultiPartUpload) {
814
-			throw new GenericFileException('Object store does not support multipart upload');
815
-		}
816
-		$cacheEntry = $this->getCache()->get($targetPath);
817
-		$urn = $this->getURN($cacheEntry->getId());
818
-		$this->objectStore->abortMultipartUpload($urn, $writeToken);
819
-	}
820
-
821
-	public function setPreserveCacheOnDelete(bool $preserve) {
822
-		$this->preserveCacheItemsOnDelete = $preserve;
823
-	}
824
-
825
-	public function free_space(string $path): int|float|false {
826
-		if ($this->totalSizeLimit === null) {
827
-			return FileInfo::SPACE_UNLIMITED;
828
-		}
829
-
830
-		// To avoid iterating all objects in the object store, calculate the sum of the cached sizes of the root folders of all object storages.
831
-		$qb = Server::get(IDBConnection::class)->getQueryBuilder();
832
-		$result = $qb->select($qb->func()->sum('f.size'))
833
-			->from('storages', 's')
834
-			->leftJoin('s', 'filecache', 'f', $qb->expr()->eq('f.storage', 's.numeric_id'))
835
-			->where($qb->expr()->like('s.id', $qb->createNamedParameter('object::%'), IQueryBuilder::PARAM_STR))
836
-			->andWhere($qb->expr()->eq('f.path', $qb->createNamedParameter('')))
837
-			->executeQuery();
838
-		$used = $result->fetchOne();
839
-		$result->closeCursor();
840
-
841
-		$available = $this->totalSizeLimit - $used;
842
-		if ($available < 0) {
843
-			$available = 0;
844
-		}
845
-
846
-		return $available;
847
-	}
848
-
849
-	#[Override]
850
-	public function getDirectDownloadById(string $fileId): array|false {
851
-		$expiration = new \DateTimeImmutable('+60 minutes');
852
-		$url = $this->objectStore->preSignedUrl($this->getURN((int)$fileId), $expiration);
853
-		return $url ? ['url' => $url, 'expiration' => $expiration->getTimestamp()] : false;
854
-	}
855
-
856
-	#[Override]
857
-	public function getDirectDownload(string $path): array|false {
858
-		$path = $this->normalizePath($path);
859
-		$cacheEntry = $this->getCache()->get($path);
860
-
861
-		if (!$cacheEntry || $cacheEntry->getMimeType() === FileInfo::MIMETYPE_FOLDER) {
862
-			return false;
863
-		}
864
-		return $this->getDirectDownloadById((string)$cacheEntry->getId());
865
-	}
537
+                $this->getCache()->remove($uploadPath);
538
+                $this->logger->error(
539
+                    'Could not create object ' . $urn . ' for ' . $path,
540
+                    [
541
+                        'app' => 'objectstore',
542
+                        'exception' => $ex,
543
+                    ]
544
+                );
545
+            } else {
546
+                $this->logger->error(
547
+                    'Could not update object ' . $urn . ' for ' . $path,
548
+                    [
549
+                        'app' => 'objectstore',
550
+                        'exception' => $ex,
551
+                    ]
552
+                );
553
+            }
554
+            throw new GenericFileException('Error while writing stream to object store', 0, $ex);
555
+        }
556
+
557
+        if ($exists) {
558
+            // Always update the unencrypted size, for encryption the Encryption wrapper will update this afterwards anyways
559
+            $stat['unencrypted_size'] = $stat['size'];
560
+            $this->getCache()->update($fileId, $stat);
561
+        } else {
562
+            if (!$this->validateWrites || $this->objectStore->objectExists($urn)) {
563
+                $this->getCache()->move($uploadPath, $path);
564
+            } else {
565
+                $this->getCache()->remove($uploadPath);
566
+                throw new \Exception("Object not found after writing (urn: $urn, path: $path)", 404);
567
+            }
568
+        }
569
+
570
+        return $totalWritten;
571
+    }
572
+
573
+    public function getObjectStore(): IObjectStore {
574
+        return $this->objectStore;
575
+    }
576
+
577
+    public function copyFromStorage(
578
+        IStorage $sourceStorage,
579
+        string $sourceInternalPath,
580
+        string $targetInternalPath,
581
+        bool $preserveMtime = false,
582
+    ): bool {
583
+        if ($sourceStorage->instanceOfStorage(ObjectStoreStorage::class)) {
584
+            /** @var ObjectStoreStorage $sourceStorage */
585
+            if ($sourceStorage->getObjectStore()->getStorageId() === $this->getObjectStore()->getStorageId()) {
586
+                /** @var CacheEntry $sourceEntry */
587
+                $sourceEntry = $sourceStorage->getCache()->get($sourceInternalPath);
588
+                $sourceEntryData = $sourceEntry->getData();
589
+                // $sourceEntry['permissions'] here is the permissions from the jailed storage for the current
590
+                // user. Instead we use $sourceEntryData['scan_permissions'] that are the permissions from the
591
+                // unjailed storage.
592
+                if (is_array($sourceEntryData) && array_key_exists('scan_permissions', $sourceEntryData)) {
593
+                    $sourceEntry['permissions'] = $sourceEntryData['scan_permissions'];
594
+                }
595
+                $this->copyInner($sourceStorage->getCache(), $sourceEntry, $targetInternalPath);
596
+                return true;
597
+            }
598
+        }
599
+
600
+        return parent::copyFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
601
+    }
602
+
603
+    public function moveFromStorage(IStorage $sourceStorage, string $sourceInternalPath, string $targetInternalPath, ?ICacheEntry $sourceCacheEntry = null): bool {
604
+        $sourceCache = $sourceStorage->getCache();
605
+        if (
606
+            $sourceStorage->instanceOfStorage(ObjectStoreStorage::class)
607
+            && $sourceStorage->getObjectStore()->getStorageId() === $this->getObjectStore()->getStorageId()
608
+        ) {
609
+            if ($this->getCache()->get($targetInternalPath)) {
610
+                $this->unlink($targetInternalPath);
611
+                $this->getCache()->remove($targetInternalPath);
612
+            }
613
+            $this->getCache()->moveFromCache($sourceCache, $sourceInternalPath, $targetInternalPath);
614
+            // Do not import any data when source and target bucket are identical.
615
+            return true;
616
+        }
617
+        if (!$sourceCacheEntry) {
618
+            $sourceCacheEntry = $sourceCache->get($sourceInternalPath);
619
+        }
620
+
621
+        $this->copyObjects($sourceStorage, $sourceCache, $sourceCacheEntry);
622
+        if ($sourceStorage->instanceOfStorage(ObjectStoreStorage::class)) {
623
+            /** @var ObjectStoreStorage $sourceStorage */
624
+            $sourceStorage->setPreserveCacheOnDelete(true);
625
+        }
626
+        if ($sourceCacheEntry->getMimeType() === ICacheEntry::DIRECTORY_MIMETYPE) {
627
+            $sourceStorage->rmdir($sourceInternalPath);
628
+        } else {
629
+            $sourceStorage->unlink($sourceInternalPath);
630
+        }
631
+        if ($sourceStorage->instanceOfStorage(ObjectStoreStorage::class)) {
632
+            /** @var ObjectStoreStorage $sourceStorage */
633
+            $sourceStorage->setPreserveCacheOnDelete(false);
634
+        }
635
+        if ($this->getCache()->get($targetInternalPath)) {
636
+            $this->unlink($targetInternalPath);
637
+            $this->getCache()->remove($targetInternalPath);
638
+        }
639
+        $this->getCache()->moveFromCache($sourceCache, $sourceInternalPath, $targetInternalPath);
640
+
641
+        return true;
642
+    }
643
+
644
+    /**
645
+     * Copy the object(s) of a file or folder into this storage, without touching the cache
646
+     */
647
+    private function copyObjects(IStorage $sourceStorage, ICache $sourceCache, ICacheEntry $sourceCacheEntry) {
648
+        $copiedFiles = [];
649
+        try {
650
+            foreach ($this->getAllChildObjects($sourceCache, $sourceCacheEntry) as $file) {
651
+                $sourceStream = $sourceStorage->fopen($file->getPath(), 'r');
652
+                if (!$sourceStream) {
653
+                    throw new \Exception("Failed to open source file {$file->getPath()} ({$file->getId()})");
654
+                }
655
+                $this->objectStore->writeObject($this->getURN($file->getId()), $sourceStream, $file->getMimeType());
656
+                if (is_resource($sourceStream)) {
657
+                    fclose($sourceStream);
658
+                }
659
+                $copiedFiles[] = $file->getId();
660
+            }
661
+        } catch (\Exception $e) {
662
+            foreach ($copiedFiles as $fileId) {
663
+                try {
664
+                    $this->objectStore->deleteObject($this->getURN($fileId));
665
+                } catch (\Exception $e) {
666
+                    // ignore
667
+                }
668
+            }
669
+            throw $e;
670
+        }
671
+    }
672
+
673
+    /**
674
+     * @return \Iterator<ICacheEntry>
675
+     */
676
+    private function getAllChildObjects(ICache $cache, ICacheEntry $entry): \Iterator {
677
+        if ($entry->getMimeType() === FileInfo::MIMETYPE_FOLDER) {
678
+            foreach ($cache->getFolderContentsById($entry->getId()) as $child) {
679
+                yield from $this->getAllChildObjects($cache, $child);
680
+            }
681
+        } else {
682
+            yield $entry;
683
+        }
684
+    }
685
+
686
+    public function copy(string $source, string $target): bool {
687
+        $source = $this->normalizePath($source);
688
+        $target = $this->normalizePath($target);
689
+
690
+        $cache = $this->getCache();
691
+        $sourceEntry = $cache->get($source);
692
+        if (!$sourceEntry) {
693
+            throw new NotFoundException('Source object not found');
694
+        }
695
+
696
+        $this->copyInner($cache, $sourceEntry, $target);
697
+
698
+        return true;
699
+    }
700
+
701
+    private function copyInner(ICache $sourceCache, ICacheEntry $sourceEntry, string $to) {
702
+        $cache = $this->getCache();
703
+
704
+        if ($sourceEntry->getMimeType() === FileInfo::MIMETYPE_FOLDER) {
705
+            if ($cache->inCache($to)) {
706
+                $cache->remove($to);
707
+            }
708
+            $this->mkdir($to, false, ['size' => $sourceEntry->getSize()]);
709
+
710
+            foreach ($sourceCache->getFolderContentsById($sourceEntry->getId()) as $child) {
711
+                $this->copyInner($sourceCache, $child, $to . '/' . $child->getName());
712
+            }
713
+        } else {
714
+            $this->copyFile($sourceEntry, $to);
715
+        }
716
+    }
717
+
718
+    private function copyFile(ICacheEntry $sourceEntry, string $to) {
719
+        $cache = $this->getCache();
720
+
721
+        $sourceUrn = $this->getURN($sourceEntry->getId());
722
+
723
+        if (!$cache instanceof Cache) {
724
+            throw new \Exception('Invalid source cache for object store copy');
725
+        }
726
+
727
+        $targetId = $cache->copyFromCache($cache, $sourceEntry, $to);
728
+
729
+        $targetUrn = $this->getURN($targetId);
730
+
731
+        try {
732
+            $this->objectStore->copyObject($sourceUrn, $targetUrn);
733
+            if ($this->handleCopiesAsOwned) {
734
+                // Copied the file thus we gain all permissions as we are the owner now ! warning while this aligns with local storage it should not be used and instead fix local storage !
735
+                $cache->update($targetId, ['permissions' => \OCP\Constants::PERMISSION_ALL]);
736
+            }
737
+        } catch (\Exception $e) {
738
+            $cache->remove($to);
739
+
740
+            throw $e;
741
+        }
742
+    }
743
+
744
+    public function startChunkedWrite(string $targetPath): string {
745
+        if (!$this->objectStore instanceof IObjectStoreMultiPartUpload) {
746
+            throw new GenericFileException('Object store does not support multipart upload');
747
+        }
748
+        $cacheEntry = $this->getCache()->get($targetPath);
749
+        $urn = $this->getURN($cacheEntry->getId());
750
+        return $this->objectStore->initiateMultipartUpload($urn);
751
+    }
752
+
753
+    /**
754
+     * @throws GenericFileException
755
+     */
756
+    public function putChunkedWritePart(
757
+        string $targetPath,
758
+        string $writeToken,
759
+        string $chunkId,
760
+        $data,
761
+        $size = null,
762
+    ): ?array {
763
+        if (!$this->objectStore instanceof IObjectStoreMultiPartUpload) {
764
+            throw new GenericFileException('Object store does not support multipart upload');
765
+        }
766
+
767
+        $cacheEntry = $this->getCache()->get($targetPath);
768
+        $urn = $this->getURN($cacheEntry->getId());
769
+
770
+        $result = $this->objectStore->uploadMultipartPart($urn, $writeToken, (int)$chunkId, $data, $size);
771
+
772
+        $parts[$chunkId] = [
773
+            'PartNumber' => $chunkId,
774
+            'ETag' => trim($result->get('ETag'), '"'),
775
+        ];
776
+        return $parts[$chunkId];
777
+    }
778
+
779
+    public function completeChunkedWrite(string $targetPath, string $writeToken): int {
780
+        if (!$this->objectStore instanceof IObjectStoreMultiPartUpload) {
781
+            throw new GenericFileException('Object store does not support multipart upload');
782
+        }
783
+        $cacheEntry = $this->getCache()->get($targetPath);
784
+        $urn = $this->getURN($cacheEntry->getId());
785
+        $parts = $this->objectStore->getMultipartUploads($urn, $writeToken);
786
+        $sortedParts = array_values($parts);
787
+        sort($sortedParts);
788
+        try {
789
+            $size = $this->objectStore->completeMultipartUpload($urn, $writeToken, $sortedParts);
790
+            $stat = $this->stat($targetPath);
791
+            $mtime = time();
792
+            if (is_array($stat)) {
793
+                $stat['size'] = $size;
794
+                $stat['mtime'] = $mtime;
795
+                $stat['mimetype'] = $this->getMimeType($targetPath);
796
+                $this->getCache()->update($stat['fileid'], $stat);
797
+            }
798
+        } catch (S3MultipartUploadException|S3Exception $e) {
799
+            $this->objectStore->abortMultipartUpload($urn, $writeToken);
800
+            $this->logger->error(
801
+                'Could not complete multipart upload ' . $urn . ' with uploadId ' . $writeToken,
802
+                [
803
+                    'app' => 'objectstore',
804
+                    'exception' => $e,
805
+                ]
806
+            );
807
+            throw new GenericFileException('Could not write chunked file');
808
+        }
809
+        return $size;
810
+    }
811
+
812
+    public function cancelChunkedWrite(string $targetPath, string $writeToken): void {
813
+        if (!$this->objectStore instanceof IObjectStoreMultiPartUpload) {
814
+            throw new GenericFileException('Object store does not support multipart upload');
815
+        }
816
+        $cacheEntry = $this->getCache()->get($targetPath);
817
+        $urn = $this->getURN($cacheEntry->getId());
818
+        $this->objectStore->abortMultipartUpload($urn, $writeToken);
819
+    }
820
+
821
+    public function setPreserveCacheOnDelete(bool $preserve) {
822
+        $this->preserveCacheItemsOnDelete = $preserve;
823
+    }
824
+
825
+    public function free_space(string $path): int|float|false {
826
+        if ($this->totalSizeLimit === null) {
827
+            return FileInfo::SPACE_UNLIMITED;
828
+        }
829
+
830
+        // To avoid iterating all objects in the object store, calculate the sum of the cached sizes of the root folders of all object storages.
831
+        $qb = Server::get(IDBConnection::class)->getQueryBuilder();
832
+        $result = $qb->select($qb->func()->sum('f.size'))
833
+            ->from('storages', 's')
834
+            ->leftJoin('s', 'filecache', 'f', $qb->expr()->eq('f.storage', 's.numeric_id'))
835
+            ->where($qb->expr()->like('s.id', $qb->createNamedParameter('object::%'), IQueryBuilder::PARAM_STR))
836
+            ->andWhere($qb->expr()->eq('f.path', $qb->createNamedParameter('')))
837
+            ->executeQuery();
838
+        $used = $result->fetchOne();
839
+        $result->closeCursor();
840
+
841
+        $available = $this->totalSizeLimit - $used;
842
+        if ($available < 0) {
843
+            $available = 0;
844
+        }
845
+
846
+        return $available;
847
+    }
848
+
849
+    #[Override]
850
+    public function getDirectDownloadById(string $fileId): array|false {
851
+        $expiration = new \DateTimeImmutable('+60 minutes');
852
+        $url = $this->objectStore->preSignedUrl($this->getURN((int)$fileId), $expiration);
853
+        return $url ? ['url' => $url, 'expiration' => $expiration->getTimestamp()] : false;
854
+    }
855
+
856
+    #[Override]
857
+    public function getDirectDownload(string $path): array|false {
858
+        $path = $this->normalizePath($path);
859
+        $cacheEntry = $this->getCache()->get($path);
860
+
861
+        if (!$cacheEntry || $cacheEntry->getMimeType() === FileInfo::MIMETYPE_FOLDER) {
862
+            return false;
863
+        }
864
+        return $this->getDirectDownloadById((string)$cacheEntry->getId());
865
+    }
866 866
 }
Please login to merge, or discard this patch.
Spacing   +30 added lines, -30 removed lines patch added patch discarded remove patch
@@ -57,17 +57,17 @@  discard block
 block discarded – undo
57 57
 			throw new \Exception('missing IObjectStore instance');
58 58
 		}
59 59
 		if (isset($parameters['storageid'])) {
60
-			$this->id = 'object::store:' . $parameters['storageid'];
60
+			$this->id = 'object::store:'.$parameters['storageid'];
61 61
 		} else {
62
-			$this->id = 'object::store:' . $this->objectStore->getStorageId();
62
+			$this->id = 'object::store:'.$this->objectStore->getStorageId();
63 63
 		}
64 64
 		if (isset($parameters['objectPrefix'])) {
65 65
 			$this->objectPrefix = $parameters['objectPrefix'];
66 66
 		}
67 67
 		if (isset($parameters['validateWrites'])) {
68
-			$this->validateWrites = (bool)$parameters['validateWrites'];
68
+			$this->validateWrites = (bool) $parameters['validateWrites'];
69 69
 		}
70
-		$this->handleCopiesAsOwned = (bool)($parameters['handleCopiesAsOwned'] ?? false);
70
+		$this->handleCopiesAsOwned = (bool) ($parameters['handleCopiesAsOwned'] ?? false);
71 71
 		if (isset($parameters['totalSizeLimit'])) {
72 72
 			$this->totalSizeLimit = $parameters['totalSizeLimit'];
73 73
 		}
@@ -204,7 +204,7 @@  discard block
 block discarded – undo
204 204
 		} catch (\Exception $ex) {
205 205
 			if ($ex->getCode() !== 404) {
206 206
 				$this->logger->error(
207
-					'Could not delete object ' . $this->getURN($entry->getId()) . ' for ' . $entry->getPath(),
207
+					'Could not delete object '.$this->getURN($entry->getId()).' for '.$entry->getPath(),
208 208
 					[
209 209
 						'app' => 'objectstore',
210 210
 						'exception' => $ex,
@@ -220,7 +220,7 @@  discard block
 block discarded – undo
220 220
 		return true;
221 221
 	}
222 222
 
223
-	public function stat(string $path): array|false {
223
+	public function stat(string $path): array | false {
224 224
 		$path = $this->normalizePath($path);
225 225
 		$cacheEntry = $this->getCache()->get($path);
226 226
 		if ($cacheEntry instanceof CacheEntry) {
@@ -255,7 +255,7 @@  discard block
 block discarded – undo
255 255
 	 * @return string the unified resource name used to identify the object
256 256
 	 */
257 257
 	public function getURN(int $fileId): string {
258
-		return $this->objectPrefix . $fileId;
258
+		return $this->objectPrefix.$fileId;
259 259
 	}
260 260
 
261 261
 	public function opendir(string $path) {
@@ -275,7 +275,7 @@  discard block
 block discarded – undo
275 275
 		}
276 276
 	}
277 277
 
278
-	public function filetype(string $path): string|false {
278
+	public function filetype(string $path): string | false {
279 279
 		$path = $this->normalizePath($path);
280 280
 		$stat = $this->stat($path);
281 281
 		if ($stat) {
@@ -316,12 +316,12 @@  discard block
 block discarded – undo
316 316
 						$streamStat = fstat($handle);
317 317
 						$actualSize = $streamStat['size'] ?? -1;
318 318
 						if ($actualSize > -1 && $actualSize !== $filesize) {
319
-							$this->getCache()->update((int)$stat['fileid'], ['size' => $actualSize]);
319
+							$this->getCache()->update((int) $stat['fileid'], ['size' => $actualSize]);
320 320
 						}
321 321
 						return $handle;
322 322
 					} catch (NotFoundException $e) {
323 323
 						$this->logger->error(
324
-							'Could not get object ' . $this->getURN($stat['fileid']) . ' for file ' . $path,
324
+							'Could not get object '.$this->getURN($stat['fileid']).' for file '.$path,
325 325
 							[
326 326
 								'app' => 'objectstore',
327 327
 								'exception' => $e,
@@ -330,7 +330,7 @@  discard block
 block discarded – undo
330 330
 						throw $e;
331 331
 					} catch (\Exception $e) {
332 332
 						$this->logger->error(
333
-							'Could not get object ' . $this->getURN($stat['fileid']) . ' for file ' . $path,
333
+							'Could not get object '.$this->getURN($stat['fileid']).' for file '.$path,
334 334
 							[
335 335
 								'app' => 'objectstore',
336 336
 								'exception' => $e,
@@ -354,7 +354,7 @@  discard block
 block discarded – undo
354 354
 
355 355
 				$tmpFile = \OC::$server->getTempManager()->getTemporaryFile($ext);
356 356
 				$handle = fopen($tmpFile, $mode);
357
-				return CallbackWrapper::wrap($handle, null, null, function () use ($path, $tmpFile) {
357
+				return CallbackWrapper::wrap($handle, null, null, function() use ($path, $tmpFile) {
358 358
 					$this->writeBack($tmpFile, $path);
359 359
 					unlink($tmpFile);
360 360
 				});
@@ -372,7 +372,7 @@  discard block
 block discarded – undo
372 372
 					file_put_contents($tmpFile, $source);
373 373
 				}
374 374
 				$handle = fopen($tmpFile, $mode);
375
-				return CallbackWrapper::wrap($handle, null, null, function () use ($path, $tmpFile) {
375
+				return CallbackWrapper::wrap($handle, null, null, function() use ($path, $tmpFile) {
376 376
 					$this->writeBack($tmpFile, $path);
377 377
 					unlink($tmpFile);
378 378
 				});
@@ -382,7 +382,7 @@  discard block
 block discarded – undo
382 382
 
383 383
 	public function file_exists(string $path): bool {
384 384
 		$path = $this->normalizePath($path);
385
-		return (bool)$this->stat($path);
385
+		return (bool) $this->stat($path);
386 386
 	}
387 387
 
388 388
 	public function rename(string $source, string $target): bool {
@@ -394,7 +394,7 @@  discard block
 block discarded – undo
394 394
 		return true;
395 395
 	}
396 396
 
397
-	public function getMimeType(string $path): string|false {
397
+	public function getMimeType(string $path): string | false {
398 398
 		$path = $this->normalizePath($path);
399 399
 		return parent::getMimeType($path);
400 400
 	}
@@ -423,7 +423,7 @@  discard block
 block discarded – undo
423 423
 				$this->file_put_contents($path, ' ');
424 424
 			} catch (\Exception $ex) {
425 425
 				$this->logger->error(
426
-					'Could not create object for ' . $path,
426
+					'Could not create object for '.$path,
427 427
 					[
428 428
 						'app' => 'objectstore',
429 429
 						'exception' => $ex,
@@ -472,7 +472,7 @@  discard block
 block discarded – undo
472 472
 		}
473 473
 		// update stat with new data
474 474
 		$mTime = time();
475
-		$stat['size'] = (int)$size;
475
+		$stat['size'] = (int) $size;
476 476
 		$stat['mtime'] = $mTime;
477 477
 		$stat['storage_mtime'] = $mTime;
478 478
 
@@ -492,7 +492,7 @@  discard block
 block discarded – undo
492 492
 		$stat['checksum'] = '';
493 493
 
494 494
 		$exists = $this->getCache()->inCache($path);
495
-		$uploadPath = $exists ? $path : $path . '.part';
495
+		$uploadPath = $exists ? $path : $path.'.part';
496 496
 
497 497
 		if ($exists) {
498 498
 			$fileId = $stat['fileid'];
@@ -509,7 +509,7 @@  discard block
 block discarded – undo
509 509
 			//upload to object storage
510 510
 
511 511
 			$totalWritten = 0;
512
-			$countStream = CountWrapper::wrap($stream, function ($writtenSize) use ($fileId, $size, $exists, &$totalWritten) {
512
+			$countStream = CountWrapper::wrap($stream, function($writtenSize) use ($fileId, $size, $exists, &$totalWritten) {
513 513
 				if (is_null($size) && !$exists) {
514 514
 					$this->getCache()->update($fileId, [
515 515
 						'size' => $writtenSize,
@@ -536,7 +536,7 @@  discard block
 block discarded – undo
536 536
 				 */
537 537
 				$this->getCache()->remove($uploadPath);
538 538
 				$this->logger->error(
539
-					'Could not create object ' . $urn . ' for ' . $path,
539
+					'Could not create object '.$urn.' for '.$path,
540 540
 					[
541 541
 						'app' => 'objectstore',
542 542
 						'exception' => $ex,
@@ -544,7 +544,7 @@  discard block
 block discarded – undo
544 544
 				);
545 545
 			} else {
546 546
 				$this->logger->error(
547
-					'Could not update object ' . $urn . ' for ' . $path,
547
+					'Could not update object '.$urn.' for '.$path,
548 548
 					[
549 549
 						'app' => 'objectstore',
550 550
 						'exception' => $ex,
@@ -708,7 +708,7 @@  discard block
 block discarded – undo
708 708
 			$this->mkdir($to, false, ['size' => $sourceEntry->getSize()]);
709 709
 
710 710
 			foreach ($sourceCache->getFolderContentsById($sourceEntry->getId()) as $child) {
711
-				$this->copyInner($sourceCache, $child, $to . '/' . $child->getName());
711
+				$this->copyInner($sourceCache, $child, $to.'/'.$child->getName());
712 712
 			}
713 713
 		} else {
714 714
 			$this->copyFile($sourceEntry, $to);
@@ -767,7 +767,7 @@  discard block
 block discarded – undo
767 767
 		$cacheEntry = $this->getCache()->get($targetPath);
768 768
 		$urn = $this->getURN($cacheEntry->getId());
769 769
 
770
-		$result = $this->objectStore->uploadMultipartPart($urn, $writeToken, (int)$chunkId, $data, $size);
770
+		$result = $this->objectStore->uploadMultipartPart($urn, $writeToken, (int) $chunkId, $data, $size);
771 771
 
772 772
 		$parts[$chunkId] = [
773 773
 			'PartNumber' => $chunkId,
@@ -795,10 +795,10 @@  discard block
 block discarded – undo
795 795
 				$stat['mimetype'] = $this->getMimeType($targetPath);
796 796
 				$this->getCache()->update($stat['fileid'], $stat);
797 797
 			}
798
-		} catch (S3MultipartUploadException|S3Exception $e) {
798
+		} catch (S3MultipartUploadException | S3Exception $e) {
799 799
 			$this->objectStore->abortMultipartUpload($urn, $writeToken);
800 800
 			$this->logger->error(
801
-				'Could not complete multipart upload ' . $urn . ' with uploadId ' . $writeToken,
801
+				'Could not complete multipart upload '.$urn.' with uploadId '.$writeToken,
802 802
 				[
803 803
 					'app' => 'objectstore',
804 804
 					'exception' => $e,
@@ -822,7 +822,7 @@  discard block
 block discarded – undo
822 822
 		$this->preserveCacheItemsOnDelete = $preserve;
823 823
 	}
824 824
 
825
-	public function free_space(string $path): int|float|false {
825
+	public function free_space(string $path): int | float | false {
826 826
 		if ($this->totalSizeLimit === null) {
827 827
 			return FileInfo::SPACE_UNLIMITED;
828 828
 		}
@@ -847,20 +847,20 @@  discard block
 block discarded – undo
847 847
 	}
848 848
 
849 849
 	#[Override]
850
-	public function getDirectDownloadById(string $fileId): array|false {
850
+	public function getDirectDownloadById(string $fileId): array | false {
851 851
 		$expiration = new \DateTimeImmutable('+60 minutes');
852
-		$url = $this->objectStore->preSignedUrl($this->getURN((int)$fileId), $expiration);
852
+		$url = $this->objectStore->preSignedUrl($this->getURN((int) $fileId), $expiration);
853 853
 		return $url ? ['url' => $url, 'expiration' => $expiration->getTimestamp()] : false;
854 854
 	}
855 855
 
856 856
 	#[Override]
857
-	public function getDirectDownload(string $path): array|false {
857
+	public function getDirectDownload(string $path): array | false {
858 858
 		$path = $this->normalizePath($path);
859 859
 		$cacheEntry = $this->getCache()->get($path);
860 860
 
861 861
 		if (!$cacheEntry || $cacheEntry->getMimeType() === FileInfo::MIMETYPE_FOLDER) {
862 862
 			return false;
863 863
 		}
864
-		return $this->getDirectDownloadById((string)$cacheEntry->getId());
864
+		return $this->getDirectDownloadById((string) $cacheEntry->getId());
865 865
 	}
866 866
 }
Please login to merge, or discard this patch.
lib/private/Files/ObjectStore/Azure.php 1 patch
Indentation   +99 added lines, -99 removed lines patch added patch discarded remove patch
@@ -12,113 +12,113 @@
 block discarded – undo
12 12
 use OCP\Files\ObjectStore\IObjectStore;
13 13
 
14 14
 class Azure implements IObjectStore {
15
-	/** @var string */
16
-	private $containerName;
17
-	/** @var string */
18
-	private $accountName;
19
-	/** @var string */
20
-	private $accountKey;
21
-	/** @var BlobRestProxy|null */
22
-	private $blobClient = null;
23
-	/** @var string|null */
24
-	private $endpoint = null;
25
-	/** @var bool */
26
-	private $autoCreate = false;
15
+    /** @var string */
16
+    private $containerName;
17
+    /** @var string */
18
+    private $accountName;
19
+    /** @var string */
20
+    private $accountKey;
21
+    /** @var BlobRestProxy|null */
22
+    private $blobClient = null;
23
+    /** @var string|null */
24
+    private $endpoint = null;
25
+    /** @var bool */
26
+    private $autoCreate = false;
27 27
 
28
-	/**
29
-	 * @param array $parameters
30
-	 */
31
-	public function __construct(array $parameters) {
32
-		$this->containerName = $parameters['container'];
33
-		$this->accountName = $parameters['account_name'];
34
-		$this->accountKey = $parameters['account_key'];
35
-		if (isset($parameters['endpoint'])) {
36
-			$this->endpoint = $parameters['endpoint'];
37
-		}
38
-		if (isset($parameters['autocreate'])) {
39
-			$this->autoCreate = $parameters['autocreate'];
40
-		}
41
-	}
28
+    /**
29
+     * @param array $parameters
30
+     */
31
+    public function __construct(array $parameters) {
32
+        $this->containerName = $parameters['container'];
33
+        $this->accountName = $parameters['account_name'];
34
+        $this->accountKey = $parameters['account_key'];
35
+        if (isset($parameters['endpoint'])) {
36
+            $this->endpoint = $parameters['endpoint'];
37
+        }
38
+        if (isset($parameters['autocreate'])) {
39
+            $this->autoCreate = $parameters['autocreate'];
40
+        }
41
+    }
42 42
 
43
-	/**
44
-	 * @return BlobRestProxy
45
-	 */
46
-	private function getBlobClient() {
47
-		if (!$this->blobClient) {
48
-			$protocol = $this->endpoint ? substr($this->endpoint, 0, strpos($this->endpoint, ':')) : 'https';
49
-			$connectionString = 'DefaultEndpointsProtocol=' . $protocol . ';AccountName=' . $this->accountName . ';AccountKey=' . $this->accountKey;
50
-			if ($this->endpoint) {
51
-				$connectionString .= ';BlobEndpoint=' . $this->endpoint;
52
-			}
53
-			$this->blobClient = BlobRestProxy::createBlobService($connectionString);
43
+    /**
44
+     * @return BlobRestProxy
45
+     */
46
+    private function getBlobClient() {
47
+        if (!$this->blobClient) {
48
+            $protocol = $this->endpoint ? substr($this->endpoint, 0, strpos($this->endpoint, ':')) : 'https';
49
+            $connectionString = 'DefaultEndpointsProtocol=' . $protocol . ';AccountName=' . $this->accountName . ';AccountKey=' . $this->accountKey;
50
+            if ($this->endpoint) {
51
+                $connectionString .= ';BlobEndpoint=' . $this->endpoint;
52
+            }
53
+            $this->blobClient = BlobRestProxy::createBlobService($connectionString);
54 54
 
55
-			if ($this->autoCreate) {
56
-				try {
57
-					$this->blobClient->createContainer($this->containerName);
58
-				} catch (ServiceException $e) {
59
-					if ($e->getCode() === 409) {
60
-						// already exists
61
-					} else {
62
-						throw $e;
63
-					}
64
-				}
65
-			}
66
-		}
67
-		return $this->blobClient;
68
-	}
55
+            if ($this->autoCreate) {
56
+                try {
57
+                    $this->blobClient->createContainer($this->containerName);
58
+                } catch (ServiceException $e) {
59
+                    if ($e->getCode() === 409) {
60
+                        // already exists
61
+                    } else {
62
+                        throw $e;
63
+                    }
64
+                }
65
+            }
66
+        }
67
+        return $this->blobClient;
68
+    }
69 69
 
70
-	/**
71
-	 * @return string the container or bucket name where objects are stored
72
-	 */
73
-	public function getStorageId() {
74
-		return 'azure::blob::' . $this->containerName;
75
-	}
70
+    /**
71
+     * @return string the container or bucket name where objects are stored
72
+     */
73
+    public function getStorageId() {
74
+        return 'azure::blob::' . $this->containerName;
75
+    }
76 76
 
77
-	/**
78
-	 * @param string $urn the unified resource name used to identify the object
79
-	 * @return resource stream with the read data
80
-	 * @throws \Exception when something goes wrong, message will be logged
81
-	 */
82
-	public function readObject($urn) {
83
-		$blob = $this->getBlobClient()->getBlob($this->containerName, $urn);
84
-		return $blob->getContentStream();
85
-	}
77
+    /**
78
+     * @param string $urn the unified resource name used to identify the object
79
+     * @return resource stream with the read data
80
+     * @throws \Exception when something goes wrong, message will be logged
81
+     */
82
+    public function readObject($urn) {
83
+        $blob = $this->getBlobClient()->getBlob($this->containerName, $urn);
84
+        return $blob->getContentStream();
85
+    }
86 86
 
87
-	public function writeObject($urn, $stream, ?string $mimetype = null) {
88
-		$options = new CreateBlockBlobOptions();
89
-		if ($mimetype) {
90
-			$options->setContentType($mimetype);
91
-		}
92
-		$this->getBlobClient()->createBlockBlob($this->containerName, $urn, $stream, $options);
93
-	}
87
+    public function writeObject($urn, $stream, ?string $mimetype = null) {
88
+        $options = new CreateBlockBlobOptions();
89
+        if ($mimetype) {
90
+            $options->setContentType($mimetype);
91
+        }
92
+        $this->getBlobClient()->createBlockBlob($this->containerName, $urn, $stream, $options);
93
+    }
94 94
 
95
-	/**
96
-	 * @param string $urn the unified resource name used to identify the object
97
-	 * @return void
98
-	 * @throws \Exception when something goes wrong, message will be logged
99
-	 */
100
-	public function deleteObject($urn) {
101
-		$this->getBlobClient()->deleteBlob($this->containerName, $urn);
102
-	}
95
+    /**
96
+     * @param string $urn the unified resource name used to identify the object
97
+     * @return void
98
+     * @throws \Exception when something goes wrong, message will be logged
99
+     */
100
+    public function deleteObject($urn) {
101
+        $this->getBlobClient()->deleteBlob($this->containerName, $urn);
102
+    }
103 103
 
104
-	public function objectExists($urn) {
105
-		try {
106
-			$this->getBlobClient()->getBlobMetadata($this->containerName, $urn);
107
-			return true;
108
-		} catch (ServiceException $e) {
109
-			if ($e->getCode() === 404) {
110
-				return false;
111
-			} else {
112
-				throw $e;
113
-			}
114
-		}
115
-	}
104
+    public function objectExists($urn) {
105
+        try {
106
+            $this->getBlobClient()->getBlobMetadata($this->containerName, $urn);
107
+            return true;
108
+        } catch (ServiceException $e) {
109
+            if ($e->getCode() === 404) {
110
+                return false;
111
+            } else {
112
+                throw $e;
113
+            }
114
+        }
115
+    }
116 116
 
117
-	public function copyObject($from, $to) {
118
-		$this->getBlobClient()->copyBlob($this->containerName, $to, $this->containerName, $from);
119
-	}
117
+    public function copyObject($from, $to) {
118
+        $this->getBlobClient()->copyBlob($this->containerName, $to, $this->containerName, $from);
119
+    }
120 120
 
121
-	public function preSignedUrl(string $urn, \DateTimeInterface $expiration): ?string {
122
-		return null;
123
-	}
121
+    public function preSignedUrl(string $urn, \DateTimeInterface $expiration): ?string {
122
+        return null;
123
+    }
124 124
 }
Please login to merge, or discard this patch.
lib/private/Files/ObjectStore/Swift.php 2 patches
Indentation   +118 added lines, -118 removed lines patch added patch discarded remove patch
@@ -19,122 +19,122 @@
 block discarded – undo
19 19
 const SWIFT_SEGMENT_SIZE = 1073741824; // 1GB
20 20
 
21 21
 class Swift implements IObjectStore {
22
-	/**
23
-	 * @var array
24
-	 */
25
-	private $params;
26
-
27
-	/** @var SwiftFactory */
28
-	private $swiftFactory;
29
-
30
-	public function __construct($params, ?SwiftFactory $connectionFactory = null) {
31
-		$this->swiftFactory = $connectionFactory ?: new SwiftFactory(
32
-			\OC::$server->getMemCacheFactory()->createDistributed('swift::'),
33
-			$params,
34
-			\OC::$server->get(LoggerInterface::class)
35
-		);
36
-		$this->params = $params;
37
-	}
38
-
39
-	/**
40
-	 * @return \OpenStack\ObjectStore\v1\Models\Container
41
-	 * @throws StorageAuthException
42
-	 * @throws \OCP\Files\StorageNotAvailableException
43
-	 */
44
-	private function getContainer() {
45
-		return $this->swiftFactory->getContainer();
46
-	}
47
-
48
-	/**
49
-	 * @return string the container name where objects are stored
50
-	 */
51
-	public function getStorageId() {
52
-		if (isset($this->params['bucket'])) {
53
-			return $this->params['bucket'];
54
-		}
55
-
56
-		return $this->params['container'];
57
-	}
58
-
59
-	public function writeObject($urn, $stream, ?string $mimetype = null) {
60
-		$tmpFile = \OC::$server->getTempManager()->getTemporaryFile('swiftwrite');
61
-		file_put_contents($tmpFile, $stream);
62
-		$handle = fopen($tmpFile, 'rb');
63
-
64
-		if (filesize($tmpFile) < SWIFT_SEGMENT_SIZE) {
65
-			$this->getContainer()->createObject([
66
-				'name' => $urn,
67
-				'stream' => Utils::streamFor($handle),
68
-				'contentType' => $mimetype,
69
-			]);
70
-		} else {
71
-			$this->getContainer()->createLargeObject([
72
-				'name' => $urn,
73
-				'stream' => Utils::streamFor($handle),
74
-				'segmentSize' => SWIFT_SEGMENT_SIZE,
75
-				'contentType' => $mimetype,
76
-			]);
77
-		}
78
-	}
79
-
80
-	/**
81
-	 * @param string $urn the unified resource name used to identify the object
82
-	 * @return resource stream with the read data
83
-	 * @throws \Exception from openstack or GuzzleHttp libs when something goes wrong
84
-	 * @throws NotFoundException if file does not exist
85
-	 */
86
-	public function readObject($urn) {
87
-		try {
88
-			$publicUri = $this->getContainer()->getObject($urn)->getPublicUri();
89
-			$tokenId = $this->swiftFactory->getCachedTokenId();
90
-
91
-			$response = (new Client())->request('GET', $publicUri,
92
-				[
93
-					'stream' => true,
94
-					'headers' => [
95
-						'X-Auth-Token' => $tokenId,
96
-						'Cache-Control' => 'no-cache',
97
-					],
98
-				]
99
-			);
100
-		} catch (BadResponseException $e) {
101
-			if ($e->getResponse() && $e->getResponse()->getStatusCode() === 404) {
102
-				throw new NotFoundException("object $urn not found in object store");
103
-			} else {
104
-				throw $e;
105
-			}
106
-		}
107
-
108
-		return RetryWrapper::wrap($response->getBody()->detach());
109
-	}
110
-
111
-	/**
112
-	 * @param string $urn Unified Resource Name
113
-	 * @return void
114
-	 * @throws \Exception from openstack lib when something goes wrong
115
-	 */
116
-	public function deleteObject($urn) {
117
-		$this->getContainer()->getObject($urn)->delete();
118
-	}
119
-
120
-	/**
121
-	 * @return void
122
-	 * @throws \Exception from openstack lib when something goes wrong
123
-	 */
124
-	public function deleteContainer() {
125
-		$this->getContainer()->delete();
126
-	}
127
-
128
-	public function objectExists($urn) {
129
-		return $this->getContainer()->objectExists($urn);
130
-	}
131
-
132
-	public function copyObject($from, $to) {
133
-		$this->getContainer()->getObject($from)->copy([
134
-			'destination' => $this->getContainer()->name . '/' . $to
135
-		]);
136
-	}
137
-	public function preSignedUrl(string $urn, \DateTimeInterface $expiration): ?string {
138
-		return null;
139
-	}
22
+    /**
23
+     * @var array
24
+     */
25
+    private $params;
26
+
27
+    /** @var SwiftFactory */
28
+    private $swiftFactory;
29
+
30
+    public function __construct($params, ?SwiftFactory $connectionFactory = null) {
31
+        $this->swiftFactory = $connectionFactory ?: new SwiftFactory(
32
+            \OC::$server->getMemCacheFactory()->createDistributed('swift::'),
33
+            $params,
34
+            \OC::$server->get(LoggerInterface::class)
35
+        );
36
+        $this->params = $params;
37
+    }
38
+
39
+    /**
40
+     * @return \OpenStack\ObjectStore\v1\Models\Container
41
+     * @throws StorageAuthException
42
+     * @throws \OCP\Files\StorageNotAvailableException
43
+     */
44
+    private function getContainer() {
45
+        return $this->swiftFactory->getContainer();
46
+    }
47
+
48
+    /**
49
+     * @return string the container name where objects are stored
50
+     */
51
+    public function getStorageId() {
52
+        if (isset($this->params['bucket'])) {
53
+            return $this->params['bucket'];
54
+        }
55
+
56
+        return $this->params['container'];
57
+    }
58
+
59
+    public function writeObject($urn, $stream, ?string $mimetype = null) {
60
+        $tmpFile = \OC::$server->getTempManager()->getTemporaryFile('swiftwrite');
61
+        file_put_contents($tmpFile, $stream);
62
+        $handle = fopen($tmpFile, 'rb');
63
+
64
+        if (filesize($tmpFile) < SWIFT_SEGMENT_SIZE) {
65
+            $this->getContainer()->createObject([
66
+                'name' => $urn,
67
+                'stream' => Utils::streamFor($handle),
68
+                'contentType' => $mimetype,
69
+            ]);
70
+        } else {
71
+            $this->getContainer()->createLargeObject([
72
+                'name' => $urn,
73
+                'stream' => Utils::streamFor($handle),
74
+                'segmentSize' => SWIFT_SEGMENT_SIZE,
75
+                'contentType' => $mimetype,
76
+            ]);
77
+        }
78
+    }
79
+
80
+    /**
81
+     * @param string $urn the unified resource name used to identify the object
82
+     * @return resource stream with the read data
83
+     * @throws \Exception from openstack or GuzzleHttp libs when something goes wrong
84
+     * @throws NotFoundException if file does not exist
85
+     */
86
+    public function readObject($urn) {
87
+        try {
88
+            $publicUri = $this->getContainer()->getObject($urn)->getPublicUri();
89
+            $tokenId = $this->swiftFactory->getCachedTokenId();
90
+
91
+            $response = (new Client())->request('GET', $publicUri,
92
+                [
93
+                    'stream' => true,
94
+                    'headers' => [
95
+                        'X-Auth-Token' => $tokenId,
96
+                        'Cache-Control' => 'no-cache',
97
+                    ],
98
+                ]
99
+            );
100
+        } catch (BadResponseException $e) {
101
+            if ($e->getResponse() && $e->getResponse()->getStatusCode() === 404) {
102
+                throw new NotFoundException("object $urn not found in object store");
103
+            } else {
104
+                throw $e;
105
+            }
106
+        }
107
+
108
+        return RetryWrapper::wrap($response->getBody()->detach());
109
+    }
110
+
111
+    /**
112
+     * @param string $urn Unified Resource Name
113
+     * @return void
114
+     * @throws \Exception from openstack lib when something goes wrong
115
+     */
116
+    public function deleteObject($urn) {
117
+        $this->getContainer()->getObject($urn)->delete();
118
+    }
119
+
120
+    /**
121
+     * @return void
122
+     * @throws \Exception from openstack lib when something goes wrong
123
+     */
124
+    public function deleteContainer() {
125
+        $this->getContainer()->delete();
126
+    }
127
+
128
+    public function objectExists($urn) {
129
+        return $this->getContainer()->objectExists($urn);
130
+    }
131
+
132
+    public function copyObject($from, $to) {
133
+        $this->getContainer()->getObject($from)->copy([
134
+            'destination' => $this->getContainer()->name . '/' . $to
135
+        ]);
136
+    }
137
+    public function preSignedUrl(string $urn, \DateTimeInterface $expiration): ?string {
138
+        return null;
139
+    }
140 140
 }
Please login to merge, or discard this patch.
Spacing   +1 added lines, -1 removed lines patch added patch discarded remove patch
@@ -131,7 +131,7 @@
 block discarded – undo
131 131
 
132 132
 	public function copyObject($from, $to) {
133 133
 		$this->getContainer()->getObject($from)->copy([
134
-			'destination' => $this->getContainer()->name . '/' . $to
134
+			'destination' => $this->getContainer()->name.'/'.$to
135 135
 		]);
136 136
 	}
137 137
 	public function preSignedUrl(string $urn, \DateTimeInterface $expiration): ?string {
Please login to merge, or discard this patch.
lib/private/Files/ObjectStore/StorageObjectStore.php 1 patch
Indentation   +55 added lines, -55 removed lines patch added patch discarded remove patch
@@ -14,68 +14,68 @@
 block discarded – undo
14 14
  * Object store that wraps a storage backend, mostly for testing purposes
15 15
  */
16 16
 class StorageObjectStore implements IObjectStore {
17
-	/** @var IStorage */
18
-	private $storage;
17
+    /** @var IStorage */
18
+    private $storage;
19 19
 
20
-	/**
21
-	 * @param IStorage $storage
22
-	 */
23
-	public function __construct(IStorage $storage) {
24
-		$this->storage = $storage;
25
-	}
20
+    /**
21
+     * @param IStorage $storage
22
+     */
23
+    public function __construct(IStorage $storage) {
24
+        $this->storage = $storage;
25
+    }
26 26
 
27
-	/**
28
-	 * @return string the container or bucket name where objects are stored
29
-	 * @since 7.0.0
30
-	 */
31
-	public function getStorageId(): string {
32
-		return $this->storage->getId();
33
-	}
27
+    /**
28
+     * @return string the container or bucket name where objects are stored
29
+     * @since 7.0.0
30
+     */
31
+    public function getStorageId(): string {
32
+        return $this->storage->getId();
33
+    }
34 34
 
35
-	/**
36
-	 * @param string $urn the unified resource name used to identify the object
37
-	 * @return resource stream with the read data
38
-	 * @throws \Exception when something goes wrong, message will be logged
39
-	 * @since 7.0.0
40
-	 */
41
-	public function readObject($urn) {
42
-		$handle = $this->storage->fopen($urn, 'r');
43
-		if (is_resource($handle)) {
44
-			return $handle;
45
-		}
35
+    /**
36
+     * @param string $urn the unified resource name used to identify the object
37
+     * @return resource stream with the read data
38
+     * @throws \Exception when something goes wrong, message will be logged
39
+     * @since 7.0.0
40
+     */
41
+    public function readObject($urn) {
42
+        $handle = $this->storage->fopen($urn, 'r');
43
+        if (is_resource($handle)) {
44
+            return $handle;
45
+        }
46 46
 
47
-		throw new \Exception();
48
-	}
47
+        throw new \Exception();
48
+    }
49 49
 
50
-	public function writeObject($urn, $stream, ?string $mimetype = null) {
51
-		$handle = $this->storage->fopen($urn, 'w');
52
-		if ($handle) {
53
-			stream_copy_to_stream($stream, $handle);
54
-			fclose($handle);
55
-		} else {
56
-			throw new \Exception();
57
-		}
58
-	}
50
+    public function writeObject($urn, $stream, ?string $mimetype = null) {
51
+        $handle = $this->storage->fopen($urn, 'w');
52
+        if ($handle) {
53
+            stream_copy_to_stream($stream, $handle);
54
+            fclose($handle);
55
+        } else {
56
+            throw new \Exception();
57
+        }
58
+    }
59 59
 
60
-	/**
61
-	 * @param string $urn the unified resource name used to identify the object
62
-	 * @return void
63
-	 * @throws \Exception when something goes wrong, message will be logged
64
-	 * @since 7.0.0
65
-	 */
66
-	public function deleteObject($urn) {
67
-		$this->storage->unlink($urn);
68
-	}
60
+    /**
61
+     * @param string $urn the unified resource name used to identify the object
62
+     * @return void
63
+     * @throws \Exception when something goes wrong, message will be logged
64
+     * @since 7.0.0
65
+     */
66
+    public function deleteObject($urn) {
67
+        $this->storage->unlink($urn);
68
+    }
69 69
 
70
-	public function objectExists($urn) {
71
-		return $this->storage->file_exists($urn);
72
-	}
70
+    public function objectExists($urn) {
71
+        return $this->storage->file_exists($urn);
72
+    }
73 73
 
74
-	public function copyObject($from, $to) {
75
-		$this->storage->copy($from, $to);
76
-	}
74
+    public function copyObject($from, $to) {
75
+        $this->storage->copy($from, $to);
76
+    }
77 77
 
78
-	public function preSignedUrl(string $urn, \DateTimeInterface $expiration): ?string {
79
-		return null;
80
-	}
78
+    public function preSignedUrl(string $urn, \DateTimeInterface $expiration): ?string {
79
+        return null;
80
+    }
81 81
 }
Please login to merge, or discard this patch.
lib/private/Files/ObjectStore/S3ConnectionTrait.php 1 patch
Indentation   +274 added lines, -274 removed lines patch added patch discarded remove patch
@@ -24,278 +24,278 @@
 block discarded – undo
24 24
 use Psr\Log\LoggerInterface;
25 25
 
26 26
 trait S3ConnectionTrait {
27
-	use S3ConfigTrait;
28
-
29
-	protected string $id;
30
-
31
-	protected bool $test;
32
-
33
-	protected ?S3Client $connection = null;
34
-	private ?ICache $existingBucketsCache = null;
35
-	private bool $usePresignedUrl = false;
36
-
37
-	protected function parseParams($params) {
38
-		if (empty($params['bucket'])) {
39
-			throw new \Exception('Bucket has to be configured.');
40
-		}
41
-
42
-		if (isset($params['multibucket']) && $params['multibucket'] === true && isset($params['perBucket'][$params['bucket']])) {
43
-			$params = array_merge($params, $params['perBucket'][$params['bucket']]);
44
-		}
45
-
46
-		$this->id = 'amazon::' . $params['bucket'];
47
-
48
-		$this->test = isset($params['test']);
49
-		$this->bucket = $params['bucket'];
50
-		// Default to 5 like the S3 SDK does
51
-		$this->concurrency = $params['concurrency'] ?? 5;
52
-		$this->proxy = $params['proxy'] ?? false;
53
-		$this->connectTimeout = $params['connect_timeout'] ?? 5;
54
-		$this->timeout = $params['timeout'] ?? 15;
55
-		$this->storageClass = !empty($params['storageClass']) ? $params['storageClass'] : 'STANDARD';
56
-		$this->uploadPartSize = $params['uploadPartSize'] ?? 524288000;
57
-		$this->putSizeLimit = $params['putSizeLimit'] ?? 104857600;
58
-		$this->copySizeLimit = $params['copySizeLimit'] ?? 5242880000;
59
-		$this->useMultipartCopy = (bool)($params['useMultipartCopy'] ?? true);
60
-		$this->retriesMaxAttempts = $params['retriesMaxAttempts'] ?? 5;
61
-		$params['region'] = empty($params['region']) ? 'eu-west-1' : $params['region'];
62
-		$params['hostname'] = empty($params['hostname']) ? 's3.' . $params['region'] . '.amazonaws.com' : $params['hostname'];
63
-		$params['s3-accelerate'] = $params['hostname'] === 's3-accelerate.amazonaws.com' || $params['hostname'] === 's3-accelerate.dualstack.amazonaws.com';
64
-		if (!isset($params['port']) || $params['port'] === '') {
65
-			$params['port'] = (isset($params['use_ssl']) && $params['use_ssl'] === false) ? 80 : 443;
66
-		}
67
-		$params['verify_bucket_exists'] = $params['verify_bucket_exists'] ?? true;
68
-
69
-		if ($params['s3-accelerate']) {
70
-			$params['verify_bucket_exists'] = false;
71
-		}
72
-
73
-		$this->params = $params;
74
-	}
75
-
76
-	public function getBucket() {
77
-		return $this->bucket;
78
-	}
79
-
80
-	public function getProxy() {
81
-		return $this->proxy;
82
-	}
83
-
84
-	/**
85
-	 * Returns the connection
86
-	 *
87
-	 * @return S3Client connected client
88
-	 * @throws \Exception if connection could not be made
89
-	 */
90
-	public function getConnection() {
91
-		if ($this->connection !== null) {
92
-			return $this->connection;
93
-		}
94
-
95
-		if ($this->existingBucketsCache === null) {
96
-			$this->existingBucketsCache = Server::get(ICacheFactory::class)
97
-				->createLocal('s3-bucket-exists-cache');
98
-		}
99
-
100
-		$scheme = (isset($this->params['use_ssl']) && $this->params['use_ssl'] === false) ? 'http' : 'https';
101
-		$base_url = $scheme . '://' . $this->params['hostname'] . ':' . $this->params['port'] . '/';
102
-
103
-		// Adding explicit credential provider to the beginning chain.
104
-		// Including default credential provider (skipping AWS shared config files).
105
-		$provider = CredentialProvider::memoize(
106
-			CredentialProvider::chain(
107
-				$this->paramCredentialProvider(),
108
-				CredentialProvider::defaultProvider(['use_aws_shared_config_files' => false])
109
-			)
110
-		);
111
-
112
-		$this->usePresignedUrl = $this->params['use_presigned_url'] ?? false;
113
-
114
-		$options = [
115
-			'version' => $this->params['version'] ?? 'latest',
116
-			'credentials' => $provider,
117
-			'endpoint' => $base_url,
118
-			'region' => $this->params['region'],
119
-			'use_path_style_endpoint' => isset($this->params['use_path_style']) ? $this->params['use_path_style'] : false,
120
-			'proxy' => isset($this->params['proxy']) ? $this->params['proxy'] : false,
121
-			'signature_provider' => \Aws\or_chain([self::class, 'legacySignatureProvider'], ClientResolver::_default_signature_provider()),
122
-			'csm' => false,
123
-			'use_arn_region' => false,
124
-			'http' => [
125
-				'verify' => $this->getCertificateBundlePath(),
126
-				'connect_timeout' => $this->connectTimeout,
127
-			],
128
-			'use_aws_shared_config_files' => false,
129
-			'retries' => [
130
-				'mode' => 'standard',
131
-				'max_attempts' => $this->retriesMaxAttempts,
132
-			],
133
-		];
134
-
135
-		if ($this->params['s3-accelerate']) {
136
-			$options['use_accelerate_endpoint'] = true;
137
-		} else {
138
-			$options['endpoint'] = $base_url;
139
-		}
140
-
141
-		if (isset($this->params['request_checksum_calculation'])) {
142
-			$options['request_checksum_calculation'] = $this->params['request_checksum_calculation'];
143
-		} else {
144
-			$options['request_checksum_calculation'] = 'when_required';
145
-		}
146
-
147
-		if (isset($this->params['response_checksum_validation'])) {
148
-			$options['response_checksum_validation'] = $this->params['response_checksum_validation'];
149
-		} else {
150
-			$options['response_checksum_validation'] = 'when_required';
151
-		}
152
-
153
-		if ($this->getProxy()) {
154
-			$options['http']['proxy'] = $this->getProxy();
155
-		}
156
-		if (isset($this->params['legacy_auth']) && $this->params['legacy_auth']) {
157
-			$options['signature_version'] = 'v2';
158
-		}
159
-		$this->connection = new S3Client($options);
160
-
161
-		try {
162
-			$logger = Server::get(LoggerInterface::class);
163
-			if (!$this->connection::isBucketDnsCompatible($this->bucket)) {
164
-				$logger->debug('Bucket "' . $this->bucket . '" This bucket name is not dns compatible, it may contain invalid characters.',
165
-					['app' => 'objectstore']);
166
-			}
167
-
168
-			if ($this->params['verify_bucket_exists']) {
169
-				$cacheKey = $this->params['hostname'] . $this->bucket;
170
-				$exist = $this->existingBucketsCache->get($cacheKey) === 1;
171
-
172
-				if (!$exist) {
173
-					if (!$this->connection->doesBucketExist($this->bucket)) {
174
-						try {
175
-							$logger->info('Bucket "' . $this->bucket . '" does not exist - creating it.', ['app' => 'objectstore']);
176
-							if (!$this->connection::isBucketDnsCompatible($this->bucket)) {
177
-								throw new StorageNotAvailableException('The bucket will not be created because the name is not dns compatible, please correct it: ' . $this->bucket);
178
-							}
179
-							$this->connection->createBucket(['Bucket' => $this->bucket]);
180
-							Server::get(IEventDispatcher::class)
181
-								->dispatchTyped(new BucketCreatedEvent(
182
-									$this->bucket,
183
-									$options['endpoint'],
184
-									$options['region'],
185
-									$options['version']
186
-								));
187
-							$this->testTimeout();
188
-						} catch (S3Exception $e) {
189
-							$logger->debug('Invalid remote storage.', [
190
-								'exception' => $e,
191
-								'app' => 'objectstore',
192
-							]);
193
-							if ($e->getAwsErrorCode() !== 'BucketAlreadyOwnedByYou') {
194
-								throw new StorageNotAvailableException('Creation of bucket "' . $this->bucket . '" failed. ' . $e->getMessage());
195
-							}
196
-						}
197
-					}
198
-					$this->existingBucketsCache->set($cacheKey, 1);
199
-				}
200
-			}
201
-
202
-			// google cloud's s3 compatibility doesn't like the EncodingType parameter
203
-			if (strpos($base_url, 'storage.googleapis.com')) {
204
-				$this->connection->getHandlerList()->remove('s3.auto_encode');
205
-			}
206
-		} catch (S3Exception $e) {
207
-			throw new StorageNotAvailableException('S3 service is unable to handle request: ' . $e->getMessage());
208
-		}
209
-
210
-		return $this->connection;
211
-	}
212
-
213
-	/**
214
-	 * when running the tests wait to let the buckets catch up
215
-	 */
216
-	private function testTimeout() {
217
-		if ($this->test) {
218
-			sleep($this->timeout);
219
-		}
220
-	}
221
-
222
-	public static function legacySignatureProvider($version, $service, $region) {
223
-		switch ($version) {
224
-			case 'v2':
225
-			case 's3':
226
-				return new S3Signature();
227
-			default:
228
-				return null;
229
-		}
230
-	}
231
-
232
-	/**
233
-	 * This function creates a credential provider based on user parameter file
234
-	 */
235
-	protected function paramCredentialProvider(): callable {
236
-		return function () {
237
-			$key = empty($this->params['key']) ? null : $this->params['key'];
238
-			$secret = empty($this->params['secret']) ? null : $this->params['secret'];
239
-			$sessionToken = empty($this->params['session_token']) ? null : $this->params['session_token'];
240
-
241
-			if ($key && $secret) {
242
-				return Create::promiseFor(
243
-					// a null sessionToken match the default signature of the constructor
244
-					new Credentials($key, $secret, $sessionToken)
245
-				);
246
-			}
247
-
248
-			$msg = 'Could not find parameters set for credentials in config file.';
249
-			return new RejectedPromise(new CredentialsException($msg));
250
-		};
251
-	}
252
-
253
-	protected function getCertificateBundlePath(): ?string {
254
-		if ((int)($this->params['use_nextcloud_bundle'] ?? '0')) {
255
-			/** @var ICertificateManager $certManager */
256
-			$certManager = Server::get(ICertificateManager::class);
257
-			// since we store the certificate bundles on the primary storage, we can't get the bundle while setting up the primary storage
258
-			if (!isset($this->params['primary_storage'])) {
259
-				return $certManager->getAbsoluteBundlePath();
260
-			} else {
261
-				return $certManager->getDefaultCertificatesBundlePath();
262
-			}
263
-		} else {
264
-			return null;
265
-		}
266
-	}
267
-
268
-	protected function getSSECKey(): ?string {
269
-		if (isset($this->params['sse_c_key']) && !empty($this->params['sse_c_key'])) {
270
-			return $this->params['sse_c_key'];
271
-		}
272
-
273
-		return null;
274
-	}
275
-
276
-	protected function getSSECParameters(bool $copy = false): array {
277
-		$key = $this->getSSECKey();
278
-
279
-		if ($key === null) {
280
-			return [];
281
-		}
282
-
283
-		$rawKey = base64_decode($key);
284
-		if ($copy) {
285
-			return [
286
-				'CopySourceSSECustomerAlgorithm' => 'AES256',
287
-				'CopySourceSSECustomerKey' => $rawKey,
288
-				'CopySourceSSECustomerKeyMD5' => md5($rawKey, true)
289
-			];
290
-		}
291
-		return [
292
-			'SSECustomerAlgorithm' => 'AES256',
293
-			'SSECustomerKey' => $rawKey,
294
-			'SSECustomerKeyMD5' => md5($rawKey, true)
295
-		];
296
-	}
297
-
298
-	public function isUsePresignedUrl(): bool {
299
-		return $this->usePresignedUrl;
300
-	}
27
+    use S3ConfigTrait;
28
+
29
+    protected string $id;
30
+
31
+    protected bool $test;
32
+
33
+    protected ?S3Client $connection = null;
34
+    private ?ICache $existingBucketsCache = null;
35
+    private bool $usePresignedUrl = false;
36
+
37
+    protected function parseParams($params) {
38
+        if (empty($params['bucket'])) {
39
+            throw new \Exception('Bucket has to be configured.');
40
+        }
41
+
42
+        if (isset($params['multibucket']) && $params['multibucket'] === true && isset($params['perBucket'][$params['bucket']])) {
43
+            $params = array_merge($params, $params['perBucket'][$params['bucket']]);
44
+        }
45
+
46
+        $this->id = 'amazon::' . $params['bucket'];
47
+
48
+        $this->test = isset($params['test']);
49
+        $this->bucket = $params['bucket'];
50
+        // Default to 5 like the S3 SDK does
51
+        $this->concurrency = $params['concurrency'] ?? 5;
52
+        $this->proxy = $params['proxy'] ?? false;
53
+        $this->connectTimeout = $params['connect_timeout'] ?? 5;
54
+        $this->timeout = $params['timeout'] ?? 15;
55
+        $this->storageClass = !empty($params['storageClass']) ? $params['storageClass'] : 'STANDARD';
56
+        $this->uploadPartSize = $params['uploadPartSize'] ?? 524288000;
57
+        $this->putSizeLimit = $params['putSizeLimit'] ?? 104857600;
58
+        $this->copySizeLimit = $params['copySizeLimit'] ?? 5242880000;
59
+        $this->useMultipartCopy = (bool)($params['useMultipartCopy'] ?? true);
60
+        $this->retriesMaxAttempts = $params['retriesMaxAttempts'] ?? 5;
61
+        $params['region'] = empty($params['region']) ? 'eu-west-1' : $params['region'];
62
+        $params['hostname'] = empty($params['hostname']) ? 's3.' . $params['region'] . '.amazonaws.com' : $params['hostname'];
63
+        $params['s3-accelerate'] = $params['hostname'] === 's3-accelerate.amazonaws.com' || $params['hostname'] === 's3-accelerate.dualstack.amazonaws.com';
64
+        if (!isset($params['port']) || $params['port'] === '') {
65
+            $params['port'] = (isset($params['use_ssl']) && $params['use_ssl'] === false) ? 80 : 443;
66
+        }
67
+        $params['verify_bucket_exists'] = $params['verify_bucket_exists'] ?? true;
68
+
69
+        if ($params['s3-accelerate']) {
70
+            $params['verify_bucket_exists'] = false;
71
+        }
72
+
73
+        $this->params = $params;
74
+    }
75
+
76
+    public function getBucket() {
77
+        return $this->bucket;
78
+    }
79
+
80
+    public function getProxy() {
81
+        return $this->proxy;
82
+    }
83
+
84
+    /**
85
+     * Returns the connection
86
+     *
87
+     * @return S3Client connected client
88
+     * @throws \Exception if connection could not be made
89
+     */
90
+    public function getConnection() {
91
+        if ($this->connection !== null) {
92
+            return $this->connection;
93
+        }
94
+
95
+        if ($this->existingBucketsCache === null) {
96
+            $this->existingBucketsCache = Server::get(ICacheFactory::class)
97
+                ->createLocal('s3-bucket-exists-cache');
98
+        }
99
+
100
+        $scheme = (isset($this->params['use_ssl']) && $this->params['use_ssl'] === false) ? 'http' : 'https';
101
+        $base_url = $scheme . '://' . $this->params['hostname'] . ':' . $this->params['port'] . '/';
102
+
103
+        // Adding explicit credential provider to the beginning chain.
104
+        // Including default credential provider (skipping AWS shared config files).
105
+        $provider = CredentialProvider::memoize(
106
+            CredentialProvider::chain(
107
+                $this->paramCredentialProvider(),
108
+                CredentialProvider::defaultProvider(['use_aws_shared_config_files' => false])
109
+            )
110
+        );
111
+
112
+        $this->usePresignedUrl = $this->params['use_presigned_url'] ?? false;
113
+
114
+        $options = [
115
+            'version' => $this->params['version'] ?? 'latest',
116
+            'credentials' => $provider,
117
+            'endpoint' => $base_url,
118
+            'region' => $this->params['region'],
119
+            'use_path_style_endpoint' => isset($this->params['use_path_style']) ? $this->params['use_path_style'] : false,
120
+            'proxy' => isset($this->params['proxy']) ? $this->params['proxy'] : false,
121
+            'signature_provider' => \Aws\or_chain([self::class, 'legacySignatureProvider'], ClientResolver::_default_signature_provider()),
122
+            'csm' => false,
123
+            'use_arn_region' => false,
124
+            'http' => [
125
+                'verify' => $this->getCertificateBundlePath(),
126
+                'connect_timeout' => $this->connectTimeout,
127
+            ],
128
+            'use_aws_shared_config_files' => false,
129
+            'retries' => [
130
+                'mode' => 'standard',
131
+                'max_attempts' => $this->retriesMaxAttempts,
132
+            ],
133
+        ];
134
+
135
+        if ($this->params['s3-accelerate']) {
136
+            $options['use_accelerate_endpoint'] = true;
137
+        } else {
138
+            $options['endpoint'] = $base_url;
139
+        }
140
+
141
+        if (isset($this->params['request_checksum_calculation'])) {
142
+            $options['request_checksum_calculation'] = $this->params['request_checksum_calculation'];
143
+        } else {
144
+            $options['request_checksum_calculation'] = 'when_required';
145
+        }
146
+
147
+        if (isset($this->params['response_checksum_validation'])) {
148
+            $options['response_checksum_validation'] = $this->params['response_checksum_validation'];
149
+        } else {
150
+            $options['response_checksum_validation'] = 'when_required';
151
+        }
152
+
153
+        if ($this->getProxy()) {
154
+            $options['http']['proxy'] = $this->getProxy();
155
+        }
156
+        if (isset($this->params['legacy_auth']) && $this->params['legacy_auth']) {
157
+            $options['signature_version'] = 'v2';
158
+        }
159
+        $this->connection = new S3Client($options);
160
+
161
+        try {
162
+            $logger = Server::get(LoggerInterface::class);
163
+            if (!$this->connection::isBucketDnsCompatible($this->bucket)) {
164
+                $logger->debug('Bucket "' . $this->bucket . '" This bucket name is not dns compatible, it may contain invalid characters.',
165
+                    ['app' => 'objectstore']);
166
+            }
167
+
168
+            if ($this->params['verify_bucket_exists']) {
169
+                $cacheKey = $this->params['hostname'] . $this->bucket;
170
+                $exist = $this->existingBucketsCache->get($cacheKey) === 1;
171
+
172
+                if (!$exist) {
173
+                    if (!$this->connection->doesBucketExist($this->bucket)) {
174
+                        try {
175
+                            $logger->info('Bucket "' . $this->bucket . '" does not exist - creating it.', ['app' => 'objectstore']);
176
+                            if (!$this->connection::isBucketDnsCompatible($this->bucket)) {
177
+                                throw new StorageNotAvailableException('The bucket will not be created because the name is not dns compatible, please correct it: ' . $this->bucket);
178
+                            }
179
+                            $this->connection->createBucket(['Bucket' => $this->bucket]);
180
+                            Server::get(IEventDispatcher::class)
181
+                                ->dispatchTyped(new BucketCreatedEvent(
182
+                                    $this->bucket,
183
+                                    $options['endpoint'],
184
+                                    $options['region'],
185
+                                    $options['version']
186
+                                ));
187
+                            $this->testTimeout();
188
+                        } catch (S3Exception $e) {
189
+                            $logger->debug('Invalid remote storage.', [
190
+                                'exception' => $e,
191
+                                'app' => 'objectstore',
192
+                            ]);
193
+                            if ($e->getAwsErrorCode() !== 'BucketAlreadyOwnedByYou') {
194
+                                throw new StorageNotAvailableException('Creation of bucket "' . $this->bucket . '" failed. ' . $e->getMessage());
195
+                            }
196
+                        }
197
+                    }
198
+                    $this->existingBucketsCache->set($cacheKey, 1);
199
+                }
200
+            }
201
+
202
+            // google cloud's s3 compatibility doesn't like the EncodingType parameter
203
+            if (strpos($base_url, 'storage.googleapis.com')) {
204
+                $this->connection->getHandlerList()->remove('s3.auto_encode');
205
+            }
206
+        } catch (S3Exception $e) {
207
+            throw new StorageNotAvailableException('S3 service is unable to handle request: ' . $e->getMessage());
208
+        }
209
+
210
+        return $this->connection;
211
+    }
212
+
213
+    /**
214
+     * when running the tests wait to let the buckets catch up
215
+     */
216
+    private function testTimeout() {
217
+        if ($this->test) {
218
+            sleep($this->timeout);
219
+        }
220
+    }
221
+
222
+    public static function legacySignatureProvider($version, $service, $region) {
223
+        switch ($version) {
224
+            case 'v2':
225
+            case 's3':
226
+                return new S3Signature();
227
+            default:
228
+                return null;
229
+        }
230
+    }
231
+
232
+    /**
233
+     * This function creates a credential provider based on user parameter file
234
+     */
235
+    protected function paramCredentialProvider(): callable {
236
+        return function () {
237
+            $key = empty($this->params['key']) ? null : $this->params['key'];
238
+            $secret = empty($this->params['secret']) ? null : $this->params['secret'];
239
+            $sessionToken = empty($this->params['session_token']) ? null : $this->params['session_token'];
240
+
241
+            if ($key && $secret) {
242
+                return Create::promiseFor(
243
+                    // a null sessionToken match the default signature of the constructor
244
+                    new Credentials($key, $secret, $sessionToken)
245
+                );
246
+            }
247
+
248
+            $msg = 'Could not find parameters set for credentials in config file.';
249
+            return new RejectedPromise(new CredentialsException($msg));
250
+        };
251
+    }
252
+
253
+    protected function getCertificateBundlePath(): ?string {
254
+        if ((int)($this->params['use_nextcloud_bundle'] ?? '0')) {
255
+            /** @var ICertificateManager $certManager */
256
+            $certManager = Server::get(ICertificateManager::class);
257
+            // since we store the certificate bundles on the primary storage, we can't get the bundle while setting up the primary storage
258
+            if (!isset($this->params['primary_storage'])) {
259
+                return $certManager->getAbsoluteBundlePath();
260
+            } else {
261
+                return $certManager->getDefaultCertificatesBundlePath();
262
+            }
263
+        } else {
264
+            return null;
265
+        }
266
+    }
267
+
268
+    protected function getSSECKey(): ?string {
269
+        if (isset($this->params['sse_c_key']) && !empty($this->params['sse_c_key'])) {
270
+            return $this->params['sse_c_key'];
271
+        }
272
+
273
+        return null;
274
+    }
275
+
276
+    protected function getSSECParameters(bool $copy = false): array {
277
+        $key = $this->getSSECKey();
278
+
279
+        if ($key === null) {
280
+            return [];
281
+        }
282
+
283
+        $rawKey = base64_decode($key);
284
+        if ($copy) {
285
+            return [
286
+                'CopySourceSSECustomerAlgorithm' => 'AES256',
287
+                'CopySourceSSECustomerKey' => $rawKey,
288
+                'CopySourceSSECustomerKeyMD5' => md5($rawKey, true)
289
+            ];
290
+        }
291
+        return [
292
+            'SSECustomerAlgorithm' => 'AES256',
293
+            'SSECustomerKey' => $rawKey,
294
+            'SSECustomerKeyMD5' => md5($rawKey, true)
295
+        ];
296
+    }
297
+
298
+    public function isUsePresignedUrl(): bool {
299
+        return $this->usePresignedUrl;
300
+    }
301 301
 }
Please login to merge, or discard this patch.