Skip to content

Instantly share code, notes, and snippets.

@xiconet
Last active March 31, 2018 22:53
Show Gist options
  • Select an option

  • Save xiconet/8f1c9c1f420e3b5a1dfb60ee612f4bd1 to your computer and use it in GitHub Desktop.

Select an option

Save xiconet/8f1c9c1f420e3b5a1dfb60ee612f4bd1 to your computer and use it in GitHub Desktop.

Revisions

  1. xiconet revised this gist Mar 31, 2018. No changes.
  2. xiconet created this gist Mar 31, 2018.
    1,396 changes: 1,396 additions & 0 deletions dboxapi.go
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,1396 @@
    // Command dbox is a client for the dropbox "rest" API
    package main

    import(
    "os"
    "os/exec"
    "time"
    "net/http"
    "net/url"
    "io"
    "io/ioutil"
    "fmt"
    "log"
    "sort"
    "strings"
    "bytes"
    "strconv"
    "menteslibres.net/gosexy/yaml"
    "menteslibres.net/gosexy/to"
    "encoding/json"
    "github.com/codegangsta/cli"
    "github.com/cheggaaa/pb"
    "runtime"
    pth "path"
    ospath "path/filepath"
    "sync"
    "github.com/xiconet/utils"
    fd "github.com/xiconet/godownload"
    )

    const(
    api_url string = "https://api.dropbox.com/2"
    content_url = "https://content.dropboxapi.com/2"
    cfg_file = "path/to/config_file" // FIXME
    )

    var(
    users = map[string]string{"user0": "0", "user1": "1", ..., "usern": "n"} // FIXME
    audioTypes = []string{".mp3", ".flac", ".ape", ".wav", ".wv", ".mpc", ".ogg", ".m4a"}
    unhandled = []string{".ape", ".wv", ".wav"} // should be handled by VLC
    chunksize = int64(8*1024*1024)
    )

    func Userlist() (u []string) {
    for user, _ := range users {
    u = append(u, user)
    }
    return
    }

    func Uids() (u []string) {
    for _, uid := range users {
    u = append(u, uid)
    }
    return
    }

    func UidToUser(u string) (string, error) {
    for user, uid := range users {
    if u == uid {
    return user, nil
    }
    }
    return "", fmt.Errorf("usage error: unregistered user")
    }

    type Info_off struct {
    Country string `json:"country"`
    DisplayName string `json:"display_name"`
    Email string `json:"email"`
    EmailVerified bool `json:"email_verified"`
    IsPaired bool `json:"is_paired"`
    Locale string `json:"locale"`
    NameDetails struct {
    FamiliarName string `json:"familiar_name"`
    GivenName string `json:"given_name"`
    Surname string `json:"surname"`
    } `json:"name_details"`
    QuotaInfo struct {
    Datastores int64 `json:"datastores"`
    Normal int64 `json:"normal"`
    Quota int64 `json:"quota"`
    Shared int64 `json:"shared"`
    } `json:"quota_info"`
    ReferralLink string `json:"referral_link"`
    Uid int `json:"uid"`
    }

    type Info struct {
    AccountId string `json:"account_id"`
    Name struct {
    GivenName string `json:"given_name"`
    Surname string `json:"surname"`
    FamiliarName string `json:"familiar_name"`
    DisplayName string `json:"display_name"`
    AbbreviatedName string `json:"abbreviated_name"`
    } `json:"name"`
    Email string `json:"email"`
    EmailVerified bool `json:"email_verified"`
    Disabled bool `json:"disabled"`
    Country string `json:"country"`
    Locale string `json:"locale"`
    ReferralLink string `json:"referral_link"`
    IsPaired bool `json:"is_paired"`
    AccountType struct {
    Tag string `json:".tag"`
    } `json:"account_type"`
    RootInfo struct {
    Tag string `json:".tag"`
    RootNamespaceId string `json:"root_namespace_id"`
    HomeNamespaceId string `json:"home_namespace_id"`
    } `json:"root_info"`
    }

    type SpaceUsage struct {
    Used int64 `json:"used"`
    Allocation struct {
    Tag string `json:".tag"`
    Individual string `json:"individual"`
    Allocated int64 `json:"allocated"`
    } `json:"allocation"`
    }

    type Entry struct {
    Tag string `json:".tag"`
    Name string `json:"name"`
    PathLower string `json:"path_lower"`
    PathDisplay string `json:"path_display"`
    Id string `json:"id"`
    Size int64 `json:size"`
    User string // to be set later on
    }

    type Entries []Entry
    type ByName []Entry

    func (a ByName) Len() int { return len(a) }
    func (a ByName) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
    func (a ByName) Less(i, j int) bool { return a[i].Name < a[j].Name }


    func (s Entries) SetUser(user string, i int) {
    s[i].User = user
    }

    type DboxFolder struct {
    Entries Entries `json:"entries"`
    Cursor string `json:"cursor"`
    HasMore bool `json:"has_more"`
    }

    type Meta struct {
    ClientModified string `json:"client_modified"`
    ContentHash string `json:"content_hash"`
    Id string `json:"id"`
    Name string `json:"name"`
    PathLower string `json:"path_lower"`
    PathDisplay string `json:"path_display"`
    Rev string `json:"rev"`
    ServerModified string `json:"server_modified"`
    Size int64 `json:"size"`
    Tag string `json:".tag"`
    ErrorSummary string `json:"error_summary"`
    Error DbxError `json:"error"`
    User string // to be set later on
    }

    type DbxError struct {
    Tag string `json:".tag"`
    Path struct {
    Tag string `json:".tag"`
    Conflict struct {
    Tag string `json:".tag"`
    } `json:"conflict"`
    } `json:"path"`
    }

    //create folder response
    type FolderMeta struct {
    Metadata struct {
    Id string `json:"id"`
    Name string `json:"name"`
    PathDisplay string `json:"path_display"`
    PathLower string `json:"path_lower"`
    } `json:"metadata"`
    ErrorSummary string `json:"error_summary"`
    Error DbxError `json:"error"`
    }


    type Metaset []Meta
    func (s Metaset) SetUser(user string, i int) {
    s[i].User = user
    }

    type Item struct {
    Bytes int64 `json:"bytes"`
    Icon string `json:"icon"`
    IsDeleted bool `json:"is_deleted"`
    IsDir bool `json:"is_dir"`
    Modified string `json:"modified"`
    Path string `json:"path"`
    ReadOnly bool `json:"read_only"`
    Rev string `json:"rev"`
    Revision int `json:"revision"`
    Root string `json:"root"`
    Size string `json:"size"`
    ThumbExists bool `json:"thumb_exists"`
    User string
    }


    type Dbox struct {
    Bytes int64 `json:"bytes"`
    Contents []Item `json:"contents"`
    Hash string `json:"hash"`
    Icon string `json:"icon"`
    IsDir bool `json:"is_dir"`
    Modified string `json:"modified"`
    Path string `json:"path"`
    ReadOnly bool `json:"read_only"`
    Rev string `json:"rev"`
    Revision int `json:"revision"`
    Root string `json:"root"`
    Size string `json:"size"`
    ThumbExists bool `json:"thumb_exists"`
    }

    type ByPath []Item

    func (a ByPath) Len() int { return len(a) }
    func (a ByPath) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
    func (a ByPath) Less(i, j int) bool { return a[i].Path < a[j].Path }

    type Itemset []Item
    func (s Itemset) SetUser(user string, i int) {
    s[i].User = user
    }

    type Cursor struct {
    SessionId string `json:"session_id"`
    Offset int64 `json:"offset"`
    }

    type Client struct {
    BaseUrl string
    CfgFile string
    User string
    Auth Auth
    Endpoints map[string]string
    }

    type Auth struct {
    TokenType string
    Token string
    }

    func NewClient(baseUrl, cfgFile, user string, auth Auth, endpoints map[string]string) (c *Client){
    return &Client{baseUrl, cfgFile, user, Auth{}, map[string]string{}}
    }

    func (c *Client) setToken(user string) string {
    config, err := yaml.Open(cfg_file)
    if err != nil {
    panic(err)
    }
    if user == "current_user" {
    user = to.String(config.Get("users", "current_user"))
    }
    token := to.String(config.Get(user, "access_token"))
    c.User = user
    c.Auth.TokenType = "Bearer"
    c.Auth.Token = token
    return token
    }

    //get user account information
    func (c *Client) Info(){
    status, body := c.apiRequest("POST", "/users/get_current_account", nil, nil, false)
    if status != "200 OK" {
    fmt.Println("error: bad server status:", status)
    fmt.Println(string(body))
    os.Exit(1)
    }
    info := Info{}
    err := json.Unmarshal([]byte(body), &info)
    if err != nil {fmt.Println(err); os.Exit(1)}

    status, body = c.apiRequest("POST", "/users/get_space_usage", nil, nil, false)
    if status != "200 OK" {
    fmt.Println("error: bad server status:", status)
    fmt.Println(string(body))
    os.Exit(1)
    }
    usage := SpaceUsage{}
    err = json.Unmarshal([]byte(body), &usage)
    if err != nil {fmt.Println(err); os.Exit(1)}

    left, _ := utils.NiceBytes(usage.Allocation.Allocated - usage.Used)
    quota, _ := utils.NiceBytes(usage.Allocation.Allocated)
    used, _ := utils.NiceBytes(usage.Used)
    fmt.Printf("Email: %s\nDisplay name: %s\nAccount Id: %s\n", info.Email, info.Name.DisplayName, info.AccountId)
    fmt.Printf("Quota: %d [%s]\n", usage.Allocation.Allocated, quota)
    fmt.Printf("Used: %d [%s]\n", usage.Used, used)
    fmt.Printf("Left space: %d byes [%s]\n", usage.Allocation.Allocated - usage.Used, left)
    }

    // generic api request
    func (c *Client) apiRequest(method, endpoint string, params interface{}, data interface{}, isJson bool) (string, []byte) {
    uri, err := url.Parse(c.BaseUrl)
    if err != nil {fmt.Println(err); os.Exit(1)}
    uri.Path += endpoint
    if params != nil {
    p := params.(map[string]string)
    q := uri.Query()
    for k, v := range p {
    q.Set(k, v)
    }
    uri.RawQuery = q.Encode()
    }
    var req *http.Request
    if data != nil {
    if isJson {
    form := data.(map[string]string)
    form_js, _ := json.Marshal(form)
    req, err = http.NewRequest(method, uri.String(), strings.NewReader(string(form_js)))
    } else {
    form := data.(url.Values)
    req, err = http.NewRequest(method, uri.String(), strings.NewReader(form.Encode()))
    }
    } else {
    req, err = http.NewRequest(method, uri.String(), nil)
    }
    if err != nil {fmt.Println(err); os.Exit(1)}
    req.Header.Set("Authorization", "Bearer "+c.Auth.Token)
    if method == "POST" || method == "PUT" {
    if isJson {
    req.Header.Add("Content-Type", "application/json")
    } else if data != nil {
    req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
    }
    }
    resp, err := http.DefaultClient.Do(req)
    if err != nil {
    fmt.Println("error:", err)
    fmt.Printf("request: %+v\n", req)
    os.Exit(1)
    }
    defer resp.Body.Close()
    switch {
    case method == "POST":
    if resp.StatusCode != 200 {
    if resp.StatusCode == 403 {
    fmt.Println("server status for %q request: %s", method, resp.Status)
    } else {
    fmt.Printf("error: bad server status for %q request: %s\n", method, resp.Status)
    }
    }
    default:
    if resp.StatusCode/10 != 20 {
    fmt.Printf("error: bad server status for %q request: %s\n", method, resp.Status)
    fmt.Printf("request: %+v\n", req)
    }
    }
    body, err := ioutil.ReadAll(resp.Body)
    if err != nil {fmt.Println(err); os.Exit(1)}
    return resp.Status, body
    }

    func (c *Client) getMetadata(path string) (meta Meta, err error){
    ep := "/files/alpha/get_metadata"
    params := map[string]string{"path":path}
    isJson := true
    status, body := c.apiRequest("POST", ep, nil, params, isJson)
    if status != "200 OK" {
    if strings.Contains(status, "path/not_file/"){
    meta.Tag = "folder"
    } else {
    err = fmt.Errorf("error: bad server status:", status+"\n"+string(body))
    }
    return
    } else {
    err = json.Unmarshal([]byte(body), &meta)
    return
    }
    }

    func fromJson(body []byte) (data DboxFolder) {
    err := json.Unmarshal(body, &data)
    if err != nil {
    fmt.Println("error while decoding response body:", err)
    fmt.Println("body", string(body))
    }
    return
    }

    func printRes(contents Entries) {
    sort.Sort(ByName(contents))
    for _, v := range contents {
    if v.Size == 0 {
    fmt.Println(v.Name)
    } else {
    filesize, _ := utils.NiceBytes(v.Size)
    fmt.Printf("%-68s %8s\n", v.Name, filesize)
    }
    }
    }

    func Legend(users map[string]string) string {
    legend := make([]string, len(users))
    i := 0
    for k, v := range users {
    legend[i] = fmt.Sprintf("[%s]=%s" , v, k)
    i += 1
    }
    return strings.Join(legend, ", ")
    }

    func printCompiled(contents []Entry) {
    for _, v := range contents {
    name := v.Name
    userId := users[v.User]
    if v.Size == 0 {
    fmt.Printf("[%s] %s\n", userId, name)
    } else {
    size, _ := utils.NiceBytes(v.Size)
    fmt.Printf("[%s] %-65s %s\n", userId, name, size)
    }
    }
    fmt.Printf("\n[%s]\n", Legend(users))
    }

    func (c *Client) getResource(path string) (data DboxFolder) {
    ep := "/files/list_folder"
    params := map[string]string{"path":path}
    isJson := true
    status, body := c.apiRequest("POST", ep, nil, params, isJson)
    if status != "200 OK" {
    fmt.Println("error: bad server status:", status+"\n"+string(body))
    } else {
    //fmt.Println(string(body))
    data = fromJson(body)
    }
    return
    }

    func (c *Client) listFolder(path string){
    fmt.Printf("User: %s\n", c.User)
    res := c.getResource(path)
    printRes(res.Entries)
    }


    func (c *Client) getTree(path string, depth, d int) {
    mx := 69
    i := strings.Repeat(" ", d)
    entries := c.getResource(path).Entries
    sort.Sort(ByName(entries))
    for _, e := range entries {
    name := e.Name
    if e.Size != 0 {
    if len(name) + len(i) > mx {
    name = utils.Shorten(name, mx - len(i))
    }
    if len(name) + len(i) < mx {
    name = utils.RightPad(name, " ", mx - (len(name) + len(i)))
    }
    fmt.Printf("%s%s %d\n", i, name, e.Size)
    } else {
    fmt.Printf("%s%s\n", i, name)
    if depth == 0 || d+1 < depth {
    c.getTree(e.PathLower, depth, d+1)
    }
    }
    }
    }

    func (c *Client) listAll(path string) {
    var compiled []Entry
    for user, _ := range users {
    c.setToken(user)
    res := c.getResource(path)
    var data Entries
    data = res.Entries
    for k, _ := range data { data.SetUser(user, k) }
    compiled = append(data, compiled...)
    }
    sort.Sort(ByName(compiled))
    printCompiled(compiled)
    }

    //type Link struct {
    // Expires string `json:"expires"`
    // Url string `json:"url"`
    //}

    type Link struct {
    Metadata Meta `json "metadata"`
    Link string `json:"link"`
    }

    // get a streamable link to a file
    func (c *Client) getLink(path string) (link Link, err error) {
    ep := "/files/get_temporary_link"
    data := map[string]string{"path":path}
    status, body := c.apiRequest("POST", ep, nil, data, true)
    if status != "200 OK" {
    err = fmt.Errorf("error: bad server status: "+status+"\n"+string(body))
    return
    }
    err = json.Unmarshal(body, &link)
    return
    }

    func (c *Client) getLinks(path string, stream bool) [][]string {
    links := [][]string{}
    meta, err := c.getMetadata(path)
    if err != nil {fmt.Println(err); os.Exit(2)}
    if meta.Tag == "folder" {
    folderName := pth.Base(path)
    res := c.getResource(path)
    items := res.Entries
    sort.Sort(ByName(items))
    for _, i := range items{
    if i.Tag != "folder" {
    if stream && !isAudioExt(pth.Ext(i.Name)) {
    continue
    }
    filePath := pth.Join(folderName, i.Name)
    if link, err := c.getLink(i.PathDisplay); err == nil {
    links = append(links, []string{filePath, link.Link})
    }
    }
    }
    } else {
    fileName := pth.Base(path)
    if link, err := c.getLink(path); err == nil {
    links = append(links, []string{fileName, link.Link})
    }
    }
    if !stream {
    for _, k := range links {
    fmt.Println(k)
    }
    }
    return links
    }

    func isAudioExt(p string) bool {
    return utils.StringInSlice(pth.Ext(p), audioTypes)
    }

    func (c *Client) streamLinks(path string) {
    vlc := "C:/Program Files (x86)/VideoLAN/VLC/vlc.exe"
    fb2k := "C:/Program Files (x86)/foobar2000/foobar2000.exe"
    playerName := map[string]string{vlc: "VLC", fb2k: "foobar2000"}

    links := c.getLinks(path, true)

    if len(links) == 0 {
    fmt.Printf("didn't find any registered audio type file in %s", path)
    os.Exit(0)
    }

    player := fb2k
    for _, k := range links {
    if utils.StringInSlice(pth.Ext(k[1]), unhandled) {
    player = vlc
    break
    }
    }
    fmt.Printf("got %d files\n", len(links))
    var args []string
    if player == fb2k { args = append(args, "/add")}
    for _, f := range links {
    fmt.Println(f[0])
    args = append(args, f[1])
    }
    if player == vlc { args = append(args, "--qt-start-minimized") }
    fmt.Println("\nlaunching", playerName[player])
    cmd := exec.Command(player, args...)
    cmd.Stdout = os.Stdout
    cmd.Stderr = os.Stderr
    err := cmd.Start()
    if err != nil {log.Fatal(err)}
    }

    func (c *Client) downloadFile(itemPath, filepath string, aria, fast bool, conns int) int64 {
    uri := "https://content.dropboxapi.com/2/files/download"
    auth := fmt.Sprintf("%s %s", c.Auth.TokenType, c.Auth.Token)
    p := map[string]string{"path":itemPath}
    params, _ := json.Marshal(p)
    switch {
    case fast:
    uri += fmt.Sprintf("?access_token=%s", c.Auth.Token)
    c.fastFileDownload(uri, conns, filepath)
    return 0
    case aria :
    cmd := exec.Command("aria2c" , "--file-allocation=falloc", "--max-connection-per-server=5" , "--min-split-size=1M", "--remote-time=true", "--header=Authorization:"+auth, "--header=Dropbox-API-Arg:"+string(params), uri, "-o "+filepath)
    cmd.Stdout = os.Stdout
    cmd.Stderr = os.Stderr
    err := cmd.Run()
    if err != nil {
    fmt.Println(err)
    }
    stat, err := os.Stat(filepath)
    if err != nil {panic(err)}
    return stat.Size()

    default:
    req, err := http.NewRequest("GET", uri, nil)
    req.Header.Add("Authorization", auth)
    req.Header.Add("Dropbox-API-Arg", string(params))
    client := &http.Client{}
    resp, err := client.Do(req)
    if err != nil {
    panic(err)
    }
    if resp.StatusCode != 200 {
    fmt.Println("error: bad server status:", resp.Status)
    return 0
    }
    defer resp.Body.Close()
    out, err := os.Create(filepath)
    if err != nil {panic(err)}
    defer out.Close()
    i, _ := strconv.Atoi(resp.Header.Get("Content-Length"))
    sourceSize := int64(i)
    source := resp.Body
    bar := pb.New(int(sourceSize)).SetUnits(pb.U_BYTES).SetRefreshRate(time.Millisecond * 10)
    if sourceSize >= 1024 * 1024 {
    bar.ShowSpeed = true
    }
    bar.Start()
    writer := io.MultiWriter(out, bar)
    n, err := io.Copy(writer, source)
    if err != nil {
    fmt.Println("Error while downloading", uri, "-", err)
    return 0
    }
    bar.Finish()
    return n
    }
    }


    func (c *Client) downsync(path, folderpath string, aria, fast bool, depth, r, conns int) {
    err := os.MkdirAll(folderpath, 0777)
    if err != nil {
    fmt.Println("error: could not create new folder", folderpath, ":", err)
    os.Exit(2)
    }
    res := c.getResource(path)
    items := res.Entries
    for _, e := range items {
    if e.Tag != "folder" {
    filepath := folderpath + "/" + e.Name
    fmt.Println("downloading", filepath)
    var dlfast bool
    if fast && e.Size >= 1024*1024 {
    dlfast = true
    }
    n := c.downloadFile(e.PathDisplay, filepath, aria, dlfast, conns)
    if !fast && (n != e.Size) {
    fmt.Printf("error: size mismatch, expected: %d bytes, actual: %d bytes\n", e.Size, n)
    }
    }
    if (r < depth || depth == 0) && (e.Tag == "folder") {
    c.downsync(e.PathDisplay, folderpath+"/"+e.Name , aria, fast, depth, r+1, conns)
    }
    }
    }


    func (c *Client) download(path, localPath string, aria, fast bool, depth, parallel, conns int) {

    meta, err := c.getMetadata(path)
    if err != nil {fmt.Println(err); os.Exit(2)}
    if meta.Tag != "folder" {
    c.downloadFile(path, localPath, aria, fast, conns)
    } else {
    data := c.getResource(path)
    if parallel > 0 {
    items := data.Entries
    c.parallelDownload(items, localPath, parallel)

    } else {
    c.downsync(path, localPath, aria, fast, depth, 1, conns)
    }
    }
    }

    func (c *Client) fastFileDownload(url string, conns int, outfile string) {
    d := fd.New()
    size, filename, err := d.Init(url, conns, outfile)
    var filesize string
    if f, err := utils.NiceBytes(int64(size)); err == nil {
    filesize = f
    }
    fmt.Printf("File size: %s; filename: %s\n", filesize, filename)
    if err != nil {
    fmt.Println(err)
    os.Exit(1)
    }
    d.StartDownload()
    go d.Wait()
    DisplayProgress(&d)
    }

    func DisplayProgress(dl *fd.Downloader) {
    barWidth := float64(37)
    for {
    status, total, downloaded, elapsed := dl.GetProgress()
    frac := float64(downloaded)/float64(total)
    bps, _ := utils.NiceBytes(int64(float64(downloaded)/elapsed.Seconds()))
    tot, _ := utils.NiceBytes(int64(total))
    if frac == 0 { continue }
    fmt.Fprintf(os.Stdout, "[%-38s] %5.1f%% of %10s %10s/s %3.fs\r",
    strings.Repeat("=", int(frac*barWidth))+">", frac*100, tot, bps, elapsed.Seconds())
    switch {
    case status == fd.Completed:
    fmt.Println("\nDownload successfully completed in", elapsed)
    return
    case status == fd.OnProgress: // needed?
    case status == fd.NotStarted: // needed?
    default:
    fmt.Printf("\nDownload failed: %s\n", status)
    os.Exit(1)
    }
    time.Sleep(time.Second)
    }
    }

    func (c *Client) parallelDownload(items []Entry, localFolder string, p int){
    var d, k int
    var dbytes int64
    err := os.Mkdir(localFolder, 0777)
    if err != nil {panic(err)}
    s := time.Now().Unix()
    for k < len(items) {
    var wg sync.WaitGroup
    for d = 0; d < p; d += 1 {
    if k+d < len(items) && items[k+d].Tag != "folder" {
    wg.Add(1)
    f := items[k+d]
    // anonymous func can be replaced by a named one
    // by passing a pointer to wg.SyncGroup
    // see example in "parallel_downloads.go"
    go func(f Entry) {
    defer wg.Done()
    uri := content_url + "/files/download"
    req, _ := http.NewRequest("GET", uri, nil)
    auth := fmt.Sprintf("%s %s", c.Auth.TokenType, c.Auth.Token)
    req.Header.Set("Authorization", auth)
    p := map[string]string{"path":f.PathDisplay}
    params, _ := json.Marshal(p)
    req.Header.Set("Dropbox-API-Arg", string(params))
    fmt.Println("downloading:", f.PathDisplay)
    resp, _ := http.DefaultClient.Do(req)
    defer resp.Body.Close()
    fmt.Println(resp.Status)
    fp := ospath.Join(localFolder, f.Name)
    out, err := os.Create(fp)
    if err != nil {panic(err)}
    defer out.Close()
    n, _ := io.Copy(out, resp.Body)
    dbytes += n
    }(f)
    }
    }
    wg.Wait()
    k += d
    }
    dtime := time.Now().Unix() - s
    fmt.Printf("downloaded %d bytes in %d seconds\n", dbytes, dtime)
    }

    func (c *Client) getFile(f Entry, wg *sync.WaitGroup, localFolder string) {
    p := map[string]string{"path": f.PathDisplay}
    uri := content_url + "/files/download"
    req, _ := http.NewRequest("GET", uri, nil)
    auth := fmt.Sprintf("%s %s", c.Auth.TokenType, c.Auth.Token)
    req.Header.Set("Authorization", auth)
    params, _ := json.Marshal(p)
    req.Header.Set("Dropbox-API-Arg", string(params))
    fmt.Println("downloading:", f.PathDisplay)
    resp, _ := http.DefaultClient.Do(req)
    defer resp.Body.Close()

    fmt.Println(resp.Status)
    filename := ospath.Base(f.PathDisplay)
    out, err := os.Create(ospath.Join(localFolder, filename))
    if err != nil {
    panic(err)
    }
    defer out.Close()
    io.Copy(out, resp.Body)
    wg.Done()
    }


    func (c *Client) pipedUpload(localPath, parent string) {
    filename := ospath.Base(localPath)
    remotePath := pth.Join(parent, filename)
    if remotePath[:1] != "/" {
    remotePath = "/" + remotePath
    }
    url := "https://content.dropboxapi.com/2/files/upload"
    p := map[string]string{"path":remotePath}
    params, _ := json.Marshal(p)
    input, err := os.Open(localPath)
    check(err)
    defer input.Close()
    stat, err := input.Stat()
    check(err)
    pipeOut, pipeIn := io.Pipe()
    fsize := stat.Size()
    bar := pb.New(int(fsize)).SetUnits(pb.U_BYTES)
    if fsize >= 1024 {
    bar.ShowSpeed = true
    }
    writer := io.Writer(pipeIn)
    // do the request concurrently
    var resp *http.Response
    done := make(chan error)

    go func() {
    req, err := http.NewRequest("POST", url, pipeOut)
    if err != nil {
    done <- err
    return
    }
    req.ContentLength = fsize
    req.Header.Set("Authorization", "Bearer "+c.Auth.Token)
    req.Header.Set("Dropbox-API-Arg", string(params))
    req.Header.Set("Content-Type", "application/octet-stream")
    log.Println("Created Request")
    bar.Start()
    resp, err = http.DefaultClient.Do(req)
    if err != nil {
    done <- err
    return
    }
    done <- nil
    }()
    out := io.MultiWriter(writer, bar)
    _, err = io.Copy(out, input)
    check(err)
    check(pipeIn.Close())
    check(<-done)
    bar.Finish()
    body, _ := ioutil.ReadAll(resp.Body)
    meta := Meta{}
    err = json.Unmarshal(body, &meta)
    if err != nil {
    fmt.Println(err, string(body))
    } else {
    fmt.Printf("%+v\n", meta)
    }
    }

    func check(err error) {
    _, file, line, _ := runtime.Caller(1)
    if err != nil {
    log.Fatalf("Fatal from <%s:%d>\nError:%s", file, line, err)
    }
    }

    func (c *Client) upsync(localPath, parent string) {
    fmt.Printf("creating folder %q in %q\n", ospath.Base(localPath), parent)
    parentPath := c.mkfolder(ospath.Base(localPath), parent)
    if parentPath == "409 Conflict" {
    parentPath = pth.Join(parent, ospath.Base(localPath))
    fmt.Println("conflict: folder %q already exists\n", parentPath)
    }
    dirlist, err := ioutil.ReadDir(localPath)
    if err != nil {panic(err)}
    for _, f := range dirlist {
    if !f.IsDir() && strings.ToLower(f.Name()) != "thumbs.db" {
    filepath := ospath.Join(localPath, f.Name())
    fmt.Printf("uploading %q to %q\n", filepath, parentPath)
    c.pipedUpload(filepath, parentPath)
    }
    if f.IsDir() {
    folderPath := ospath.Join(localPath, f.Name())
    c.upsync(folderPath, parentPath)
    }
    }
    }

    func (c *Client) upload(localPath, parent string) {
    stat, er := os.Stat(localPath)
    if er != nil {
    fmt.Println(er)
    os.Exit(2)
    }
    if !stat.IsDir(){
    c.pipedUpload(localPath, parent)
    } else {
    c.upsync(localPath, parent)
    }
    }

    func makeChunk(fh *os.File, offset int64) []byte {
    p := make([]byte, chunksize)
    //fmt.Println("offset:", offset)
    n, _ := fh.ReadAt(p, offset)
    return p[:n]
    }

    func (c *Client) startUploadSession(fh *os.File) (cursor Cursor){
    //Returns json {"session_id": <session_id>}

    uri, _ := url.Parse(content_url + "/files/upload_session/start")
    p := map[string]bool{"close": false}
    params, _ := json.Marshal(p)
    chunk := makeChunk(fh, 0)
    data := bytes.NewReader(chunk)
    req, err := http.NewRequest("POST", uri.String(), data)
    if err != nil {
    fmt.Println(err)
    fmt.Printf("request: %+v\n", req)
    os.Exit(2)
    }
    req.Header.Add("Authorization", "Bearer "+c.Auth.Token)
    req.Header.Add("Content-Type", "application/octet-stream")
    req.Header.Add("Dropbox-API-Arg", string(params))

    resp, err := http.DefaultClient.Do(req)
    if err != nil {
    fmt.Println(err)
    fmt.Printf("request: %+v\n", req)
    os.Exit(2)
    }
    defer resp.Body.Close()
    body, _ := ioutil.ReadAll(resp.Body)
    if resp.StatusCode != 200 {
    fmt.Println("error: bad server status:", resp.Status)
    fmt.Println(string(body))
    }
    err = json.Unmarshal(body, &cursor)
    if err != nil {
    fmt.Println(err)
    }
    return
    }

    func (c *Client) uploadSessionAppend(fh *os.File, cursor Cursor){
    //No return values.
    uri, _ := url.Parse(content_url + "/files/upload_session/append_v2")
    offset := cursor.Offset
    chunk := makeChunk(fh, offset)
    data := bytes.NewReader(chunk)
    req, err := http.NewRequest("POST", uri.String(), data)
    if err != nil {
    fmt.Println(err)
    fmt.Printf("request: %+v\n", req)
    os.Exit(2)
    }
    req.Header.Add("Authorization", "Bearer "+c.Auth.Token)
    req.Header.Add("Content-Type", "application/octet-stream")
    type Params struct {
    Cursor Cursor `json:"cursor"`
    Close bool `json:"close"`
    }
    p := Params{}
    p.Cursor = cursor
    p.Close = false
    params, _ := json.Marshal(p)
    //params map[string]Cursor{"cursor": cursor, "close": False}
    req.Header.Add("Dropbox-API-Arg", string(params))
    resp, err := http.DefaultClient.Do(req)
    if err != nil {
    fmt.Println(err)
    fmt.Printf("request: %+v\n", req)
    os.Exit(2)
    }
    defer resp.Body.Close()
    body, _ := ioutil.ReadAll(resp.Body)
    if resp.StatusCode != 200 {
    fmt.Println("error: bad server status:", resp.Status)
    fmt.Println("response body:", string(body))
    }
    }

    func (c *Client) uploadSessionFinish(fh *os.File, cursor Cursor, remote_path string) (res Meta) {
    //Returns file props.
    uri, _ := url.Parse(content_url + "/files/upload_session/finish")
    type Commit struct {
    Path string `json:"path"`
    Mode string `json:"mode"`
    Autorename bool `json:"autorename"`
    Mute bool `json:"mute"`
    }
    type Params struct {
    Cursor Cursor `json:"cursor"`
    Commit Commit `json:"commit"`
    }
    //params = map[string]{"cursor": cursor, "commit": {"path": remote_path, "mode": "add", "autorename": true, "mute": false}
    p := Params{}
    p.Cursor = cursor
    commit := Commit{Path: remote_path, Mode: "add", Autorename: true, Mute: false}
    p.Commit = commit
    params, _ := json.Marshal(p)
    offset := cursor.Offset
    chunk := makeChunk(fh, offset)
    data := bytes.NewReader(chunk)
    req, err := http.NewRequest("POST", uri.String(), data)
    if err != nil {
    fmt.Println(err)
    fmt.Printf("request: %+v\n", req)
    os.Exit(2)
    }
    req.Header.Add("Authorization", "Bearer "+c.Auth.Token)
    req.Header.Add("Dropbox-API-Arg", string(params))
    req.Header.Add("Content-Type", "application/octet-stream")
    resp, err := http.DefaultClient.Do(req)
    if err != nil {
    fmt.Println(err)
    fmt.Printf("request: %+v\n", req)
    os.Exit(2)
    }
    defer resp.Body.Close()
    body, _ := ioutil.ReadAll(resp.Body)
    if resp.StatusCode != 200 {
    fmt.Println("error: bad server status:", resp.Status)
    fmt.Println("response body:", string(body))
    }
    err = json.Unmarshal(body, &res)
    if err != nil {fmt.Println(err)}
    return
    }

    func (c *Client) chunkedUpload(localPath, parent string) {
    stat, err := os.Stat(localPath)
    if err != nil {
    fmt.Println(err)
    os.Exit(2)
    }
    filesize := stat.Size()
    fmt.Printf("filesize: %d bytes\n", filesize)
    fh, er := os.Open(localPath)
    if er != nil {fmt.Println(er); os.Exit(2)}
    if filesize <= chunksize {
    c.pipedUpload(localPath, parent)
    } else {
    remotePath := pth.Join(parent, ospath.Base(fh.Name()))
    if remotePath[:1] != "/" {
    remotePath = "/" + remotePath
    }
    fmt.Println("starting upload session")
    fmt.Println("remote path:", remotePath)
    cursor := c.startUploadSession(fh)
    cursor.Offset += chunksize
    position := chunksize
    fmt.Printf("cursor: %+v\n", cursor)
    for position < filesize {
    if ((filesize - position) <= chunksize){
    fmt.Println("Last chunk, finishing upload session...")
    finish := c.uploadSessionFinish(fh, cursor, remotePath)
    fmt.Printf("%+v\n", finish)
    position += chunksize
    } else {
    fmt.Println("appending data; offset:", cursor.Offset)
    c.uploadSessionAppend(fh, cursor)
    position += chunksize
    cursor.Offset = position
    }
    }
    }
    }

    type Search struct {
    Matches []Match `json:"matches"`
    More bool `json:"more"`
    Start int `json:"start"`
    }

    type Match struct {
    MatchType struct {
    Tag string `json:".tag"`
    } `json:"match_type"`
    Metadata Meta `json:"metadata"`
    }

    type Matchset []Match
    func(m Matchset) SetUser(user string, i int) {
    m[i].Metadata.User = user
    }

    func (c *Client) doSearch(path, query string) Search {
    var result Search
    ep := "/files/search"
    params := map[string]string{"path": path, "query":query}
    status, body := c.apiRequest("POST", ep, nil, params, true)
    if status == "200 OK" {
    err := json.Unmarshal(body, &result)
    if err != nil { fmt.Println(err);os.Exit(1) }
    }
    return result
    }


    func (c *Client) searchUser(path, query string) {
    result := c.doSearch(path, query)
    matches := result.Matches
    count := len(matches)
    if path == "" {
    path = "/"
    } else if path [:1] != "/" { path = "/"+path}
    fmt.Printf("found %d item(s) in %s:\n\n", count, path)
    for _,e := range matches {
    if e.Metadata.Tag != "folder" {
    size, _ := utils.NiceBytes(e.Metadata.Size)
    fmt.Printf("%-70s %8s\n", e.Metadata.Name, size)
    } else {
    fmt.Println(e.Metadata.Name)
    }
    }
    }

    func (c *Client) searchAll(path, query string){
    var res []Match
    for user, _ := range users {
    var result Matchset
    c.setToken(user)
    resp := c.doSearch(path, query)
    result = resp.Matches
    for k, _ := range result {
    result.SetUser(user, k)
    }
    res = append(result, res...)
    }
    //sort.Sort(ByName(res))
    if path == "" {
    path = "/"
    } else if path [:1] != "/" { path = "/"+path}
    fmt.Printf("found %d items in %v:\n\n", len(res), path)
    for _, e := range res {
    if e.Metadata.Tag != "folder" {
    size, _ := utils.NiceBytes(e.Metadata.Size)
    fmt.Printf("[%s] %-65s %8s\n", users[e.Metadata.User], e.Metadata.PathDisplay, size)
    } else {
    fmt.Printf("[%s] %s\n", users[e.Metadata.User], e.Metadata.PathDisplay)
    }
    }
    fmt.Printf("\n[%s]\n", Legend(users))
    }

    func (c *Client) mkfolder(foldername, parent string) (p string) {
    path := pth.Join(parent, foldername)
    if path[:1] != "/" {
    path = "/" + path
    }
    form := map[string]string{"path": path}
    var res FolderMeta
    status, body := c.apiRequest("POST", "/files/create_folder_v2", nil, form, true)
    if status != "200 OK" {
    fmt.Println("error: bad status:", status)
    if status != "409 Conflict" {
    return
    }
    }
    err := json.Unmarshal(body, &res)
    if err != nil {
    fmt.Println(err)
    return
    }
    fmt.Printf("%+v\n", res)
    if status == "409 Conflict" {
    return status
    }
    return res.Metadata.PathDisplay
    }

    func (c *Client) createFolder(foldername, parent string) {
    c.mkfolder(foldername, parent)
    }

    func (c *Client) move(src, dest string) {
    type Resp struct {
    Metadata Meta `json:"metadata"`
    }
    if src[:1] != "/"{
    src = "/" + src
    }
    form := map[string]string{"from_path": src, "to_path": dest}
    status, body := c.apiRequest("POST", "/files/move_v2", nil, form, true)
    if status != "200 OK" { fmt.Println("bad status:", status); os.Exit(1)}
    var res Resp
    err := json.Unmarshal(body, &res)
    if err != nil {fmt.Println(err); os.Exit(1)}
    if res.Metadata.Tag != "folder" {
    size, _ := utils.NiceBytes(res.Metadata.Size)
    fmt.Printf("path:%s size:%s\n", res.Metadata.PathDisplay, size)
    } else {
    fmt.Println("path:", res.Metadata.PathDisplay)
    }
    }

    func (c *Client) remove(path string) {
    type Resp struct {
    Metadata Meta `json:"metadata"`
    }
    if path[:1] != "/" {
    path = "/" + path
    }
    params := map[string]string{"path":path}
    status, body := c.apiRequest("POST", "/files/delete_v2", nil, params, true)
    if status != "200 OK" { fmt.Println("bad status:", status); os.Exit(1)}
    var res Resp
    err := json.Unmarshal(body, &res)
    if err != nil {panic(err)}
    fmt.Printf("%+v\n", res.Metadata)
    }

    func main() {
    userlist := Userlist()
    uids := Uids()
    app := cli.NewApp()
    app.Name = "dropbox"
    app.Version = "0.42"
    app.Usage = "client for the dropbox 'rest' APIv2"

    app.Flags = []cli.Flag {
    cli.StringFlag{
    Name: "user, u",
    Value: "current_user",
    Usage: fmt.Sprintf("user name, one of %s", strings.Join(userlist, ", ")),
    },
    cli.BoolFlag{
    Name: "info, i",
    Usage: "get account info for the current/specified user",
    },
    cli.BoolFlag{
    Name: "meta, M",
    Usage: "get metadata for the specified path",
    },
    cli.BoolFlag{
    Name: "tree, t",
    Usage: "recursively list the specified path",
    },
    cli.BoolFlag{
    Name: "all_users, a",
    Usage: "list the specified path for all users",
    },
    cli.BoolFlag{
    Name: "link, k",
    Usage: "get streamable link(s) for item(s) under the specified path",
    },
    cli.BoolFlag{
    Name: "play, S",
    Usage: "stream link(s) for item(s) under the specified path in foobar2000 or VLC",
    },
    cli.BoolFlag{
    Name: "download, d",
    Usage: "download file(s) under the specified path",
    },
    cli.IntFlag{
    Name: "depth, r",
    Value: 1,
    Usage: "recursion depth for folder downloading",
    },
    cli.BoolFlag{
    Name: "aria, x",
    Usage: "use external (and faster) aria2c downloader",
    },
    cli.BoolFlag{
    Name: "fast, f",
    Usage: "download using parallel connections (internal code)",
    },
    cli.IntFlag{
    Name: "conns, c",
    Value: 5,
    Usage: "number of connections for 'fast' (parallel) downloading",
    },
    cli.IntFlag{
    Name: "parallel, P",
    Value: 0,
    Usage: "use with --d to download a folder's files by batches of <n> parallel goroutines",
    },
    cli.StringFlag{
    Name: "mkfolder, m",
    Value: "",
    Usage: "create a new folder in the specified parent folder path",
    },
    cli.StringFlag{
    Name: "upload, p",
    Value: "",
    Usage: "upload file(s) to the specified parent folder path",
    },
    cli.StringFlag{
    Name: "chunked_upload, cu",
    Value: "",
    Usage: "upload large file(s) by chunks to the specified parent folder path",
    },
    cli.IntFlag{
    Name: "chunk_size, cs",
    Value: 0,
    Usage: "chunk size in MiB to the specified parent folder path",
    },
    cli.StringFlag{
    Name: "move, mv",
    Value: "",
    Usage: "move and/or rename item(s) from <src> to <dest> full path",
    },
    cli.StringFlag{
    Name: "search, s",
    Value: "",
    Usage: "search for the specified <query> string",
    },
    cli.BoolFlag{
    Name: "remove, rm",
    Usage: "remove item(s) at the specified path",
    },
    }
    app.Action = func(c *cli.Context) {
    user := c.String("user")
    path := ""
    if len(c.Args()) > 0 {
    path = c.Args()[0]
    }
    if user != "current_user" {
    if _, ok := users[user]; !ok {
    fmt.Printf("error: %q is not a registered user\n", user)
    fmt.Println("use one of", strings.Join(userlist, ", "))
    os.Exit(2)
    }
    }
    if utils.StringInSlice(strings.Split(path, "/")[0], userlist) {
    user = strings.Split(path, "/")[0]
    path = strings.Join(strings.Split(path, "/")[1:], "/")
    } else if utils.StringInSlice(strings.Split(path, "/")[0], uids) {
    var err error
    user, err = UidToUser(strings.Split(path, "/")[0])
    if err != nil {
    fmt.Println(err); os.Exit(1)
    }
    path = strings.Join(strings.Split(path, "/")[1:], "/")
    }
    if path != "" && path[:1] != "/" {path = "/"+path}

    d := NewClient(api_url, cfg_file, "", Auth{}, map[string]string{})
    if !c.Bool("all") { d.setToken(user) }
    switch {
    case c.Bool("tree"):
    depth := c.Int("depth")
    if depth == 1 {depth = 0}
    d.getTree(path, depth, 0)
    case c.Bool("info"):
    d.Info()
    case c.Bool("download"):
    localPath := pth.Base(path)
    d.download(path, localPath, c.Bool("aria"), c.Bool("fast"), c.Int("depth"), c.Int("parallel"), c.Int("conns"))
    case c.Bool("link"):
    stream := false
    d.getLinks(path, stream)
    case c.Bool("play"):
    d.streamLinks(path)
    case c.String("search") != "" :
    query := c.String("search")
    if c.Bool("all_users") {
    d.searchAll(path, query)
    } else {
    d.searchUser(path, query)
    }
    case c.String("move") != "" :
    d.move(c.String("move"), path)
    case c.String("upload") != "" :
    d.upload(c.String("upload"), path)
    case c.String("chunked_upload") != "" :
    if c.Int("chunk_size") > 0 {
    chunksize = int64(c.Int("chunk_size")*1024*1024)
    }
    d.chunkedUpload(c.String("chunked_upload"), path)
    case c.String("mkfolder") != "" :
    d.createFolder(c.String("mkfolder"), path)
    case c.Bool("remove"):
    if path == "/" {
    fmt.Println("error: cannot remove root folder")
    os.Exit(2)
    }
    d.remove(path)
    case c.Bool("meta"):
    meta, _ := d.getMetadata(path)
    fmt.Printf("%+v\n", meta)
    default:
    if c.Bool("all_users") {
    d.listAll(path)
    } else {
    d.listFolder(path)
    }
    }
    }
    app.Run(os.Args)
    }