golang dropbox

curl

curl -X POST https://content.dropboxapi.com/2/files/alpha/upload \
    --header "Authorization: Bearer {token}" \
    --header "Dropbox-API-Arg: {\"path\": \"/Homework/math/あいう.txt\",\"mode\": \"add\",\"autorename\": true,\"mute\": false}" \
    --header "Content-Type: application/octet-stream" \
    --data-binary @hello.txt // ローカルのファイル名

golang

client.go

package dropbox

import (
    "encoding/json"
    "fmt"
    "io"
    "io/ioutil"
    "net/http"
    "net/url"
)

// APIEndpoint constants
const (
    APIEndpointBase   = "https://content.dropboxapi.com"
    APIEndpointUpload = "/2/files/alpha/upload"
)

// Client ...
type Client struct {
    token        string
    endpointBase *url.URL     // default APIEndpointBase
    httpClient   *http.Client // default http.DefaultClient
}

// NewClient ...
func NewClient(token string, httpClient *http.Client) (*Client, error) {
    u, err := url.ParseRequestURI(APIEndpointBase)
    if err != nil {
        return nil, err
    }
    c := Client{
        token:        token,
        endpointBase: u,
    }
    if httpClient != nil {
        c.httpClient = httpClient
    } else {
        c.httpClient = http.DefaultClient
    }
    return &c, nil
}

func (c *Client) url(endpoint string, query url.Values) string {
    u := c.endpointBase
    u.Path = endpoint
    u.RawQuery = query.Encode()
    return u.String()
}

func (c *Client) do(req *http.Request) (*http.Response, error) {
    req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", c.token))
    req.Header.Set("Content-Type", "application/octet-stream")
    return c.httpClient.Do(req)
}

// UploadFile ...
func (c *Client) UploadFile(body io.Reader, ci *CommitInfo) error {
    arg, err := json.Marshal(ci)
    if err != nil {
        return err
    }
    query := url.Values{}
    query.Set("arg", string(arg))
    req, err := http.NewRequest("POST", c.url(APIEndpointUpload, query), body)
    if err != nil {
        return err
    }
    req.Header.Set("Content-Type", "application/json; charset=UTF-8")
    res, err := c.do(req)
    if err != nil {
        return err
    }
    resBody, err := ioutil.ReadAll(res.Body)
    if err != nil {
        return err
    }
    if res.StatusCode != 200 {
        return fmt.Errorf(string(resBody))
    }
    return nil
}

client_test.go

package dropbox

import (
    "log"
    "os"
    "testing"

    "github.com/joho/godotenv"
)

func init() {
    err := godotenv.Load("dropbox.env")
    if err != nil {
        log.Fatal("Error loading .env file")
    }
}

func TestNewAppInfo(t *testing.T) {
    c, err := NewClient(os.Getenv("DROPBOX_TOKEN"), nil)
    if err != nil {
        t.Error(err)
    }
    ci := NewCommitInfo("/あああ.txt", NewWriteMode(ModeOverwrite))
    body, err := os.Open(`hello.txt`) // ファイルを取得して、io.Readerとしてそのままpost可能
    if err != nil {
        t.Error(err)
    }
    defer body.Close()

    err = c.UploadFile(body, ci)
    if err != nil {
        t.Error(err)
    }
}

model.go

package dropbox

import (
    "time"
)

// Mode ...
const (
    ModeAdd       = "add"
    ModeUpdate    = "update"
    ModeOverwrite = "overwrite"
)

// CommitInfo ...
type CommitInfo struct {
    // Path : Path in the user's Dropbox to save the file.
    Path string `json:"path"`
    // Mode : Selects what to do if the file already exists.
    Mode *WriteMode `json:"mode"`
    // Autorename : If there's a conflict, as determined by `mode`, have the
    // Dropbox server try to autorename the file to avoid conflict.
    Autorename bool `json:"autorename"`
    // ClientModified : The value to store as the `client_modified` timestamp.
    // Dropbox automatically records the time at which the file was written to
    // the Dropbox servers. It can also record an additional timestamp, provided
    // by Dropbox desktop clients, mobile clients, and API apps of when the file
    // was actually created or modified.
    ClientModified time.Time `json:"client_modified,omitempty"`
    // Mute : Normally, users are made aware of any file modifications in their
    // Dropbox account via notifications in the client software. If true, this
    // tells the clients that this modification shouldn't result in a user
    // notification.
    Mute bool `json:"mute"`
    // PropertyGroups : List of custom properties to add to file.
    // PropertyGroups []*file_properties.PropertyGroup `json:"property_groups,omitempty"`
}

// NewCommitInfo ...
func NewCommitInfo(path string, mode *WriteMode) *CommitInfo {
    s := new(CommitInfo)
    s.Path = path
    s.Mode = mode
    s.Autorename = false
    s.Mute = false
    return s
}

// WriteMode ...
type WriteMode struct {
    Tagged
    // Update : Overwrite if the given "rev" matches the existing file's "rev".
    // The autorename strategy is to append the string "conflicted copy" to the
    // file name. For example, "document.txt" might become "document (conflicted
    // copy).txt" or "document (Panda's conflicted copy).txt".
    Update string `json:"update,omitempty"`
}

// Tagged ...
type Tagged struct {
    Tag string `json:".tag"`
}

// NewWriteMode ...
func NewWriteMode(mode string) *WriteMode {
    return &WriteMode{
        Tagged: Tagged{mode},
        Update: "",
    }
}

参考

https://www.dropbox.com/developers/documentation/http/documentation