Completed
Push — master ( ddb51c...e53df5 )
by Ramesh
05:40 queued 03:35
created

Client::deleteFile()   A

Complexity

Conditions 5
Paths 4

Size

Total Lines 25
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 5.1158

Importance

Changes 0
Metric Value
eloc 13
dl 0
loc 25
c 0
b 0
f 0
ccs 10
cts 12
cp 0.8333
rs 9.5222
cc 5
nc 4
nop 1
crap 5.1158
1
<?php
2
3
namespace BackblazeB2;
4
5
use BackblazeB2\Exceptions\NotFoundException;
6
use BackblazeB2\Exceptions\ValidationException;
7
use BackblazeB2\Http\Client as HttpClient;
8
9
class Client
10
{
11
    protected $accountId;
12
    protected $applicationKey;
13
14
    protected $authToken;
15
    protected $apiUrl;
16
    protected $downloadUrl;
17
18
    protected $client;
19
20
    /**
21
     * Client constructor. Accepts the account ID, application key and an optional array of options.
22
     *
23
     * @param $accountId
24
     * @param $applicationKey
25
     * @param array $options
26
     */
27 27
    public function __construct($accountId, $applicationKey, array $options = [])
28
    {
29 27
        $this->accountId = $accountId;
30 27
        $this->applicationKey = $applicationKey;
31
32 27
        if (isset($options['client'])) {
33 27
            $this->client = $options['client'];
34
        } else {
35
            $this->client = new HttpClient(['exceptions' => false]);
36
        }
37
38 27
        $this->authorizeAccount();
39 27
    }
40
41
    /**
42
     * Create a bucket with the given name and type.
43
     *
44
     * @param array $options
45
     *
46
     * @throws ValidationException
47
     *
48
     * @return Bucket
49
     */
50 4
    public function createBucket(array $options)
51
    {
52 4
        if (!in_array($options['BucketType'], [Bucket::TYPE_PUBLIC, Bucket::TYPE_PRIVATE])) {
53 1
            throw new ValidationException(
54 1
                sprintf('Bucket type must be %s or %s', Bucket::TYPE_PRIVATE, Bucket::TYPE_PUBLIC)
55
            );
56
        }
57
58 3
        $response = $this->client->request('POST', $this->apiUrl.'/b2_create_bucket', [
59
            'headers' => [
60 3
                'Authorization' => $this->authToken,
61
            ],
62
            'json' => [
63 3
                'accountId'  => $this->accountId,
64 3
                'bucketName' => $options['BucketName'],
65 3
                'bucketType' => $options['BucketType'],
66
            ],
67
        ]);
68
69 2
        return new Bucket($response['bucketId'], $response['bucketName'], $response['bucketType']);
70
    }
71
72
    /**
73
     * Updates the type attribute of a bucket by the given ID.
74
     *
75
     * @param array $options
76
     *
77
     * @throws ValidationException
78
     *
79
     * @return Bucket
80
     */
81 2
    public function updateBucket(array $options)
82
    {
83 2
        if (!in_array($options['BucketType'], [Bucket::TYPE_PUBLIC, Bucket::TYPE_PRIVATE])) {
84
            throw new ValidationException(
85
                sprintf('Bucket type must be %s or %s', Bucket::TYPE_PRIVATE, Bucket::TYPE_PUBLIC)
86
            );
87
        }
88
89 2
        if (!isset($options['BucketId']) && isset($options['BucketName'])) {
90
            $options['BucketId'] = $this->getBucketIdFromName($options['BucketName']);
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $options['BucketId'] is correct as $this->getBucketIdFromNa...$options['BucketName']) targeting BackblazeB2\Client::getBucketIdFromName() seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
91
        }
92
93 2
        $response = $this->client->request('POST', $this->apiUrl.'/b2_update_bucket', [
94
            'headers' => [
95 2
                'Authorization' => $this->authToken,
96
            ],
97
            'json' => [
98 2
                'accountId'  => $this->accountId,
99 2
                'bucketId'   => $options['BucketId'],
100 2
                'bucketType' => $options['BucketType'],
101
            ],
102
        ]);
103
104 2
        return new Bucket($response['bucketId'], $response['bucketName'], $response['bucketType']);
105
    }
106
107
    /**
108
     * Returns a list of bucket objects representing the buckets on the account.
109
     *
110
     * @return array
111
     */
