Pull 135: Backblaze B2 storage driver #138
@ -2,7 +2,6 @@
|
||||
|
||||
namespace App\AlbumSources;
|
||||
|
||||
use App\Album;
|
||||
use App\Photo;
|
||||
use App\Services\BackblazeB2Service;
|
||||
use App\Storage;
|
||||
@ -10,15 +9,26 @@ use Guzzle\Http\EntityBody;
|
||||
|
||||
class BackblazeB2Source extends AlbumSourceBase implements IAlbumSource
|
||||
{
|
||||
const BUCKET_TYPE_AUTO = 0;
|
||||
const BUCKET_TYPE_PRIVATE = 1;
|
||||
const BUCKET_TYPE_PUBLIC = 2;
|
||||
|
||||
/**
|
||||
* @var BackblazeB2Service
|
||||
*/
|
||||
private $backblaze;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->backblaze = new BackblazeB2Service();
|
||||
}
|
||||
/**
|
||||
* Type of bucket which determines what type of URLs to generate to images.
|
||||
* @var integer
|
||||
*/
|
||||
private $bucketType;
|
||||
|
||||
/**
|
||||
* Token used to download files from a private bucket.
|
||||
* @var string
|
||||
*/
|
||||
private $downloadToken;
|
||||
|
||||
/**
|
||||
* Deletes an entire album's media contents.
|
||||
@ -68,7 +78,31 @@ class BackblazeB2Source extends AlbumSourceBase implements IAlbumSource
|
||||
*/
|
||||
public function getUrlToPhoto(Photo $photo, $thumbnail = null)
|
||||
{
|
||||
// TODO: Implement getUrlToPhoto() method.
|
||||
$client = $this->getClient();
|
||||
$storagePathToFile = $this->getPathToPhoto($photo, $thumbnail);
|
||||
|
||||
/*
|
||||
* From https://www.backblaze.com/b2/docs/b2_download_file_by_name.html:
|
||||
* The base URL to use comes from the b2_authorize_account call, and looks something like
|
||||
* https://f345.backblazeb2.com. The "f" in the URL stands for "file", and the number is the cluster
|
||||
* number containing your account. To this base, you add "file/", your bucket name, a "/", and then the
|
||||
* name of the file. The file name may itself include more "/" characters.
|
||||
*/
|
||||
$fileDownloadUrl = sprintf('%s/file/%s/%s', $client->getDownloadUrl(), $this->configuration->container_name, $storagePathToFile);
|
||||
|
||||
switch ($this->bucketType)
|
||||
{
|
||||
case self::BUCKET_TYPE_PRIVATE:
|
||||
if (is_null($this->downloadToken))
|
||||
{
|
||||
$this->downloadToken = $client->getDownloadAuthToken();
|
||||
}
|
||||
|
||||
return sprintf('%s?Authorization=%s', $fileDownloadUrl, $this->downloadToken);
|
||||
|
||||
case self::BUCKET_TYPE_PUBLIC:
|
||||
return $fileDownloadUrl;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -88,15 +122,39 @@ class BackblazeB2Source extends AlbumSourceBase implements IAlbumSource
|
||||
public function setConfiguration(Storage $configuration)
|
||||
{
|
||||
parent::setConfiguration($configuration);
|
||||
|
||||
$this->backblaze->setCredentials(decrypt($configuration->access_key), decrypt($configuration->secret_key));
|
||||
}
|
||||
|
||||
private function getClient()
|
||||
{
|
||||
if (is_null($this->backblaze))
|
||||
{
|
||||
$this->backblaze = new BackblazeB2Service();
|
||||
$this->backblaze->setCredentials(decrypt($this->configuration->access_key), decrypt($this->configuration->secret_key));
|
||||
$this->backblaze->authorizeAccount();
|
||||
$this->backblaze->setBucketName($this->configuration->container_name);
|
||||
|
||||
if (intval($this->configuration->b2_bucket_type) == self::BUCKET_TYPE_AUTO)
|
||||
{
|
||||
/* Auto-detect the type of bucket in use on B2 */
|
||||
|
||||
switch ($this->backblaze->getBucketType())
|
||||
{
|
||||
case 'allPrivate':
|
||||
$this->configuration->b2_bucket_type = self::BUCKET_TYPE_PRIVATE;
|
||||
break;
|
||||
|
||||
case 'allPublic':
|
||||
$this->configuration->b2_bucket_type = self::BUCKET_TYPE_PUBLIC;
|
||||
break;
|
||||
}
|
||||
|
||||
$this->configuration->save();
|
||||
}
|
||||
|
||||
// Set the bucket type
|
||||
$this->bucketType = $this->configuration->b2_bucket_type;
|
||||
}
|
||||
|
||||
return $this->backblaze;
|
||||
}
|
||||
|
||||
|
@ -88,7 +88,8 @@ class StorageController extends Controller
|
||||
'container_name',
|
||||
'cdn_url',
|
||||
'access_key',
|
||||
'secret_key'
|
||||
'secret_key',
|
||||
'b2_bucket_type'
|
||||
]));
|
||||
$storage->is_active = true;
|
||||
$storage->is_default = (strtolower($request->get('is_default')) == 'on');
|
||||
@ -217,7 +218,8 @@ class StorageController extends Controller
|
||||
'container_name',
|
||||
'cdn_url',
|
||||
'access_key',
|
||||
'secret_key'
|
||||
'secret_key',
|
||||
'b2_bucket_type'
|
||||
]));
|
||||
$storage->is_active = (strtolower($request->get('is_active')) == 'on');
|
||||
$storage->is_default = (strtolower($request->get('is_default')) == 'on');
|
||||
|
@ -10,14 +10,46 @@ class BackblazeB2Service
|
||||
*/
|
||||
private $accountApiUrl;
|
||||
|
||||
/**
|
||||
* ID of the account in Backblaze B2.
|
||||
* @var string
|
||||
*/
|
||||
private $accountID;
|
||||
|
||||
/**
|
||||
* The base URL for public access to the account's files.
|
||||
* @var string
|
||||
*/
|
||||
private $downloadUrl;
|
||||
|
||||
/**
|
||||
* Authorisation header for authenticating to the API.
|
||||
* @var string
|
||||
*/
|
||||
private $authHeader;
|
||||
|
||||
/**
|
||||
* Authorisation token for accessing the API post-authentication.
|
||||
* @var string
|
||||
*/
|
||||
private $authToken;
|
||||
|
||||
/**
|
||||
* ID of the bucket.
|
||||
* @var string
|
||||
*/
|
||||
private $bucketId;
|
||||
|
||||
/**
|
||||
* Type of the bucket.
|
||||
* @var integer
|
||||
*/
|
||||
private $bucketType;
|
||||
|
||||
/**
|
||||
* Configuration related to the Backblaze B2 service.
|
||||
* @var \Illuminate\Config\Repository|mixed
|
||||
*/
|
||||
private $config;
|
||||
|
||||
public function __construct()
|
||||
@ -39,12 +71,41 @@ class BackblazeB2Service
|
||||
$this->authToken = $result->authorizationToken;
|
||||
$this->accountApiUrl = $result->apiUrl;
|
||||
$this->accountID = $result->accountId;
|
||||
$this->downloadUrl = $result->downloadUrl;
|
||||
}
|
||||
}
|
||||
|
||||
public function getBucketType()
|
||||
{
|
||||
return $this->bucketType;
|
||||
}
|
||||
|
||||
public function getDownloadAuthToken()
|
||||
{
|
||||
$result = $this->sendRequest(
|
||||
sprintf('%s/b2api/v2/b2_get_download_authorization', $this->accountApiUrl),
|
||||
'POST',
|
||||
[
|
||||
'bucketId' => $this->bucketId,
|
||||
'validDurationInSeconds' => intval($this->config['download_token_lifetime']),
|
||||
'fileNamePrefix' => ''
|
||||
]
|
||||
);
|
||||
|
||||
return $result->authorizationToken;
|
||||
}
|
||||
|
||||
public function getDownloadUrl()
|
||||
{
|
||||
return $this->downloadUrl;
|
||||
}
|
||||
|
||||
public function setBucketName($bucketName)
|
||||
{
|
||||
$this->bucketId = $this->getBucketIdFromName($bucketName);
|
||||
$bucketDetails = $this->getBucketDetailsFromName($bucketName);
|
||||
|
||||
$this->bucketId = $bucketDetails->bucketId;
|
||||
$this->bucketType = $bucketDetails->bucketType;
|
||||
}
|
||||
|
||||
public function setCredentials($applicationKeyID, $applicationKey)
|
||||
@ -78,12 +139,17 @@ class BackblazeB2Service
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, $fileContents);
|
||||
|
||||
$result = curl_exec($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
|
||||
var_dump($result);
|
||||
exit();
|
||||
if ($httpCode != 200 && $httpCode != 304)
|
||||
{
|
||||
throw new \Exception(sprintf('Exception from Backblaze B2: %s', $result));
|
||||
}
|
||||
|
||||
private function getBucketIdFromName($bucketName)
|
||||
curl_close($ch);
|
||||
}
|
||||
|
||||
private function getBucketDetailsFromName($bucketName)
|
||||
{
|
||||
$result = $this->sendRequest(
|
||||
sprintf('%s/b2api/v2/b2_list_buckets', $this->accountApiUrl),
|
||||
@ -96,7 +162,7 @@ class BackblazeB2Service
|
||||
|
||||
if (isset($result->buckets) && is_array($result->buckets) && count($result->buckets) >= 1)
|
||||
{
|
||||
return $result->buckets[0]->bucketId;
|
||||
return $result->buckets[0];
|
||||
}
|
||||
|
||||
throw new \Exception(sprintf('The bucket \'%s\' was not found or your API key does not have access.', $bucketName));
|
||||
|
@ -30,7 +30,8 @@ class Storage extends Model
|
||||
'container_name',
|
||||
'cdn_url',
|
||||
'access_key',
|
||||
'secret_key'
|
||||
'secret_key',
|
||||
'b2_bucket_type'
|
||||
];
|
||||
|
||||
public function albums()
|
||||
|
@ -15,7 +15,8 @@ return [
|
||||
*/
|
||||
|
||||
'backblaze_b2' => [
|
||||
'auth_url' => 'https://api.backblazeb2.com/b2api/v2/b2_authorize_account'
|
||||
'auth_url' => 'https://api.backblazeb2.com/b2api/v2/b2_authorize_account',
|
||||
'download_token_lifetime' => 300
|
||||
],
|
||||
|
||||
'gitea' => [
|
||||
|
@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class AddBackblazeStorageColumns extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('storages', function (Blueprint $table)
|
||||
{
|
||||
$table->tinyInteger('b2_bucket_type')->default(0);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::table('storages', function (Blueprint $table)
|
||||
{
|
||||
$table->dropColumn('b2_bucket_type');
|
||||
});
|
||||
}
|
||||
}
|
@ -101,6 +101,12 @@ return [
|
||||
'storage_application_key_id_label' => 'Application key ID:',
|
||||
'storage_application_key_label' => 'Application key:',
|
||||
'storage_auth_url_label' => 'Authentication URL:',
|
||||
'storage_b2_bucket_type' => [
|
||||
'autodetect' => 'Auto-detect',
|
||||
'label' => 'Bucket type:',
|
||||
'private' => 'Private',
|
||||
'public' => 'Public'
|
||||
],
|
||||
'storage_bucket_name_label' => 'Bucket name:',
|
||||
'storage_cdn_url_label' => 'Public CDN URL (if supported and enabled):',
|
||||
'storage_container_name_label' => 'Container name:',
|
||||
|
@ -39,4 +39,20 @@
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<label class="form-control-label" for="b2-bucket-type">@lang('forms.storage_b2_bucket_type.label')</label>
|
||||
<select class="form-control{{ $errors->has('b2_bucket_type') ? ' is-invalid' : '' }}" id="b2-bucket-type" name="b2_bucket_type">
|
||||
<option value="0"{{ old('b2_bucket_type', $storage->b2_bucket_type) === 0 ? ' selected="selected"' : '' }}>@lang('forms.storage_b2_bucket_type.autodetect')</option>
|
||||
<option value="1"{{ old('b2_bucket_type', $storage->b2_bucket_type) === 1 ? ' selected="selected"' : '' }}>@lang('forms.storage_b2_bucket_type.private')</option>
|
||||
<option value="2"{{ old('b2_bucket_type', $storage->b2_bucket_type) === 2 ? ' selected="selected"' : '' }}>@lang('forms.storage_b2_bucket_type.public')</option>
|
||||
</select>
|
||||
|
||||
@if ($errors->has('b2_bucket_type'))
|
||||
<div class="invalid-feedback">
|
||||
<strong>{{ $errors->first('b2_bucket_type') }}</strong>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
Loading…
Reference in New Issue
Block a user