sliproad/files/backblaze.go
Gabriel Simmer 5e85a600d8
Simplify file router, add documenting comments.
The file router was starting to get out of hand, so I've done my best to
simplify it and get ahead of more spaghetti. As part of this, tidied up
some other code and added some much-needed comments and constants to
make things more readable. ObjectInfo() is much more useful as well,
determing if a file exists, if it's a directory and the location.

Feeling other bits of the project can be simplified, but mostly been
focused on the file providers and associated router.
2020-04-15 23:19:33 +01:00

276 lines
6.1 KiB
Go

package files
import (
"bytes"
"crypto/sha1"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"strings"
)
type BackblazeProvider struct {
FileProvider
Bucket string
DownloadLocation string
}
type BackblazeAuthPayload struct {
AccountId string `json:"accountId"`
AuthToken string `json:"authorizationToken"`
ApiUrl string `json:"apiUrl"`
DownloadUrl string `json:"downloadUrl"`
}
type BackblazeFile struct {
Action string `json:"action"`
Size int64 `json:"contentLength"`
Type string `json:"contentType"`
FileName string `json:"fileName"`
Timestamp int64 `json:"uploadTimestamp"`
}
type BackblazeFilePayload struct {
Files []BackblazeFile `json:"files"`
}
type BackblazeBucketInfo struct {
BucketId string `json:"bucketId"`
BucketName string `json:"bucketName"`
}
type BackblazeBucketInfoPayload struct {
Buckets []BackblazeBucketInfo `json:"buckets"`
}
type BackblazeUploadInfo struct {
UploadUrl string `json:"uploadUrl"`
AuthToken string `json:"authorizationToken"`
}
// Call Backblaze API endpoint to authorize and gather facts.
func (bp *BackblazeProvider) Setup(args map[string]string) bool {
applicationKeyId := args["applicationKeyId"]
applicationKey := args["applicationKey"]
client := &http.Client{}
req, err := http.NewRequest("GET",
"https://api.backblazeb2.com/b2api/v2/b2_authorize_account",
nil)
if err != nil {
return false
}
req.SetBasicAuth(applicationKeyId, applicationKey)
resp, err := client.Do(req)
if err != nil {
return false
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return false
}
var data BackblazeAuthPayload
err = json.Unmarshal(body, &data)
if err != nil {
return false
}
bp.Authentication = data.AuthToken
bp.Location = data.ApiUrl
bp.Name = data.AccountId
bp.DownloadLocation = data.DownloadUrl
return true
}
func (bp *BackblazeProvider) GetDirectory(path string) Directory {
client := &http.Client{}
requestBody := fmt.Sprintf(`{"bucketId": "%s"}`, bp.Bucket)
req, err := http.NewRequest("POST",
bp.Location + "/b2api/v2/b2_list_file_names",
bytes.NewBuffer([]byte(requestBody)))
if err != nil {
fmt.Println(err.Error())
return Directory{}
}
req.Header.Add("Authorization", bp.Authentication)
resp, err := client.Do(req)
if err != nil {
fmt.Println(err.Error())
return Directory{}
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
fmt.Println(err.Error())
return Directory{}
}
var data BackblazeFilePayload
err = json.Unmarshal(body, &data)
if err != nil {
fmt.Println(err.Error())
return Directory{}
}
finalDir := Directory{
Path: bp.Bucket,
}
for _, v := range data.Files {
file := FileInfo{
IsDirectory: v.Action == "folder",
Name: v.FileName,
}
if v.Action != "folder" {
split := strings.Split(v.FileName, ".")
file.Extension = split[len(split) - 1]
}
finalDir.Files = append(finalDir.Files, file)
}
return finalDir
}
func (bp *BackblazeProvider) FilePath(path string) string {
return ""
}
func (bp *BackblazeProvider) RemoteFile(path string, w io.Writer) {
client := &http.Client{}
// Get bucket name >:(
bucketIdPayload := fmt.Sprintf(`{"accountId": "%s", "bucketId": "%s"}`, bp.Name, bp.Bucket)
req, err := http.NewRequest("POST", bp.Location + "/b2api/v2/b2_list_buckets",
bytes.NewBuffer([]byte(bucketIdPayload)))
if err != nil {
fmt.Println(err.Error())
return
}
req.Header.Add("Authorization", bp.Authentication)
res, err := client.Do(req)
if err != nil {
fmt.Println(err.Error())
return
}
bucketData, err := ioutil.ReadAll(res.Body)
if err != nil {
fmt.Println(err.Error())
return
}
var data BackblazeBucketInfoPayload
json.Unmarshal(bucketData, &data)
ourBucket := data.Buckets[0].BucketName
// Get file and write over to client.
url := strings.Join([]string{bp.DownloadLocation,"file",ourBucket,path}, "/")
req, err = http.NewRequest("GET",
url,
bytes.NewBuffer([]byte("")))
if err != nil {
fmt.Println(err.Error())
return
}
req.Header.Add("Authorization", bp.Authentication)
file, err := client.Do(req)
if err != nil {
fmt.Println(err.Error())
return
}
_, err = io.Copy(w, file.Body)
if err != nil {
fmt.Println(err.Error())
return
}
}
func (bp *BackblazeProvider) SaveFile(file io.Reader, filename string, path string) bool {
client := &http.Client{}
bucketIdPayload := fmt.Sprintf(`{"bucketId": "%s"}`, bp.Bucket)
req, err := http.NewRequest("POST", bp.Location + "/b2api/v2/b2_get_upload_url",
bytes.NewBuffer([]byte(bucketIdPayload)))
if err != nil {
fmt.Println(err.Error())
return false
}
req.Header.Add("Authorization", bp.Authentication)
res, err := client.Do(req)
if err != nil {
fmt.Println(err.Error())
return false
}
bucketData, err := ioutil.ReadAll(res.Body)
if err != nil {
fmt.Println(err.Error())
return false
}
var data BackblazeUploadInfo
err = json.Unmarshal(bucketData, &data)
if err != nil {
fmt.Println(err.Error())
return false
}
req, err = http.NewRequest("POST",
data.UploadUrl,
file,
)
if err != nil {
fmt.Println(err.Error())
return false
}
// Read the content
var bodyBytes []byte
if req.Body != nil {
bodyBytes, _ = ioutil.ReadAll(req.Body)
}
req.Body = ioutil.NopCloser(bytes.NewBuffer(bodyBytes))
// Calculate SHA1 and add required headers.
fileSha := sha1.New()
fileSha.Write(bodyBytes)
req.Header.Add("Authorization", data.AuthToken)
req.Header.Add("X-Bz-File-Name", filename)
req.Header.Add("Content-Type", "b2/x-auto")
req.Header.Add("X-Bz-Content-Sha1", fmt.Sprintf("%x", fileSha.Sum(nil)))
req.ContentLength = int64(len(bodyBytes))
res, err = client.Do(req)
if err != nil {
fmt.Println(err.Error())
return false
}
return true
}
func (bp *BackblazeProvider) ObjectInfo(path string) (bool, bool, string) {
// B2 is really a "flat" filesystem, with directories being virtual.
// Therefore, we can assume everything is a file ;)
// TODO: Return true value.
isDir := path == ""
return true, isDir, FILE_IS_REMOTE
}
func (bp *BackblazeProvider) CreateDirectory(path string) bool {
// See above comment about virtual directories.
return false
}
func (bp *BackblazeProvider) Delete(path string) bool {
// TODO
return false
}