Skip to content

Instantly share code, notes, and snippets.

Created January 24, 2012 17:39
Show Gist options
  • Star 14 You must be signed in to star a gist
  • Fork 5 You must be signed in to fork a gist
  • Save anonymous/1671416 to your computer and use it in GitHub Desktop.
Save anonymous/1671416 to your computer and use it in GitHub Desktop.
Go SDK for Dropbox
package dropbox
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"strconv"
"time"
)
const TimeFormat = time.RFC1123Z
type OAuthToken struct {
Key, Secret string
}
type token interface {
key() string
secret() string
}
func buildAuthString(consumerToken AppToken, tok token) string {
var buf bytes.Buffer
buf.WriteString(`OAuth oauth_version="1.0", oauth_signature_method="PLAINTEXT"`)
fmt.Fprintf(&buf, `, oauth_consumer_key="%s"`, url.QueryEscape(consumerToken.Key))
fmt.Fprintf(&buf, `, oauth_timestamp="%v"`, time.Now().Unix())
sigend := ""
if tok != nil {
sigend = url.QueryEscape(tok.secret())
fmt.Fprintf(&buf, `, oauth_token="%s"`, url.QueryEscape(tok.key()))
}
fmt.Fprintf(&buf, `, oauth_signature="%s&%s"`, url.QueryEscape(consumerToken.Secret), sigend)
return buf.String()
}
type Error struct {
Code int
Message string
}
func (e Error) Error() string {
return fmt.Sprintf("%d: %s", e.Code, e.Message)
}
func doRequest(r *http.Request, consumerTok AppToken, accessTok token) (*FileReader, error) {
r.Header.Set("Authorization", buildAuthString(consumerTok, accessTok))
resp, err := http.DefaultClient.Do(r)
if err != nil {
return nil, err
}
if resp.StatusCode != http.StatusOK {
defer resp.Body.Close()
var info struct {
Error string `json:"error"`
}
json.NewDecoder(resp.Body).Decode(&info)
return nil, Error{
Code: resp.StatusCode,
Message: info.Error}
}
return newFileReader(resp), nil
}
func apiURL(path string) url.URL {
return url.URL{
Scheme: "https",
Host: apiHost,
Path: "/" + apiVersion + path}
}
func GetAuthorizeURL(requestToken RequestToken, callback *url.URL) *url.URL {
params := url.Values{"oauth_token": {requestToken.Key}}
if callback != nil {
params.Add("oauth_callback", callback.String())
}
return &url.URL{
Scheme: "https",
Host: webHost,
Path: "/" + apiVersion + "/oauth/authorize",
RawQuery: params.Encode()}
}
type AppToken OAuthToken
type RequestToken OAuthToken
type AccessToken OAuthToken
func (at AccessToken) key() string {
return at.Key
}
func (at AccessToken) secret() string {
return at.Secret
}
func (rt RequestToken) key() string {
return rt.Key
}
func (rt RequestToken) secret() string {
return rt.Secret
}
func postForToken(u url.URL, appToken AppToken, accessToken token) (OAuthToken, error) {
r, e := http.NewRequest("POST", u.String(), nil)
if e != nil {
return OAuthToken{}, e
}
rc, e := doRequest(r, appToken, accessToken)
if e != nil {
return OAuthToken{}, e
}
defer rc.Close()
var buf bytes.Buffer
buf.ReadFrom(rc)
vals, e := url.ParseQuery(buf.String())
if e != nil {
return OAuthToken{}, e
}
return OAuthToken{
Key: vals.Get("oauth_token"),
Secret: vals.Get("oauth_token_secret")}, nil
}
func StartAuth(appToken AppToken) (RequestToken, error) {
u := apiURL("/oauth/request_token")
t, e := postForToken(u, appToken, nil)
return RequestToken(t), e
}
func FinishAuth(appToken AppToken, requestToken RequestToken) (AccessToken, error) {
u := apiURL("/oauth/access_token")
t, e := postForToken(u, appToken, requestToken)
return AccessToken(t), e
}
type accessType int
const (
AppFolder accessType = iota
Dropbox
)
type Config struct {
Access accessType
Locale string
}
type Client struct {
AppToken AppToken
AccessToken AccessToken
Config Config
}
type AccountInfo struct {
ReferralLink string `json:"referral_link"`
DisplayName string `json:"display_name"`
Uid uint64 `json:"uid"`
Country string `json:"country"`
QuotaInfo struct {
Shared uint64 `json:"shared"`
Quota uint64 `json:"quota"`
Normal uint64 `json:"normal"`
} `json:"quota_info"`
Email string `json:"email"`
}
type FileMetadata struct {
Size string `json:"size"`
Rev string `json:"rev"`
ThumbExists bool `json:"thumb_exists"`
Bytes int64 `json:"bytes"`
Modified string `json:"modified"`
Path string `json:"path"`
IsDir bool `json:"is_dir"`
Icon string `json:"icon"`
Root string `json:"root"`
MimeType string `json:"mime_type"`
Revision int64 `json:"revision"`
Hash *string `json:"hash"`
Contents []FileMetadata `json:"contents"`
}
func (md *FileMetadata) ModTime() time.Time {
t, _ := time.Parse(TimeFormat, md.Modified)
return t
}
type Link struct {
URL string `json:"url"`
Expires string `json:"expires"`
}
func (s *Client) doGet(u url.URL) (*FileReader, error) {
r, e := http.NewRequest("GET", u.String(), nil)
if e != nil {
return nil, e
}
return doRequest(r, s.AppToken, s.AccessToken)
}
func (s *Client) getForJson(u url.URL, jdata interface{}) error {
buf, err := s.doGet(u)
if err != nil {
return err
}
defer buf.Close()
return json.NewDecoder(buf).Decode(jdata)
}
func (s *Client) postForJson(u url.URL, jdata interface{}) error {
r, e := http.NewRequest("POST", u.String(), nil)
if e != nil {
return e
}
rc, e := doRequest(r, s.AppToken, s.AccessToken)
if e != nil {
return e
}
defer rc.Close()
return json.NewDecoder(rc).Decode(jdata)
}
const (
apiVersion = "1"
apiHost = "api.dropbox.com"
contentHost = "api-content.dropbox.com"
webHost = "www.dropbox.com"
)
func (s *Client) GetAccountInfo() (*AccountInfo, error) {
u := apiURL("/account/info")
u.RawQuery = s.Config.localeQuery()
var info AccountInfo
if e := s.getForJson(u, &info); e != nil {
return nil, e
}
return &info, nil
}
func (s *Client) root() string {
if s.Config.Access == Dropbox {
return "dropbox"
}
return "sandbox"
}
func (s *Client) GetMetadata(path string, list bool) (*FileMetadata, error) {
u := apiURL("/metadata/" + s.root() + path)
v := url.Values{"list": {strconv.FormatBool(list)}}
u.RawQuery = s.Config.setLocale(v).Encode()
var md FileMetadata
if e := s.getForJson(u, &md); e != nil {
return nil, e
}
return &md, nil
}
func contentURL(path string) url.URL {
return url.URL{
Scheme: "https",
Host: contentHost,
Path: "/" + apiVersion + path}
}
type ThumbSize string
const (
ThumbSmall ThumbSize = "small"
ThumbMedium ThumbSize = "medium"
ThumbLarge ThumbSize = "large"
ThumbL ThumbSize = "l"
ThumbXL ThumbSize = "xl"
)
func (s *Client) GetThumb(path string, size ThumbSize) (*FileReader, error) {
u := contentURL("/thumbnails/" + s.root() + path)
if size != "" {
u.RawQuery = url.Values{"size": {string(size)}}.Encode()
}
rc, e := s.doGet(u)
return rc, e
}
func (s *Client) AddFile(path string, contents io.Reader, size int64) (*FileMetadata, error) {
return s.putFile(path, contents, size, url.Values{"overwrite": {"false"}})
}
func (s *Client) UpdateFile(path string, contents io.Reader, size int64, parentRev string) (*FileMetadata, error) {
return s.putFile(path, contents, size, url.Values{"parent_rev": {parentRev}})
}
func (s *Client) ForceFile(path string, contents io.Reader, size int64) (*FileMetadata, error) {
return s.putFile(path, contents, size, url.Values{"overwrite": {"true"}})
}
func (s *Client) putFile(path string, contents io.Reader, size int64, vals url.Values) (*FileMetadata, error) {
u := contentURL("/files_put/" + s.root() + path)
if vals == nil {
vals = make(url.Values)
}
u.RawQuery = s.Config.setLocale(vals).Encode()
r, e := http.NewRequest("PUT", u.String(), contents)
if e != nil {
return nil, e
}
r.ContentLength = size
buf, err := doRequest(r, s.AppToken, s.AccessToken)
if err != nil {
return nil, err
}
var md FileMetadata
dec := json.NewDecoder(buf)
if e := dec.Decode(&md); e != nil {
return nil, e
}
return &md, nil
}
type FileReader struct {
io.ReadCloser
// -1 if unknown.
Size int64
ContentType string
}
func newFileReader(r *http.Response) *FileReader {
return &FileReader{
r.Body,
r.ContentLength,
r.Header.Get("Content-Type")}
}
func (s *Client) GetFile(path string) (*FileReader, error) {
return s.doGet(contentURL("/files/" + s.root() + path))
}
func (s *Client) GetLink(path string) (*Link, error) {
u := apiURL("/shares/" + s.root() + path)
u.RawQuery = s.Config.localeQuery()
var link Link
if e := s.postForJson(u, &link); e != nil {
return nil, e
}
return &link, nil
}
func (s *Client) GetMedia(path string) (*Link, error) {
u := apiURL("/media/" + s.root() + path)
u.RawQuery = s.Config.localeQuery()
var link Link
if e := s.postForJson(u, &link); e != nil {
return nil, e
}
return &link, nil
}
func (c *Config) localeQuery() string {
return c.setLocale(url.Values{}).Encode()
}
func (c *Config) setLocale(v url.Values) url.Values {
if c.Locale != "" {
v.Set("locale", c.Locale)
}
return v
}
func (s *Client) fileOp(op string, vals url.Values) (*FileMetadata, error) {
u := apiURL("/fileops/" + op)
vals.Set("root", s.root())
u.RawQuery = s.Config.setLocale(vals).Encode()
var md FileMetadata
if e := s.postForJson(u, &md); e != nil {
return nil, e
}
return &md, nil
}
func (s *Client) Move(from, to string) (*FileMetadata, error) {
return s.fileOp("move", url.Values{"from_path": {from}, "to_path": {to}})
}
func (s *Client) Copy(from, to string) (*FileMetadata, error) {
return s.fileOp("copy", url.Values{"from_path": {from}, "to_path": {to}})
}
func (s *Client) CreateDir(path string) (*FileMetadata, error) {
return s.fileOp("create_folder", url.Values{"path": {path}})
}
func (s *Client) Delete(path string) (*FileMetadata, error) {
return s.fileOp("delete", url.Values{"path": {path}})
}
func (c *Client) Search(path, query string, limit int) ([]FileMetadata, error) {
u := apiURL("/search/" + c.root() + path)
v := url.Values{"query": {query}}
if limit > 0 {
v.Set("limit", strconv.Itoa(limit))
}
u.RawQuery = c.Config.setLocale(v).Encode()
var md []FileMetadata
if e := c.getForJson(u, &md); e != nil {
return nil, e
}
return md, nil
}
package main
import (
"bufio"
"dropbox"
"encoding/json"
"fmt"
"io"
"os"
"os/exec"
gpath "path"
"strings"
"text/tabwriter"
"time"
"unicode"
)
func Ls(db *dropbox.Client, args []string) error {
md, e := db.GetMetadata(Cwd, true)
if e != nil {
return e
}
w := tabwriter.NewWriter(os.Stdout, 0, 2, 1, ' ', 0)
defer w.Flush()
for _, f := range md.Contents {
fmt.Fprintf(w, "%d\t%s\t%s\t\n", f.Bytes, f.ModTime().Format(time.Stamp), gpath.Base(f.Path))
}
return nil
}
func Cd(db *dropbox.Client, args []string) error {
dest := args[0]
if dest == ".." {
Cwd = gpath.Dir(Cwd)
return nil
}
dest = mkabs(dest)
md, e := db.GetMetadata(dest, false)
if e != nil {
return e
}
if md.IsDir {
Cwd = dest
return nil
}
return fmt.Errorf("No such dir: %s", dest)
}
func Cat(db *dropbox.Client, args []string) error {
rc, e := db.GetFile(mkabs(args[0]))
if e != nil {
return e
}
defer rc.Close()
if !strings.HasPrefix(rc.ContentType, "text/") {
return fmt.Errorf("Not a content type you should cat: %s", rc.ContentType)
}
_, e = io.Copy(os.Stdout, rc)
return e
}
func Put(db *dropbox.Client, args []string) error {
srcfile := args[0]
if !gpath.IsAbs(srcfile) {
srcdir, e := os.Getwd()
if e != nil {
return e
}
srcfile = gpath.Join(srcdir, srcfile)
}
src, e := os.Open(srcfile)
if e != nil {
return e
}
defer src.Close()
fi, e := src.Stat()
if e != nil {
return e
}
destpath := gpath.Join(Cwd, gpath.Base(srcfile))
fmt.Printf("Uploading to %s\n", destpath)
_, e = db.AddFile(destpath, src, fi.Size())
return e
}
func Get(db *dropbox.Client, args []string) error {
fname := mkabs(args[0])
destdir, e := os.Getwd()
if e != nil {
return e
}
destfile := gpath.Join(destdir, gpath.Base(fname))
r, e := db.GetFile(fname)
if e != nil {
return e
}
defer r.Close()
fmt.Printf("Saving to %s\n", destfile)
dest, e := os.Create(destfile)
if e != nil {
return e
}
defer dest.Close()
_, e = io.Copy(dest, r)
return e
}
func Share(db *dropbox.Client, args []string) error {
link, e := db.GetLink(mkabs(args[0]))
if e != nil {
return e
}
fmt.Println(link.URL)
return nil
}
func mkabs(path string) string {
if !gpath.IsAbs(path) {
return gpath.Join(Cwd, path)
}
return path
}
func Mv(db *dropbox.Client, args []string) error {
from, to := mkabs(args[0]), mkabs(args[1])
_, e := db.Move(from, to)
return e
}
func Cp(db *dropbox.Client, args []string) error {
from, to := mkabs(args[0]), mkabs(args[1])
_, e := db.Copy(from, to)
return e
}
func Rm(db *dropbox.Client, args []string) error {
_, e := db.Delete(mkabs(args[0]))
return e
}
func Mkdir(db *dropbox.Client, args []string) error {
_, e := db.CreateDir(mkabs(args[0]))
return e
}
func Whoami(db *dropbox.Client, args []string) error {
ai, e := db.GetAccountInfo()
if e != nil {
return e
}
b, e := json.MarshalIndent(ai, "", " ")
if e != nil {
return e
}
fmt.Println(string(b))
return nil
}
func Find(db *dropbox.Client, args []string) error {
r, e := db.Search(Cwd, args[0], 0)
if e != nil {
return e
}
for _, m := range r {
fmt.Println(m.Path)
}
return nil
}
type Cmd struct {
Fn func(*dropbox.Client, []string) error
ArgCount int
}
func tryCmd(c *dropbox.Client, cname string, args []string) error {
if len(args) == 0 {
return fmt.Errorf("Last argument needs to be a dropbox path.")
}
fname := args[len(args)-1]
args = args[:len(args)-1]
f, e := c.GetFile(mkabs(fname))
if e != nil {
return e
}
defer f.Close()
cmd := exec.Command(cname, args...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Stdin = f
return cmd.Run()
}
var cmds = map[string]Cmd{
"pwd": {func(*dropbox.Client, []string) error {
fmt.Println(Cwd)
return nil
}, 0},
"mv": {Mv, 2},
"cp": {Cp, 2},
"cd": {Cd, 1},
"rm": {Rm, 1},
"mkdir": {Mkdir, 1},
"share": {Share, 1},
"cat": {Cat, 1},
"ls": {Ls, 0},
"find": {Find, 1},
"whoami": {Whoami, 0},
"help": {func(*dropbox.Client, []string) error {
for k := range cmds {
fmt.Println(k)
}
return nil
}, 0},
"put": {Put, 1},
"get": {Get, 1},
"exit": {func(*dropbox.Client, []string) error {
os.Exit(0)
return nil
}, 0},
}
// Global mutable var. Oh noes.
var Cwd = "/"
func tokenize(s string) []string {
curr := make([]rune, 0, 32)
res := make([]string, 0, 10)
var in_word, last_slash bool
for _, r := range s {
if last_slash || (!unicode.IsSpace(r) && (r != '\\')) {
curr = append(curr, r)
in_word = true
} else {
if in_word {
res = append(res, string(curr))
curr = curr[0:0]
in_word = false
}
}
last_slash = r == '\\'
}
if len(curr) > 0 {
res = append(res, string(curr))
}
return res
}
var AppToken = dropbox.AppToken{
Key: "<redacted>",
Secret: "<redacted>"}
var AccessToken = dropbox.AccessToken{
Key: "<redacted>",
Secret: "<redacted>"}
func main() {
db := &dropbox.Client{
AppToken: AppToken,
AccessToken: AccessToken,
Config: dropbox.Config{
Access: dropbox.Dropbox,
Locale: "us",
}}
in := bufio.NewReader(os.Stdin)
for {
fmt.Printf("%s > ", gpath.Base(Cwd))
lineb, _, e := in.ReadLine()
if e != nil {
if e == io.EOF {
break
}
panic(e)
}
tokens := tokenize(string(lineb))
if len(tokens) == 0 {
continue
}
cmd, ok := cmds[tokens[0]]
if !ok {
if e := tryCmd(db, tokens[0], tokens[1:]); e != nil {
fmt.Printf("ERROR: %v\n", e)
}
} else {
args := tokens[1:]
if len(args) != cmd.ArgCount && cmd.ArgCount != -1 {
fmt.Printf("ERROR: %s expected %d args, got %d.\n", tokens[0], cmd.ArgCount, len(args))
} else if e := cmd.Fn(db, args); e != nil {
fmt.Printf("ERROR: %v\n", e)
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment