Completed
Push — master ( 2dc3a3...27b2df )
by Timur
02:48
created

Container::objectData()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

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