Skip to content

Instantly share code, notes, and snippets.

Created January 24, 2012 17:39

Revisions

  1. @invalid-email-address Anonymous created this gist Jan 24, 2012.
    411 changes: 411 additions & 0 deletions db.go
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,411 @@
    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
    }
    292 changes: 292 additions & 0 deletions dbsh.go
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,292 @@
    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)
    }
    }
    }
    }