Implement Backblaze B2 - fixes #224

This commit is contained in:
Nick Craig-Wood
2015-12-30 10:05:07 +00:00
parent 113624691a
commit 33b3eea6ec
16 changed files with 1530 additions and 12 deletions
+50 -11
View File
@@ -6,6 +6,7 @@ import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"github.com/ncw/rclone/fs"
@@ -16,27 +17,48 @@ type Client struct {
c *http.Client
rootURL string
errorHandler func(resp *http.Response) error
headers map[string]string
}
// NewClient takes an oauth http.Client and makes a new api instance
func NewClient(c *http.Client, rootURL string) *Client {
return &Client{
func NewClient(c *http.Client) *Client {
api := &Client{
c: c,
rootURL: rootURL,
errorHandler: defaultErrorHandler,
headers: make(map[string]string),
}
api.SetHeader("User-Agent", fs.UserAgent)
return api
}
// defaultErrorHandler doesn't attempt to parse the http body
// defaultErrorHandler doesn't attempt to parse the http body, just
// returns it in the error message
func defaultErrorHandler(resp *http.Response) (err error) {
defer fs.CheckClose(resp.Body, &err)
return fmt.Errorf("HTTP error %v (%v) returned", resp.StatusCode, resp.Status)
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return err
}
return fmt.Errorf("HTTP error %v (%v) returned body: %q", resp.StatusCode, resp.Status, body)
}
// SetErrorHandler sets the handler to decode an error response when
// the HTTP status code is not 2xx. The handler should close resp.Body.
func (api *Client) SetErrorHandler(fn func(resp *http.Response) error) {
func (api *Client) SetErrorHandler(fn func(resp *http.Response) error) *Client {
api.errorHandler = fn
return api
}
// SetRoot sets the default root URL
func (api *Client) SetRoot(RootURL string) *Client {
api.rootURL = RootURL
return api
}
// SetHeader sets a header for all requests
func (api *Client) SetHeader(key, value string) *Client {
api.headers[key] = value
return api
}
// Opts contains parameters for Call, CallJSON etc
@@ -50,6 +72,8 @@ type Opts struct {
ContentLength *int64
ContentRange string
ExtraHeaders map[string]string
UserName string // username for Basic Auth
Password string // password for Basic Auth
}
// DecodeJSON decodes resp.Body into result
@@ -72,27 +96,42 @@ func (api *Client) Call(opts *Opts) (resp *http.Response, err error) {
if opts.Absolute {
url = opts.Path
} else {
if api.rootURL == "" {
return nil, fmt.Errorf("RootURL not set")
}
url = api.rootURL + opts.Path
}
req, err := http.NewRequest(opts.Method, url, opts.Body)
if err != nil {
return
}
headers := make(map[string]string)
// Set default headers
for k, v := range api.headers {
headers[k] = v
}
if opts.ContentType != "" {
req.Header.Add("Content-Type", opts.ContentType)
headers["Content-Type"] = opts.ContentType
}
if opts.ContentLength != nil {
req.ContentLength = *opts.ContentLength
}
if opts.ContentRange != "" {
req.Header.Add("Content-Range", opts.ContentRange)
headers["Content-Range"] = opts.ContentRange
}
// Set any extra headers
if opts.ExtraHeaders != nil {
for k, v := range opts.ExtraHeaders {
req.Header.Add(k, v)
headers[k] = v
}
}
req.Header.Add("User-Agent", fs.UserAgent)
// Now set the headers
for k, v := range headers {
req.Header.Add(k, v)
}
if opts.UserName != "" || opts.Password != "" {
req.SetBasicAuth(opts.UserName, opts.Password)
}
resp, err = api.c.Do(req)
if err != nil {
return nil, err
@@ -127,7 +166,7 @@ func (api *Client) CallJSON(opts *Opts, request interface{}, response interface{
if err != nil {
return resp, err
}
if opts.NoResponse {
if response == nil || opts.NoResponse {
return resp, nil
}
err = DecodeJSON(resp, response)