Container::uploadFromStream()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 3
1
<?php
2
3
namespace ArgentCrusade\Selectel\CloudStorage;
4
5
use Countable;
6
use JsonSerializable;
7
use ArgentCrusade\Selectel\CloudStorage\Traits\MetaData;
8
use ArgentCrusade\Selectel\CloudStorage\Contracts\HasMetaData;
9
use ArgentCrusade\Selectel\CloudStorage\Traits\FilesTransformer;
10
use ArgentCrusade\Selectel\CloudStorage\Contracts\ContainerContract;
11
use ArgentCrusade\Selectel\CloudStorage\Contracts\Api\ApiClientContract;
12
use ArgentCrusade\Selectel\CloudStorage\Contracts\FilesTransformerContract;
13
use ArgentCrusade\Selectel\CloudStorage\Exceptions\ApiRequestFailedException;
14
15
class Container implements ContainerContract, FilesTransformerContract, Countable, JsonSerializable, HasMetaData
16
{
17
    use FilesTransformer, MetaData;
18
19
    /**
20
     * @var \ArgentCrusade\Selectel\CloudStorage\Contracts\Api\ApiClientContract $api
21
     */
22
    protected $api;
23
24
    /**
25
     * File uploader.
26
     *
27
     * @var \ArgentCrusade\Selectel\CloudStorage\Contracts\FileUploaderContract
28
     */
29
    protected $uploader;
30
31
    /**
32
     * Container name.
33
     *
34
     * @var string
35
     */
36
    protected $containerName;
37
38
    /**
39
     * Container data.
40
     *
41
     * @var array
42
     */
43
    protected $data = [];
44
45
    /**
46
     * Determines if container data was already loaded.
47
     *
48
     * @var bool
49
     */
50
    protected $dataLoaded = false;
51
52
    /**
53
     * Container URL. Uses "X-Storage-Url/container-name" by default.
54
     *
55
     * @var string
56
     */
57
    protected $url;
58
59
    /**
60
     * @param \ArgentCrusade\Selectel\CloudStorage\Contracts\Api\ApiClientContract $api
61
     * @param \ArgentCrusade\Selectel\CloudStorage\FileUploader                    $uploader
62
     * @param string                                                               $name
63
     * @param array                                                                $data
64
     */
65
    public function __construct(ApiClientContract $api, FileUploader $uploader, $name, array $data = [])
66
    {
67
        $this->api = $api;
68
        $this->uploader = $uploader;
69
        $this->containerName = $name;
70
        $this->data = $data;
71
        $this->dataLoaded = !empty($data);
72
        $this->url = rtrim($api->storageUrl(), '/').'/'.$name;
73
    }
74
75
    /**
76
     * Returns specific container data.
77
     *
78
     * @param string $key
79
     * @param mixed  $default = null
80
     *
81
     * @return mixed
82
     */
83
    protected function objectData($key, $default = null)
84
    {
85
        if (!$this->dataLoaded) {
86
            $this->loadContainerData();
87
        }
88
89
        return isset($this->data[$key]) ? $this->data[$key] : $default;
90
    }
91
92
    /**
93
     * Container data lazy loader.
94
     *
95
     * @throws \ArgentCrusade\Selectel\CloudStorage\Exceptions\ApiRequestFailedException
96
     */
97
    protected function loadContainerData()
98
    {
99
        // CloudStorage::containers and CloudStorage::getContainer methods did not
100
        // produce any requests to Selectel API, since it may be unnecessary if
101
        // user only wants to upload/manage files or delete container via API.
102
103
        // If user really wants some container info, we will load
104
        // it here on demand. This speeds up application a bit.
105
106
        $response = $this->api->request('HEAD', $this->absolutePath());
107
108
        if ($response->getStatusCode() !== 204) {
109
            throw new ApiRequestFailedException('Container "'.$this->name().'" was not found.');
110
        }
111
112
        $this->dataLoaded = true;
113
114
        // We will extract some headers from response
115
        // and assign them as container data. Also
116
        // we'll try to find any container Meta.
117
118
        $this->data = [
119
            'type' => $response->getHeaderLine('X-Container-Meta-Type'),
120
            'count' => intval($response->getHeaderLine('X-Container-Object-Count')),
121
            'bytes' => intval($response->getHeaderLine('X-Container-Bytes-Used')),
122
            'rx_bytes' => intval($response->getHeaderLine('X-Received-Bytes')),
123
            'tx_bytes' => intval($response->getHeaderLine('X-Transfered-Bytes')),
124
            'meta' => $this->extractMetaData($response),
125
        ];
126
    }
127
128
    /**
129
     * Returns object meta type.
130
     *
131
     * @return string
132
     */
133
    protected function objectMetaType()
134
    {
135
        return 'Container';
136
    }
137
138
    /**
139
     * Absolute path to file from storage root.
140
     *
141
     * @param string $path = '' Relative file path.
142
     *
143
     * @return string
144
     */
145
    protected function absolutePath($path = '')
146
    {
147
        return '/'.$this->name().($path ? '/'.ltrim($path, '/') : '');
148
    }
149
150
    /**
151
     * API Client.
152
     *
153
     * @return \ArgentCrusade\Selectel\CloudStorage\Contracts\Api\ApiClientContract
154
     */
155
    public function apiClient()
156
    {
157
        return $this->api;
158
    }
159
160
    /**
161
     * JSON representation of container.
162
     *
163
     * @return array
164
     */
165
    public function jsonSerialize()
166
    {
167
        return [
168
            'name' => $this->name(),
169
            'type' => $this->type(),
170
            'files_count' => $this->filesCount(),
171
            'size' => $this->size(),
172
            'uploaded_bytes' => $this->uploadedBytes(),
173
            'downloaded_bytes' => $this->downloadedBytes(),
174
        ];
175
    }
176
177
    /**
178
     * Container name.
179
     *
180
     * @return string
181
     */
182
    public function name()
183
    {
184
        return $this->containerName();
185
    }
186
187
    /**
188
     * Container name.
189
     *
190
     * @return string
191
     */
192
    public function containerName()
193
    {
194
        return $this->containerName;
195
    }
196
197
    /**
198
     * Get container's root URL.
199
     *
200
     * @param string $path = ''
201
     *
202
     * @return string
203
     */
204
    public function url($path = '')
205
    {
206
        return $this->url.($path ? '/'.ltrim($path, '/') : '');
207
    }
208
209
    /**
210
     * Container type.
211
     *
212
     * @return string
213
     */
214
    public function type()
215
    {
216
        return $this->objectData('type', 'public');
217
    }
218
219
    /**
220
     * Container files count.
221
     *
222
     * @return int
223
     */
224
    public function filesCount()
225
    {
226
        return intval($this->objectData('count', 0));
227
    }
228
229
    /**
230
     * Container files count.
231
     *
232
     * @return int
233
     */
234
    public function count()
235
    {
236
        return $this->filesCount();
237
    }
238
239
    /**
240
     * Container size in bytes.
241
     *
242
     * @return int
243
     */
244
    public function size()
245
    {
246
        return intval($this->objectData('bytes', 0));
247
    }
248
249
    /**
250
     * Total uploaded (received) bytes.
251
     *
252
     * @return int
253
     */
254
    public function uploadedBytes()
255
    {
256
        return intval($this->objectData('rx_bytes', 0));
257
    }
258
259
    /**
260
     * Total downloaded (transmitted) bytes.
261
     *
262
     * @return int
263
     */
264
    public function downloadedBytes()
265
    {
266
        return intval($this->objectData('tx_bytes', 0));
267
    }
268
269
    /**
270
     * Updates container type.
271
     *
272
     * @param string $type Container type: 'public', 'private' or 'gallery'.
273
     *
274
     * @throws \ArgentCrusade\Selectel\CloudStorage\Exceptions\ApiRequestFailedException
275
     *
276
     * @return string
277
     */
278
    public function setType($type)
279
    {
280
        if ($this->type() === $type) {
281
            return $type;
282
        }
283
284
        // Catch any API Request Exceptions here
285
        // so we can replace exception message
286
        // with more informative one.
287
288
        try {
289
            $this->setMeta(['Type' => $type]);
290
        } catch (ApiRequestFailedException $e) {
291
            throw new ApiRequestFailedException('Unable to set container type to "'.$type.'".', $e->getCode());
292
        }
293
294
        return $this->data['type'] = $type;
295
    }
296
297
    /**
298
     * Set container's root URL.
299
     *
300
     * @param string $url
301
     *
302
     * @return static
303
     */
304
    public function setUrl($url)
305
    {
306
        $this->url = $url;
307
308
        return $this;
309
    }
310
311
    /**
312
     * Creates new Fluent files loader instance.
313
     *
314
     * @return \ArgentCrusade\Selectel\CloudStorage\Contracts\FluentFilesLoaderContract
315
     */
316
    public function files()
317
    {
318
        return new FluentFilesLoader($this->api, $this->name(), $this->absolutePath());
319
    }
320
321
    /**
322
     * Creates new directory.
323
     *
324
     * @param string $name Directory name.
325
     *
326
     * @throws \ArgentCrusade\Selectel\CloudStorage\Exceptions\ApiRequestFailedException
327
     *
328
     * @return string
329
     */
330
    public function createDir($name)
331
    {
332
        $response = $this->api->request('PUT', $this->absolutePath($name), [
333
            'headers' => [
334
                'Content-Type' => 'application/directory',
335
            ],
336
        ]);
337
338
        if ($response->getStatusCode() !== 201) {
339
            throw new ApiRequestFailedException('Unable to create directory "'.$name.'".', $response->getStatusCode());
340
        }
341
342
        return $response->getHeaderLine('ETag');
343
    }
344
345
    /**
346
     * Deletes directory.
347
     *
348
     * @param string $name Directory name.
349
     *
350
     * @throws \ArgentCrusade\Selectel\CloudStorage\Exceptions\ApiRequestFailedException
351
     *
352
     * @return bool
353
     */
354
    public function deleteDir($name)
355
    {
356
        $response = $this->api->request('DELETE', $this->absolutePath($name));
357
358
        if ($response->getStatusCode() !== 204) {
359
            throw new ApiRequestFailedException('Unable to delete directory "'.$name.'".', $response->getStatusCode());
360
        }
361
362
        return true;
363
    }
364
365
    /**
366
     * Uploads file contents from string. Returns ETag header value if upload was successful.
367
     *
368
     * @param string $path           Remote path.
369
     * @param string $contents       File contents.
370
     * @param array  $params         = [] Upload params.
371
     * @param bool   $verifyChecksum = true
372
     *
373
     * @throws \ArgentCrusade\Selectel\CloudStorage\Exceptions\UploadFailedException
374
     *
375
     * @return string
376
     */
377
    public function uploadFromString($path, $contents, array $params = [], $verifyChecksum = true)
378
    {
379
        return $this->uploader->upload($this->api, $this->absolutePath($path), $contents, $params, $verifyChecksum);
380
    }
381
382
    /**
383
     * Uploads file from stream. Returns ETag header value if upload was successful.
384
     *
385
     * @param string   $path     Remote path.
386
     * @param resource $resource Stream resource.
387
     * @param array    $params   = [] Upload params.
388
     *
389
     * @throws \ArgentCrusade\Selectel\CloudStorage\Exceptions\UploadFailedException
390
     *
391
     * @return string
392
     */
393
    public function uploadFromStream($path, $resource, array $params = [])
394
    {
395
        return $this->uploader->upload($this->api, $this->absolutePath($path), $resource, $params, false);
396
    }
397
398
    /**
399
     * Deletes container. Container must be empty in order to perform this operation.
400
     *
401
     * @throws \ArgentCrusade\Selectel\CloudStorage\Exceptions\ApiRequestFailedException
402
     */
403
    public function delete()
404
    {
405
        $response = $this->api->request('DELETE', $this->absolutePath());
406
407
        switch ($response->getStatusCode()) {
408
            case 204:
409
                // Container removed.
410
                return;
411
            case 404:
412
                throw new ApiRequestFailedException('Container "'.$this->name().'" was not found.');
413
            case 409:
414
                throw new ApiRequestFailedException('Container must be empty.');
415
        }
416
    }
417
}
418