Fixed #1 added comments where necessary
Fixed #2 reorganized methods and structs Fixed #3 hide the upload struct better and make it more convenient Fixed #5 parse headers from downloads into a struct
This commit is contained in:
parent
5ada11af4c
commit
cf24af0470
5 changed files with 246 additions and 106 deletions
159
b2.go → b2/b2.go
159
b2.go → b2/b2.go
|
@ -9,7 +9,8 @@ import (
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"time"
|
"strconv"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// APIurl base address for the B2 API
|
// APIurl base address for the B2 API
|
||||||
|
@ -21,6 +22,9 @@ const APIsuffix = "/b2api/v1"
|
||||||
// GoodStatus status code for a successful API call
|
// GoodStatus status code for a successful API call
|
||||||
const GoodStatus = 200
|
const GoodStatus = 200
|
||||||
|
|
||||||
|
// HeaderInfoPrefix the prefix for file header info
|
||||||
|
const HeaderInfoPrefix = "X-Bz-Info-"
|
||||||
|
|
||||||
// ErrGeneric generic error from API
|
// ErrGeneric generic error from API
|
||||||
var ErrGeneric = errors.New("Received invalid response from B2 API")
|
var ErrGeneric = errors.New("Received invalid response from B2 API")
|
||||||
|
|
||||||
|
@ -31,43 +35,6 @@ type B2 struct {
|
||||||
AuthToken string `json:"authorizationToken"`
|
AuthToken string `json:"authorizationToken"`
|
||||||
DownloadURL string `json:"downloadUrl"`
|
DownloadURL string `json:"downloadUrl"`
|
||||||
AppKey string `json:"-"`
|
AppKey string `json:"-"`
|
||||||
Upload *Upload `json:"-"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Upload B2 upload information
|
|
||||||
type Upload struct {
|
|
||||||
BucketID string `json:"bucketId"`
|
|
||||||
UploadURL string `json:"uploadUrl"`
|
|
||||||
AuthToken string `json:"authorizationToken"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bucket B2 bucket type
|
|
||||||
type Bucket struct {
|
|
||||||
AccountID string `json:"accountId"`
|
|
||||||
ID string `json:"bucketId"`
|
|
||||||
Name string `json:"bucketName"`
|
|
||||||
Type string `json:"bucketType"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// FileInfo B2 file information
|
|
||||||
type FileInfo struct {
|
|
||||||
AccountID string `json:"accountId"`
|
|
||||||
ID string `json:"fileId"`
|
|
||||||
Name string `json:"fileName"`
|
|
||||||
BucketID string `json:"bucketId"`
|
|
||||||
Length int64 `json:"contentLength"`
|
|
||||||
Sha1 string `json:"contentSha1"`
|
|
||||||
Type string `json:"contentType"`
|
|
||||||
Info map[string]string `json:"fileInfo"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// FileName B2 file name
|
|
||||||
type FileName struct {
|
|
||||||
ID string `json:"fileId"`
|
|
||||||
Name string `json:"fileName"`
|
|
||||||
Action string `json:"action"`
|
|
||||||
Size int64 `json:"size"`
|
|
||||||
Timestamp int64 `json:"uploadTimestamp"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Err B2 error information
|
// Err B2 error information
|
||||||
|
@ -81,6 +48,7 @@ func (b *Err) Error() string {
|
||||||
return fmt.Sprintf("code: '%s' status: '%d' message: '%s'", b.Code, b.Status, b.Message)
|
return fmt.Sprintf("code: '%s' status: '%d' message: '%s'", b.Code, b.Status, b.Message)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// readResp take an http response from the B2 API and unmarshal it to the appropriate type
|
||||||
func readResp(resp *http.Response, output interface{}) error {
|
func readResp(resp *http.Response, output interface{}) error {
|
||||||
data, err := ioutil.ReadAll(resp.Body)
|
data, err := ioutil.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -96,6 +64,7 @@ func readResp(resp *http.Response, output interface{}) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// errors are generated anytime there is not a status code of GoodStatus
|
||||||
errb2 := &Err{}
|
errb2 := &Err{}
|
||||||
err = json.Unmarshal(data, errb2)
|
err = json.Unmarshal(data, errb2)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -105,6 +74,34 @@ func readResp(resp *http.Response, output interface{}) error {
|
||||||
return errb2
|
return errb2
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (b *B2) readHeaderFileInfo(header http.Header) (*FileInfo, error) {
|
||||||
|
var err error
|
||||||
|
info := &FileInfo{conn: b}
|
||||||
|
info.AccountID = b.AccountID
|
||||||
|
info.Type = header.Get("Content-Type")
|
||||||
|
info.ID = header.Get("X-Bz-File-Id")
|
||||||
|
info.Length, err = strconv.ParseInt(header.Get("Content-Length"), 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
info.Name, err = url.QueryUnescape(header.Get("X-Bz-File-Name"))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
info.Sha1 = header.Get("X-Bz-Content-Sha1")
|
||||||
|
|
||||||
|
for headerName, val := range header {
|
||||||
|
if !strings.HasPrefix(headerName, HeaderInfoPrefix) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// B2 does not support multiple values per header
|
||||||
|
info.Info[headerName[len(HeaderInfoPrefix):]] = val[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
return info, nil
|
||||||
|
}
|
||||||
|
|
||||||
// NewB2 create a new B2 API handler
|
// NewB2 create a new B2 API handler
|
||||||
func NewB2(accountID string, applicationKey string) (*B2, error) {
|
func NewB2(accountID string, applicationKey string) (*B2, error) {
|
||||||
req, err := http.NewRequest("GET", APIurl+APIsuffix+"/b2_authorize_account", nil)
|
req, err := http.NewRequest("GET", APIurl+APIsuffix+"/b2_authorize_account", nil)
|
||||||
|
@ -147,7 +144,7 @@ func (b *B2) CreateBucket(bucketName string, bucketType string) (*Bucket, error)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
bucket := &Bucket{}
|
bucket := &Bucket{conn: b}
|
||||||
err = readResp(resp, bucket)
|
err = readResp(resp, bucket)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -178,7 +175,7 @@ func (b *B2) DeleteBucket(bucketID string) (*Bucket, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
bucket := &Bucket{}
|
bucket := &Bucket{conn: b}
|
||||||
|
|
||||||
err = readResp(resp, bucket)
|
err = readResp(resp, bucket)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -218,60 +215,8 @@ func (b *B2) GetUploadURL(bucketID string) (*Upload, error) {
|
||||||
return upload, nil
|
return upload, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// UploadFile uploads one file to B2
|
|
||||||
func (b *B2) UploadFile(data io.Reader, fileName string, fileSize int64, contentType string, sha1 string, mtime *time.Time, info map[string]string) (*FileInfo, error) {
|
|
||||||
if b.Upload == nil {
|
|
||||||
return nil, errors.New("Must run GetUploadURL and set B2.Upload to upload")
|
|
||||||
}
|
|
||||||
|
|
||||||
req, err := http.NewRequest("POST", b.Upload.UploadURL, data)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
req.ContentLength = fileSize
|
|
||||||
|
|
||||||
if contentType == "" {
|
|
||||||
contentType = "b2/x-auto"
|
|
||||||
}
|
|
||||||
|
|
||||||
fileEncoded, err := url.Parse(fileName)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
fileName = fileEncoded.String()
|
|
||||||
|
|
||||||
req.Header.Add("Authorization", b.Upload.AuthToken)
|
|
||||||
req.Header.Add("X-Bz-File-Name", fileName)
|
|
||||||
req.Header.Add("Content-Type", contentType)
|
|
||||||
req.Header.Add("X-Bz-Content-Sha1", sha1)
|
|
||||||
if mtime != nil {
|
|
||||||
req.Header.Add("X-Bz-Info-src_last_modified_millis", fmt.Sprint(mtime.UnixNano()/1000000))
|
|
||||||
}
|
|
||||||
|
|
||||||
if info != nil {
|
|
||||||
for name, value := range info {
|
|
||||||
req.Header.Add("X-Bz-Info-"+name, value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := http.DefaultClient.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
fileInfo := &FileInfo{}
|
|
||||||
err = readResp(resp, fileInfo)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return fileInfo, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// DownloadFileByID Downloads one file from B2
|
// DownloadFileByID Downloads one file from B2
|
||||||
func (b *B2) DownloadFileByID(fileID string, output io.Writer) (http.Header, error) {
|
func (b *B2) DownloadFileByID(fileID string, output io.Writer) (*FileInfo, error) {
|
||||||
req, err := http.NewRequest("GET", b.DownloadURL+APIsuffix+"/b2_download_file_by_id", nil)
|
req, err := http.NewRequest("GET", b.DownloadURL+APIsuffix+"/b2_download_file_by_id", nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -299,11 +244,11 @@ func (b *B2) DownloadFileByID(fileID string, output io.Writer) (http.Header, err
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return resp.Header, nil
|
return b.readHeaderFileInfo(resp.Header)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DownloadFileByName downloads one file by providing the name of the bucket and the name of the file
|
// DownloadFileByName downloads one file by providing the name of the bucket and the name of the file
|
||||||
func (b *B2) DownloadFileByName(bucketName string, fileName string, output io.Writer) (http.Header, error) {
|
func (b *B2) DownloadFileByName(bucketName string, fileName string, output io.Writer) (*FileInfo, error) {
|
||||||
urlFileName, err := url.Parse(fileName)
|
urlFileName, err := url.Parse(fileName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -332,7 +277,7 @@ func (b *B2) DownloadFileByName(bucketName string, fileName string, output io.Wr
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return resp.Header, nil
|
return b.readHeaderFileInfo(resp.Header)
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateBucket update an existing bucket
|
// UpdateBucket update an existing bucket
|
||||||
|
@ -358,7 +303,7 @@ func (b *B2) UpdateBucket(bucketID string, bucketType string) (*Bucket, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
bucket := &Bucket{}
|
bucket := &Bucket{conn: b}
|
||||||
err = readResp(resp, bucket)
|
err = readResp(resp, bucket)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -389,7 +334,7 @@ func (b *B2) DeleteFileVersion(fileName string, fileID string) (*FileInfo, error
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
fileInfo := &FileInfo{}
|
fileInfo := &FileInfo{conn: b}
|
||||||
err = readResp(resp, fileInfo)
|
err = readResp(resp, fileInfo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -427,10 +372,14 @@ func (b *B2) ListBuckets() ([]Bucket, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for i := range buckets.Buckets {
|
||||||
|
buckets.Buckets[i].conn = b
|
||||||
|
}
|
||||||
|
|
||||||
return buckets.Buckets, nil
|
return buckets.Buckets, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListFileNames Lists the names of all files in a bucket, starting at a given name<Paste>
|
// ListFileNames Lists the names of all files in a bucket, starting at a given name
|
||||||
func (b *B2) ListFileNames(bucketID string, startFileName string, maxFileCount int) ([]FileName, string, error) {
|
func (b *B2) ListFileNames(bucketID string, startFileName string, maxFileCount int) ([]FileName, string, error) {
|
||||||
data, err := json.Marshal(struct {
|
data, err := json.Marshal(struct {
|
||||||
BucketID string `json:"bucketId"`
|
BucketID string `json:"bucketId"`
|
||||||
|
@ -465,6 +414,10 @@ func (b *B2) ListFileNames(bucketID string, startFileName string, maxFileCount i
|
||||||
return nil, "", err
|
return nil, "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for i := range list.Files {
|
||||||
|
list.Files[i].conn = b
|
||||||
|
}
|
||||||
|
|
||||||
return list.Files, list.NextFileName, nil
|
return list.Files, list.NextFileName, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -506,6 +459,10 @@ func (b *B2) ListFileVersions(bucketID string, startFileName string, startFileID
|
||||||
return nil, "", "", err
|
return nil, "", "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for i := range list.Files {
|
||||||
|
list.Files[i].conn = b
|
||||||
|
}
|
||||||
|
|
||||||
return list.Files, list.NextFileID, list.NextFileName, nil
|
return list.Files, list.NextFileID, list.NextFileName, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -529,7 +486,7 @@ func (b *B2) GetFileInfo(fileID string) (*FileInfo, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
info := &FileInfo{}
|
info := &FileInfo{conn: b}
|
||||||
err = readResp(resp, info)
|
err = readResp(resp, info)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -559,7 +516,7 @@ func (b *B2) HideFile(bucketID string, fileName string) (*FileName, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
info := &FileName{}
|
info := &FileName{conn: b}
|
||||||
err = readResp(resp, info)
|
err = readResp(resp, info)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
65
b2/bucket.go
Normal file
65
b2/bucket.go
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
package b2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Bucket B2 bucket type
|
||||||
|
type Bucket struct {
|
||||||
|
AccountID string `json:"accountId"`
|
||||||
|
ID string `json:"bucketId"`
|
||||||
|
Name string `json:"bucketName"`
|
||||||
|
Type string `json:"bucketType"`
|
||||||
|
conn *B2
|
||||||
|
upload *Upload
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete deletes this bucket
|
||||||
|
func (b *Bucket) Delete() error {
|
||||||
|
_, err := b.conn.DeleteBucket(b.ID)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update updates this bucket
|
||||||
|
func (b *Bucket) Update(bucketType string) error {
|
||||||
|
bucket, err := b.conn.UpdateBucket(b.ID, bucketType)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
b.AccountID = bucket.AccountID
|
||||||
|
b.ID = bucket.ID
|
||||||
|
b.Name = bucket.Name
|
||||||
|
b.Type = bucket.Type
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListFileNames Lists the names of all files in a bucket, starting a given name
|
||||||
|
func (b *Bucket) ListFileNames(startFileName string, maxFileCount int) ([]FileName, string, error) {
|
||||||
|
return b.conn.ListFileNames(b.ID, startFileName, maxFileCount)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListFileVersions lists all of the versions of all of the files contained in one bucket, in alphabetical order by file name, and by reverse of date/time uploaded for versions of files with the same name
|
||||||
|
func (b *Bucket) ListFileVersions(startFileName string, startFileID string, maxFileCount int) ([]FileName, string, string, error) {
|
||||||
|
return b.conn.ListFileVersions(b.ID, startFileName, startFileID, maxFileCount)
|
||||||
|
}
|
||||||
|
|
||||||
|
// HideFile hides a file so that downloading by name will not find the file, but previous versions of the file are still stored. See File Versions about what it means to hide a file
|
||||||
|
func (b *Bucket) HideFile(fileName string) (*FileName, error) {
|
||||||
|
return b.conn.HideFile(b.ID, fileName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UploadFile uploads one file to B2
|
||||||
|
func (b *Bucket) UploadFile(data io.Reader, fileName string, fileSize int64, contentType string, sha1 string, mtime *time.Time, info map[string]string) (*FileInfo, error) {
|
||||||
|
if b.upload == nil {
|
||||||
|
var err error
|
||||||
|
b.upload, err = b.conn.GetUploadURL(b.ID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return b.upload.UploadFile(data, fileName, fileSize, contentType, sha1, mtime, info)
|
||||||
|
}
|
33
b2/fileinfo.go
Normal file
33
b2/fileinfo.go
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
package b2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
// FileInfo B2 file information
|
||||||
|
type FileInfo struct {
|
||||||
|
AccountID string `json:"accountId"`
|
||||||
|
ID string `json:"fileId"`
|
||||||
|
Name string `json:"fileName"`
|
||||||
|
BucketID string `json:"bucketId"`
|
||||||
|
Length int64 `json:"contentLength"`
|
||||||
|
Sha1 string `json:"contentSha1"`
|
||||||
|
Type string `json:"contentType"`
|
||||||
|
Info map[string]string `json:"fileInfo"`
|
||||||
|
conn *B2
|
||||||
|
}
|
||||||
|
|
||||||
|
// Download downloads this file ID's content
|
||||||
|
func (f *FileInfo) Download(output io.Writer) (*FileInfo, error) {
|
||||||
|
return f.conn.DownloadFileByID(f.ID, output)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete deletes this version of the file
|
||||||
|
func (f *FileInfo) Delete() (*FileInfo, error) {
|
||||||
|
return f.conn.DeleteFileVersion(f.Name, f.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hide hides a file so that downloading by name will not find the file, but previous versions of the file are still stored. See File Versions about what it means to hide a file
|
||||||
|
func (f *FileInfo) Hide() (*FileName, error) {
|
||||||
|
return f.conn.HideFile(f.BucketID, f.Name)
|
||||||
|
}
|
16
b2/filename.go
Normal file
16
b2/filename.go
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
package b2
|
||||||
|
|
||||||
|
// FileName B2 file name
|
||||||
|
type FileName struct {
|
||||||
|
ID string `json:"fileId"`
|
||||||
|
Name string `json:"fileName"`
|
||||||
|
Action string `json:"action"`
|
||||||
|
Size int64 `json:"size"`
|
||||||
|
Timestamp int64 `json:"uploadTimestamp"`
|
||||||
|
conn *B2
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetFileInfo Gets information about one file stored in B2
|
||||||
|
func (f *FileName) GetFileInfo() (*FileInfo, error) {
|
||||||
|
return f.conn.GetFileInfo(f.ID)
|
||||||
|
}
|
69
b2/upload.go
Normal file
69
b2/upload.go
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
package b2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Upload B2 upload information
|
||||||
|
type Upload struct {
|
||||||
|
BucketID string `json:"bucketId"`
|
||||||
|
UploadURL string `json:"uploadUrl"`
|
||||||
|
AuthToken string `json:"authorizationToken"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// UploadFile uploads one file to B2
|
||||||
|
func (u *Upload) UploadFile(data io.Reader, fileName string, fileSize int64, contentType string, sha1 string, mtime *time.Time, info map[string]string) (*FileInfo, error) {
|
||||||
|
req, err := http.NewRequest("POST", u.UploadURL, data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// content length is necessary for buffers like os.File
|
||||||
|
req.ContentLength = fileSize
|
||||||
|
|
||||||
|
// use B2's autodetect content type if one is not passed
|
||||||
|
if contentType == "" {
|
||||||
|
contentType = "b2/x-auto"
|
||||||
|
}
|
||||||
|
|
||||||
|
// encode fileName via URL encoding per B2's documentation
|
||||||
|
fileEncoded, err := url.Parse(fileName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
fileName = fileEncoded.String()
|
||||||
|
|
||||||
|
req.Header.Add("Authorization", u.AuthToken)
|
||||||
|
req.Header.Add("X-Bz-File-Name", fileName)
|
||||||
|
req.Header.Add("Content-Type", contentType)
|
||||||
|
req.Header.Add("X-Bz-Content-Sha1", sha1)
|
||||||
|
|
||||||
|
// B2 requires time to be in UNIX milliseconds
|
||||||
|
if mtime != nil {
|
||||||
|
req.Header.Add("X-Bz-Info-src_last_modified_millis", fmt.Sprint(mtime.UnixNano()/1000000))
|
||||||
|
}
|
||||||
|
|
||||||
|
if info != nil {
|
||||||
|
for name, value := range info {
|
||||||
|
req.Header.Add("X-Bz-Info-"+name, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := http.DefaultClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
fileInfo := &FileInfo{}
|
||||||
|
err = readResp(resp, fileInfo)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return fileInfo, nil
|
||||||
|
}
|
Loading…
Reference in a new issue