112 2
    public function listBuckets()
113
    {
114 2
        $buckets = [];
115
116 2
        $response = $this->client->request('POST', $this->apiUrl.'/b2_list_buckets', [
117
            'headers' => [
118 2
                'Authorization' => $this->authToken,
119
            ],
120
            'json' => [
121 2
                'accountId' => $this->accountId,
122
            ],
123
        ]);
124
125 2
        foreach ($response['buckets'] as $bucket) {
126 1
            $buckets[] = new Bucket($bucket['bucketId'], $bucket['bucketName'], $bucket['bucketType']);
127
        }
128
129 2
        return $buckets;
130
    }
131
132
    /**
133
     * Deletes the bucket identified by its ID.
134
     *
135
     * @param array $options
136
     *
137
     * @return bool
138
     */
139 3
    public function deleteBucket(array $options)
140
    {
141 3
        if (!isset($options['BucketId']) && isset($options['BucketName'])) {
142
            $options['BucketId'] = $this->getBucketIdFromName($options['BucketName']);
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $options['BucketId'] is correct as $this->getBucketIdFromNa...$options['BucketName']) targeting BackblazeB2\Client::getBucketIdFromName() seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
143
        }
144
145 3
        $this->client->request('POST', $this->apiUrl.'/b2_delete_bucket', [
146
            'headers' => [
147 3
                'Authorization' => $this->authToken,
148
            ],
149
            'json' => [
150 3
                'accountId' => $this->accountId,
151 3
                'bucketId'  => $options['BucketId'],
152
            ],
153
        ]);
154
155 1
        return true;
156
    }
157
158
    /**
159
     * Uploads a file to a bucket and returns a File object.
160
     *
161
     * @param array $options
162
     *
163
     * @return File
164
     */
165 3
    public function upload(array $options)
166
    {
167
        // Clean the path if it starts with /.
168 3
        if (substr($options['FileName'], 0, 1) === '/') {
169
            $options['FileName'] = ltrim($options['FileName'], '/');
170
        }
171
172 3
        if (!isset($options['BucketId']) && isset($options['BucketName'])) {
173
            $options['BucketId'] = $this->getBucketIdFromName($options['BucketName']);
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $options['BucketId'] is correct as $this->getBucketIdFromNa...$options['BucketName']) targeting BackblazeB2\Client::getBucketIdFromName() seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
174
        }
175
176
        // Retrieve the URL that we should be uploading to.
177 3
        $response = $this->client->request('POST', $this->apiUrl.'/b2_get_upload_url', [
178
            'headers' => [
179 3
                'Authorization' => $this->authToken,
180
            ],
181
            'json' => [
182 3
                'bucketId' => $options['BucketId'],
183
            ],
184
        ]);
185
186 3
        $uploadEndpoint = $response['uploadUrl'];
187 3
        $uploadAuthToken = $response['authorizationToken'];
188
189 3
        if (is_resource($options['Body'])) {
190
            // We need to calculate the file's hash incrementally from the stream.
191 1
            $context = hash_init('sha1');
192 1
            hash_update_stream($context, $options['Body']);
193 1
            $hash = hash_final($context);
194
195
            // Similarly, we have to use fstat to get the size of the stream.
196 1
            $size = fstat($options['Body'])['size'];
197
198
            // Rewind the stream before passing it to the HTTP client.
199 1
            rewind($options['Body']);
200
        } else {
201
            // We've been given a simple string body, it's super simple to calculate the hash and size.
202 2
            $hash = sha1($options['Body']);
203 2
            $size = mb_strlen($options['Body']);
204
        }
205
206 3
        if (!isset($options['FileLastModified'])) {
207 2
            $options['FileLastModified'] = round(microtime(true) * 1000);
208
        }
209
210 3
        if (!isset($options['FileContentType'])) {
211 2
            $options['FileContentType'] = 'b2/x-auto';
212
        }
213
214 3
        $response = $this->client->request('POST', $uploadEndpoint, [
215
            'headers' => [
216 3
                'Authorization'                      => $uploadAuthToken,
217 3
                'Content-Type'                       => $options['FileContentType'],
218 3
                'Content-Length'                     => $size,
219 3
                'X-Bz-File-Name'                     => $options['FileName'],
220 3
                'X-Bz-Content-Sha1'                  => $hash,
221 3
                'X-Bz-Info-src_last_modified_millis' => $options['FileLastModified'],
222
            ],
223 3
            'body' => $options['Body'],
224
        ]);
