Completed
Push — master ( 008ed8...101863 )
by Timur
02:20
created

Container::jsonSerialize()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 11
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

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