Skip to content

Commit

Permalink
Merge branch 'master' into prefix-delimiter
Browse files Browse the repository at this point in the history
  • Loading branch information
mhetreramesh authored Jan 13, 2019
2 parents a2c84cb + 7afde30 commit eb12893
Show file tree
Hide file tree
Showing 5 changed files with 266 additions and 13 deletions.
2 changes: 0 additions & 2 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
language: php

php:
- '5.6'
- '7.0'
- '7.1'
- nightly

Expand Down
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,12 @@ $ composer require gliterd/backblaze-b2
use BackblazeB2\Client;
use BackblazeB2\Bucket;

$client = new Client('accountId', 'applicationKey');
$options = ['auth_timeout_seconds' => seconds];

$client = new Client('accountId', 'applicationKey', $options);
```
_$options_ is optional. If omitted, the default timeout is 12 hours. The timeout allows for a long lived Client object
so that the authorization token does not expire.
## *ApplicationKey is not supported yet, please use MasterKey only*

#### Returns a bucket details
Expand Down
5 changes: 3 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,9 @@
}
],
"require": {
"php": ">=5.5.0",
"guzzlehttp/guzzle": "^6.1"
"php": "^7.1.8",
"guzzlehttp/guzzle": "^6.1",
"nesbot/carbon": "^2.10"
},
"require-dev": {
"phpunit/phpunit": "4.8.*",
Expand Down
234 changes: 226 additions & 8 deletions src/Client.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use BackblazeB2\Exceptions\NotFoundException;
use BackblazeB2\Exceptions\ValidationException;
use BackblazeB2\Http\Client as HttpClient;
use Carbon\Carbon;

class Client
{
Expand All @@ -17,6 +18,9 @@ class Client

protected $client;

protected $reauthTime;
protected $authTimeoutSeconds;

/**
* Client constructor. Accepts the account ID, application key and an optional array of options.
*
Expand All @@ -29,6 +33,15 @@ public function __construct($accountId, $applicationKey, array $options = [])
$this->accountId = $accountId;
$this->applicationKey = $applicationKey;

if (isset($options['auth_timeout_seconds'])) {
$this->authTimeoutSeconds = $options['auth_timeout_seconds'];
} else {
$this->authTimeoutSeconds = 12 * 60 * 60; // 12 hour default
}

// set reauthorize time to force an authentication to take place
$this->reauthTime = Carbon::now('UTC')->subSeconds($this->authTimeoutSeconds * 2);

if (isset($options['client'])) {
$this->client = $options['client'];
} else {
Expand All @@ -55,6 +68,8 @@ public function createBucket(array $options)
);
}

$this->authorizeAccount();

$response = $this->client->request('POST', $this->apiUrl.'/b2_create_bucket', [
'headers' => [
'Authorization' => $this->authToken,
Expand Down Expand Up @@ -90,6 +105,8 @@ public function updateBucket(array $options)
$options['BucketId'] = $this->getBucketIdFromName($options['BucketName']);
}

$this->authorizeAccount();

$response = $this->client->request('POST', $this->apiUrl.'/b2_update_bucket', [
'headers' => [
'Authorization' => $this->authToken,
Expand All @@ -113,6 +130,8 @@ public function listBuckets()
{
$buckets = [];

$this->authorizeAccount();

$response = $this->client->request('POST', $this->apiUrl.'/b2_list_buckets', [
'headers' => [
'Authorization' => $this->authToken,
Expand Down Expand Up @@ -142,6 +161,8 @@ public function deleteBucket(array $options)
$options['BucketId'] = $this->getBucketIdFromName($options['BucketName']);
}

$this->authorizeAccount();

$this->client->request('POST', $this->apiUrl.'/b2_delete_bucket', [
'headers' => [
'Authorization' => $this->authToken,
Expand Down Expand Up @@ -173,6 +194,8 @@ public function upload(array $options)
$options['BucketId'] = $this->getBucketIdFromName($options['BucketName']);
}

$this->authorizeAccount();

// Retrieve the URL that we should be uploading to.
$response = $this->client->request('POST', $this->apiUrl.'/b2_get_upload_url', [
'headers' => [
Expand Down Expand Up @@ -200,7 +223,7 @@ public function upload(array $options)
} else {
// We've been given a simple string body, it's super simple to calculate the hash and size.
$hash = sha1($options['Body']);
$size = mb_strlen($options['Body']);
$size = strlen($options['Body']);
}

if (!isset($options['FileLastModified'])) {
Expand Down Expand Up @@ -261,6 +284,8 @@ public function download(array $options)
$requestUrl = sprintf('%s/file/%s/%s', $this->downloadUrl, $options['BucketName'], $options['FileName']);
}

$this->authorizeAccount();

$response = $this->client->request('GET', $requestUrl, $requestOptions, false);

return isset($options['SaveAs']) ? true : $response;
Expand Down Expand Up @@ -295,7 +320,9 @@ public function listFiles(array $options)
$maxFileCount = 1;
}

// B2 returns, at most, 1000 files per 'page'. Loop through the pages and compile an array of File objects.
$this->authorizeAccount();

// B2 returns, at most, 1000 files per "page". Loop through the pages and compile an array of File objects.
while (true) {
$response = $this->client->request('POST', $this->apiUrl.'/b2_list_file_names', [
'headers' => [
Expand Down Expand Up @@ -361,6 +388,8 @@ public function getFile(array $options)
}
}

$this->authorizeAccount();

$response = $this->client->request('POST', $this->apiUrl.'/b2_get_file_info', [
'headers' => [
'Authorization' => $this->authToken,
Expand Down Expand Up @@ -404,6 +433,8 @@ public function deleteFile(array $options)
$options['FileId'] = $file->getId();
}

$this->authorizeAccount();

$this->client->request('POST', $this->apiUrl.'/b2_delete_file_version', [
'headers' => [
'Authorization' => $this->authToken,
Expand All @@ -424,13 +455,17 @@ public function deleteFile(array $options)
*/
protected function authorizeAccount()
{
$response = $this->client->request('GET', 'https://api.backblazeb2.com/b2api/v1/b2_authorize_account', [
'auth' => [$this->accountId, $this->applicationKey],
]);
if (Carbon::now('UTC')->timestamp > $this->reauthTime->timestamp) {
$response = $this->client->request('GET', 'https://api.backblazeb2.com/b2api/v1/b2_authorize_account', [
'auth' => [$this->accountId, $this->applicationKey],
]);

$this->authToken = $response['authorizationToken'];
$this->apiUrl = $response['apiUrl'].'/b2api/v1';
$this->downloadUrl = $response['downloadUrl'];
$this->authToken = $response['authorizationToken'];
$this->apiUrl = $response['apiUrl'].'/b2api/v1';
$this->downloadUrl = $response['downloadUrl'];
$this->reauthTime = Carbon::now('UTC');
$this->reauthTime->addSeconds($this->authTimeoutSeconds);
}
}

/**
Expand Down Expand Up @@ -482,4 +517,187 @@ protected function getFileIdFromBucketAndFileName($bucketName, $fileName)
}
}
}

/**
* Uploads a large file using b2 large file proceedure.
*
* @param array $options
*
* @return \BackblazeB2\File
*/
public function uploadLargeFile(array $options)
{
if (substr($options['FileName'], 0, 1) === '/') {
$options['FileName'] = ltrim($options['FileName'], '/');
}

//if last char of path is not a "/" then add a "/"
if (substr($options['FilePath'], -1) != '/') {
$options['FilePath'] = $options['FilePath'].'/';
}

if (!isset($options['BucketId']) && isset($options['BucketName'])) {
$options['BucketId'] = $this->getBucketIdFromName($options['BucketName']);
}

if (!isset($options['FileContentType'])) {
$options['FileContentType'] = 'b2/x-auto';
}

$this->authorizeAccount();

// 1) b2_start_large_file, (returns fileId)
$start = $this->startLargeFile($options['FileName'], $options['FileContentType'], $options['BucketId']);

// 2) b2_get_upload_part_url for each thread uploading (takes fileId)
$url = $this->getUploadPartUrl($start['fileId']);

// 3) b2_upload_part for each part of the file
$parts = $this->uploadParts($options['FilePath'].$options['FileName'], $url['uploadUrl'], $url['authorizationToken']);

$sha1s = [];

foreach ($parts as $part) {
$sha1s[] = $part['contentSha1'];
}

// 4) b2_finish_large_file.
return $this->finishLargeFile($start['fileId'], $sha1s);
}

/**
* starts the large file upload process.
*
* @param $fileName
* @param $contentType
* @param $bucketId
*
* @return array
*/
protected function startLargeFile($fileName, $contentType, $bucketId)
{
$response = $this->client->request('POST', $this->apiUrl.'/b2_start_large_file', [
'headers' => [
'Authorization' => $this->authToken,
],
'json' => [
'fileName' => $fileName,
'contentType' => $contentType,
'bucketId' => $bucketId,
],
]);

return $response;
}

/**
* gets the url for the next large file part upload.
*
* @param $fileId
*
* @return array
*/
protected function getUploadPartUrl($fileId)
{
$response = $this->client->request('POST', $this->apiUrl.'/b2_get_upload_part_url', [
'headers' => [
'Authorization' => $this->authToken,
],
'json' => [
'fileId' => $fileId,
],
]);

return $response;
}

/**
* uploads the file as "parts" of 100MB each.
*
* @param $filePath
* @param $uploadUrl
* @param $largeFileAuthToken
*
* @return array
*/
protected function uploadParts($filePath, $uploadUrl, $largeFileAuthToken)
{
$return = [];

$minimum_part_size = 100 * (1000 * 1000);

$local_file_size = filesize($filePath);
$total_bytes_sent = 0;
$bytes_sent_for_part = $minimum_part_size;
$sha1_of_parts = [];
$part_no = 1;
$file_handle = fopen($filePath, 'r');

while ($total_bytes_sent < $local_file_size) {

// Determine the number of bytes to send based on the minimum part size
if (($local_file_size - $total_bytes_sent) < $minimum_part_size) {
$bytes_sent_for_part = ($local_file_size - $total_bytes_sent);
}

// Get a sha1 of the part we are going to send
fseek($file_handle, $total_bytes_sent);
$data_part = fread($file_handle, $bytes_sent_for_part);
array_push($sha1_of_parts, sha1($data_part));
fseek($file_handle, $total_bytes_sent);

$response = $this->client->request('POST', $uploadUrl, [
'headers' => [
'Authorization' => $largeFileAuthToken,
'Content-Length' => $bytes_sent_for_part,
'X-Bz-Part-Number' => $part_no,
'X-Bz-Content-Sha1' => $sha1_of_parts[$part_no - 1],
],
'body' => $data_part,
]);

$return[] = $response;

// Prepare for the next iteration of the loop
$part_no++;
$total_bytes_sent = $bytes_sent_for_part + $total_bytes_sent;
}

fclose($file_handle);

return $return;
}

/**
* finishes the large file upload proceedure.
*
* @param $fileId
* @param array $sha1s
*
* @return File
*/
protected function finishLargeFile($fileId, array $sha1s)
{
$response = $this->client->request('POST', $this->apiUrl.'/b2_finish_large_file', [
'headers' => [
'Authorization' => $this->authToken,
],
'json' => [
'fileId' => $fileId,
'partSha1Array' => $sha1s,
],
]);

return new File(
$response['fileId'],
$response['fileName'],
$response['contentSha1'],
$response['contentLength'],
$response['contentType'],
$response['fileInfo'],
$response['bucketId'],
$response['action'],
$response['uploadTimestamp']
);
}
}
Loading

0 comments on commit eb12893

Please sign in to comment.