225
226 3
        return new File(
227 3
            $response['fileId'],
228 3
            $response['fileName'],
229 3
            $response['contentSha1'],
230 3
            $response['contentLength'],
231 3
            $response['contentType'],
232 3
            $response['fileInfo']
233
        );
234
    }
235
236
    /**
237
     * Download a file from a B2 bucket.
238
     *
239
     * @param array $options
240
     *
241
     * @return bool|mixed|string
242
     */
243 6
    public function download(array $options)
244
    {
245 6
        $requestUrl = null;
246
        $requestOptions = [
247 6
            'headers' => [
248 6
                'Authorization' => $this->authToken,
249
            ],
250 6
            'sink' => isset($options['SaveAs']) ? $options['SaveAs'] : null,
251
        ];
252
253 6
        if (isset($options['FileId'])) {
254 3
            $requestOptions['query'] = ['fileId' => $options['FileId']];
255 3
            $requestUrl = $this->downloadUrl.'/b2api/v1/b2_download_file_by_id';
256
        } else {
257 3
            if (!isset($options['BucketName']) && isset($options['BucketId'])) {
258
                $options['BucketName'] = $this->getBucketNameFromId($options['BucketId']);
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $options['BucketName'] is correct as $this->getBucketNameFromId($options['BucketId']) targeting BackblazeB2\Client::getBucketNameFromId() seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
259
            }
260
261 3
            $requestUrl = sprintf('%s/file/%s/%s', $this->downloadUrl, $options['BucketName'], $options['FileName']);
262
        }
263
264 6
        $response = $this->client->request('GET', $requestUrl, $requestOptions, false);
265
266 4
        return isset($options['SaveAs']) ? true : $response;
267
    }
268
269
    /**
270
     * Retrieve a collection of File objects representing the files stored inside a bucket.
271
     *
272
     * @param array $options
273
     *
274
     * @return array
275
     */
276 2
    public function listFiles(array $options)
277
    {
278
        // if FileName is set, we only attempt to retrieve information about that single file.
279 2
        $fileName = !empty($options['FileName']) ? $options['FileName'] : null;
280
281 2
        $nextFileName = null;
282 2
        $maxFileCount = 1000;
283 2
        $files = [];
284
285 2
        if (!isset($options['BucketId']) && isset($options['BucketName'])) {
286
            $options['BucketId'] = $this->getBucketIdFromName($options['BucketName']);
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $options['BucketId'] is correct as $this->getBucketIdFromNa...$options['BucketName']) targeting BackblazeB2\Client::getBucketIdFromName() seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
287
        }
288
289 2
        if ($fileName) {
290
            $nextFileName = $fileName;
291
            $maxFileCount = 1;
292
        }
293
294
        // B2 returns, at most, 1000 files per "page". Loop through the pages and compile an array of File objects.
295 2
        while (true) {
296 2
            $response = $this->client->request('POST', $this->apiUrl.'/b2_list_file_names', [
297
                'headers' => [
298 2
                    'Authorization' => $this->authToken,
299
                ],
300
                'json' => [
301 2
                    'bucketId'      => $options['BucketId'],
302 2
                    'startFileName' => $nextFileName,
303 2
                    'maxFileCount'  => $maxFileCount,
304
                ],
305
            ]);
306
307 2
            foreach ($response['files'] as $file) {
308
                // if we have a file name set, only retrieve information if the file name matches
309 1
                if (!$fileName || ($fileName === $file['fileName'])) {
310 1
                    $files[] = new File($file['fileId'], $file['fileName'], null, $file['size']);
311
                }
312
            }
313
314 2
            if ($fileName || $response['nextFileName'] === null) {
315
                // We've got all the files - break out of loop.
316 2
                break;
317
            }
318
319 1
            $nextFileName = $response['nextFileName'];
320
        }
321
322 2
        return $files;
323
    }
324
325
    /**
326
     * Test whether a file exists in B2 for the given bucket.
327
     *
328
     * @param array $options
329
     *
330
     * @return bool
331
     */
332
    public function fileExists(array $options)
333
    {
334
        $files = $this->listFiles($options);
335
336
        return !empty($files);
337
    }
