2016-10-31 15:50:02 +00:00
|
|
|
// Copyright (c) 2015 Serge Gebhardt. All rights reserved.
|
|
|
|
//
|
|
|
|
// Use of this source code is governed by the ISC
|
|
|
|
// license that can be found in the LICENSE file.
|
|
|
|
|
|
|
|
package acd
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"encoding/json"
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
"io/ioutil"
|
|
|
|
"net/http"
|
|
|
|
"net/url"
|
2016-11-07 10:20:26 +00:00
|
|
|
"strings"
|
2016-10-31 15:50:02 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
2016-11-06 14:26:12 +00:00
|
|
|
// LibraryVersion is the current version of this library
|
2016-10-31 15:50:02 +00:00
|
|
|
LibraryVersion = "0.1.0"
|
|
|
|
defaultMetadataURL = "https://drive.amazonaws.com/drive/v1/"
|
|
|
|
defaultContentURL = "https://content-na.drive.amazonaws.com/cdproxy/"
|
|
|
|
userAgent = "go-acd/" + LibraryVersion
|
|
|
|
)
|
|
|
|
|
|
|
|
// A Client manages communication with the Amazon Cloud Drive API.
|
|
|
|
type Client struct {
|
|
|
|
// HTTP client used to communicate with the API.
|
|
|
|
httpClient *http.Client
|
|
|
|
|
|
|
|
// Metadata URL for API requests. Defaults to the public Amazon Cloud Drive API.
|
|
|
|
// MetadataURL should always be specified with a trailing slash.
|
|
|
|
MetadataURL *url.URL
|
|
|
|
|
|
|
|
// Content URL for API requests. Defaults to the public Amazon Cloud Drive API.
|
|
|
|
// ContentURL should always be specified with a trailing slash.
|
|
|
|
ContentURL *url.URL
|
|
|
|
|
|
|
|
// User agent used when communicating with the API.
|
|
|
|
UserAgent string
|
|
|
|
|
|
|
|
// Services used for talking to different parts of the API.
|
|
|
|
Account *AccountService
|
|
|
|
Nodes *NodesService
|
2018-01-16 13:20:59 +00:00
|
|
|
Changes *ChangesService
|
2016-10-31 15:50:02 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// NewClient returns a new Amazon Cloud Drive API client. If a nil httpClient is
|
|
|
|
// provided, http.DefaultClient will be used. To use API methods which require
|
|
|
|
// authentication, provide an http.Client that will perform the authentication
|
|
|
|
// for you (such as that provided by the golang.org/x/oauth2 library).
|
|
|
|
func NewClient(httpClient *http.Client) *Client {
|
|
|
|
if httpClient == nil {
|
|
|
|
httpClient = http.DefaultClient
|
|
|
|
}
|
|
|
|
metadataURL, _ := url.Parse(defaultMetadataURL)
|
|
|
|
contentURL, _ := url.Parse(defaultContentURL)
|
|
|
|
|
|
|
|
c := &Client{
|
|
|
|
httpClient: httpClient,
|
|
|
|
MetadataURL: metadataURL,
|
|
|
|
ContentURL: contentURL,
|
|
|
|
UserAgent: userAgent,
|
|
|
|
}
|
|
|
|
|
|
|
|
c.Account = &AccountService{client: c}
|
|
|
|
c.Nodes = &NodesService{client: c}
|
2018-01-16 13:20:59 +00:00
|
|
|
c.Changes = &ChangesService{client: c}
|
2016-10-31 15:50:02 +00:00
|
|
|
|
|
|
|
return c
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewMetadataRequest creates an API request for metadata. A relative URL can be
|
|
|
|
// provided in urlStr, in which case it is resolved relative to the MetadataURL
|
|
|
|
// of the Client. Relative URLs should always be specified without a preceding
|
|
|
|
// slash. If specified, the value pointed to by body is JSON encoded and included
|
|
|
|
// as the request body.
|
|
|
|
func (c *Client) NewMetadataRequest(method, urlStr string, body interface{}) (*http.Request, error) {
|
|
|
|
return c.newRequest(c.MetadataURL, method, urlStr, body)
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewContentRequest creates an API request for content. A relative URL can be
|
|
|
|
// provided in urlStr, in which case it is resolved relative to the ContentURL
|
|
|
|
// of the Client. Relative URLs should always be specified without a preceding
|
|
|
|
// slash. If specified, the value pointed to by body is JSON encoded and included
|
|
|
|
// as the request body.
|
|
|
|
func (c *Client) NewContentRequest(method, urlStr string, body interface{}) (*http.Request, error) {
|
|
|
|
return c.newRequest(c.ContentURL, method, urlStr, body)
|
|
|
|
}
|
|
|
|
|
|
|
|
// newRequest creates an API request. A relative URL can be provided in urlStr,
|
|
|
|
// in which case it is resolved relative to base URL.
|
|
|
|
// Relative URLs should always be specified without a preceding slash. If
|
|
|
|
// specified, the value pointed to by body is JSON encoded and included as the
|
|
|
|
// request body.
|
|
|
|
func (c *Client) newRequest(base *url.URL, method, urlStr string, body interface{}) (*http.Request, error) {
|
|
|
|
rel, err := url.Parse(urlStr)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
u := base.ResolveReference(rel)
|
|
|
|
|
|
|
|
bodyReader, ok := body.(io.Reader)
|
|
|
|
if !ok && body != nil {
|
|
|
|
buf := &bytes.Buffer{}
|
|
|
|
err := json.NewEncoder(buf).Encode(body)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
bodyReader = buf
|
|
|
|
}
|
|
|
|
|
|
|
|
req, err := http.NewRequest(method, u.String(), bodyReader)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// req.Header.Add("Accept", mediaTypeV3)
|
|
|
|
if c.UserAgent != "" {
|
|
|
|
req.Header.Add("User-Agent", c.UserAgent)
|
|
|
|
}
|
|
|
|
return req, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Do sends an API request and returns the API response. The API response is
|
|
|
|
// JSON decoded and stored in the value pointed to by v, or returned as an
|
|
|
|
// error if an API error has occurred. If v implements the io.Writer
|
|
|
|
// interface, the raw response body will be written to v, without attempting to
|
|
|
|
// first decode it. If v is nil then the resp.Body won't be closed - this is
|
|
|
|
// your responsibility.
|
|
|
|
//
|
|
|
|
func (c *Client) Do(req *http.Request, v interface{}) (*http.Response, error) {
|
|
|
|
//buf, _ := httputil.DumpRequest(req, true)
|
|
|
|
//buf, _ := httputil.DumpRequest(req, false)
|
|
|
|
//log.Printf("req = %s", string(buf))
|
|
|
|
|
|
|
|
resp, err := c.httpClient.Do(req)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if v != nil {
|
|
|
|
defer resp.Body.Close()
|
|
|
|
}
|
|
|
|
//buf, _ = httputil.DumpResponse(resp, true)
|
|
|
|
//buf, _ = httputil.DumpResponse(resp, false)
|
|
|
|
//log.Printf("resp = %s", string(buf))
|
|
|
|
|
|
|
|
err = CheckResponse(resp)
|
|
|
|
if err != nil {
|
|
|
|
// even though there was an error, we still return the response
|
|
|
|
// in case the caller wants to inspect it further. We do close the
|
|
|
|
// Body though
|
|
|
|
if v == nil {
|
|
|
|
resp.Body.Close()
|
|
|
|
}
|
|
|
|
return resp, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if v != nil {
|
|
|
|
if w, ok := v.(io.Writer); ok {
|
|
|
|
io.Copy(w, resp.Body)
|
|
|
|
} else {
|
|
|
|
err = json.NewDecoder(resp.Body).Decode(v)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return resp, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// CheckResponse checks the API response for errors, and returns them if
|
|
|
|
// present. A response is considered an error if it has a status code outside
|
|
|
|
// the 200 range.
|
|
|
|
func CheckResponse(r *http.Response) error {
|
|
|
|
c := r.StatusCode
|
|
|
|
if 200 <= c && c <= 299 {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
errBody := ""
|
|
|
|
if data, err := ioutil.ReadAll(r.Body); err == nil {
|
2016-11-07 10:20:26 +00:00
|
|
|
errBody = strings.TrimSpace(string(data))
|
2016-10-31 15:50:02 +00:00
|
|
|
}
|
|
|
|
|
2016-11-07 10:20:26 +00:00
|
|
|
errMsg := fmt.Sprintf("HTTP code %v: %q: ", c, r.Status)
|
2016-10-31 15:50:02 +00:00
|
|
|
if errBody == "" {
|
|
|
|
errMsg += "no response body"
|
|
|
|
} else {
|
2016-11-07 10:20:26 +00:00
|
|
|
errMsg += fmt.Sprintf("response body: %q", errBody)
|
2016-10-31 15:50:02 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return errors.New(errMsg)
|
|
|
|
}
|