338
339
    /**
340
     * Returns a single File object representing a file stored on B2.
341
     *
342
     * @param array $options
343
     *
344
     * @throws NotFoundException If no file id was provided and BucketName + FileName does not resolve to a file, a NotFoundException is thrown.
345
     *
346
     * @return File
347
     */
348 4
    public function getFile(array $options)
349
    {
350 4
        if (!isset($options['FileId']) && isset($options['BucketName']) && isset($options['FileName'])) {
351
            $options['FileId'] = $this->getFileIdFromBucketAndFileName($options['BucketName'], $options['FileName']);
352
353
            if (!$options['FileId']) {
354
                throw new NotFoundException();
355
            }
356
        }
357
358 4
        $response = $this->client->request('POST', $this->apiUrl.'/b2_get_file_info', [
359
            'headers' => [
360 4
                'Authorization' => $this->authToken,
361
            ],
362
            'json' => [
363 4
                'fileId' => $options['FileId'],
364
            ],
365
        ]);
366
367 3
        return new File(
368 3
            $response['fileId'],
369 3
            $response['fileName'],
370 3
            $response['contentSha1'],
371 3
            $response['contentLength'],
372 3
            $response['contentType'],
373 3
            $response['fileInfo'],
374 3
            $response['bucketId'],
375 3
            $response['action'],
376 3
            $response['uploadTimestamp']
377
        );
378
    }
379
380
    /**
381
     * Deletes the file identified by ID from Backblaze B2.
382
     *
383
     * @param array $options
384
     *
385
     * @return bool
386
     */
387 3
    public function deleteFile(array $options)
388
    {
389 3
        if (!isset($options['FileName'])) {
390 2
            $file = $this->getFile($options);
391
392 2
            $options['FileName'] = $file->getName();
393
        }
394
395 3
        if (!isset($options['FileId']) && isset($options['BucketName']) && isset($options['FileName'])) {
396
            $file = $this->getFile($options);
397
398
            $options['FileId'] = $file->getId();
399
        }
400
401 3
        $this->client->request('POST', $this->apiUrl.'/b2_delete_file_version', [
402
            'headers' => [
403 3
                'Authorization' => $this->authToken,
404
            ],
405
            'json' => [
406 3
                'fileName' => $options['FileName'],
407 3
                'fileId'   => $options['FileId'],
408
            ],
409
        ]);
410
411 2
        return true;
412
    }
413
414
    /**
415
     * Authorize the B2 account in order to get an auth token and API/download URLs.
416
     *
417
     * @throws \Exception
418
     */
419 27
    protected function authorizeAccount()
420
    {
421 27
        $response = $this->client->request('GET', 'https://api.backblazeb2.com/b2api/v1/b2_authorize_account', [
422 27
            'auth' => [$this->accountId, $this->applicationKey],
423
        ]);
424
425 27
        $this->authToken = $response['authorizationToken'];
426 27
        $this->apiUrl = $response['apiUrl'].'/b2api/v1';
427 27
        $this->downloadUrl = $response['downloadUrl'];
428 27
    }
429
430
    /**
431
     * Maps the provided bucket name to the appropriate bucket ID.
432
     *
433
     * @param $name
434
     *
435
     * @return null
436
     */
437
    protected function getBucketIdFromName($name)
438
    {
439
        $buckets = $this->listBuckets();
440
441
        foreach ($buckets as $bucket) {
442
            if ($bucket->getName() === $name) {
443
                return $bucket->getId();
444
            }
445
        }
446
    }
447
448
    /**
449
     * Maps the provided bucket ID to the appropriate bucket name.
450
     *
451
     * @param $id
452
     *
453
     * @return null
454
     */
455
    protected function getBucketNameFromId($id)
456
    {
457
        $buckets = $this->listBuckets();
458
459
        foreach ($buckets as $bucket) {
460
            if ($bucket->getId() === $id) {
461
                return $bucket->getName();
462
            }
463
        }
464
    }
465
466
    protected function getFileIdFromBucketAndFileName($bucketName, $fileName)
467
    {
468
        $files = $this->listFiles([
469
            'BucketName' => $bucketName,
470
            'FileName'   => $fileName,
471
        ]);
472
473
        foreach ($files as $file) {
474
            if ($file->getName() === $fileName) {
475
                return $file->getId();
476
            }
477
        }
478
    }
479
}
480