Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 24 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -143,12 +143,14 @@ $ dbxcli du --output=json
$ dbxcli ls --output=json /
$ dbxcli search --output=json report /Reports
$ dbxcli revs --output=json /Reports/old.pdf
$ dbxcli share-link create --output=json /Reports/old.pdf
$ dbxcli share-link list --output=json /Reports/old.pdf
$ dbxcli mkdir --output=json /new-folder
$ dbxcli rm --output=json /old-file.txt
$ dbxcli restore --output=json /Reports/old.pdf 015f...
```

JSON support is rolling out command by command. Currently migrated commands are `account`, `du`, `ls`, `search`, `revs`, `mkdir`, `rm`, and `restore`. Commands that have not been migrated return a JSON error whose `error.message` is `structured output is not supported for this command yet` when used with `--output=json`.
JSON support is rolling out command by command. Currently migrated commands are `account`, `du`, `ls`, `search`, `revs`, `share-link create`, `share-link list`, `share-link info`, `share-link update`, `share-link revoke`, `share-link download`, `mkdir`, `rm`, and `restore`. Commands that have not been migrated return a JSON error whose `error.message` is `structured output is not supported for this command yet` when used with `--output=json`.

Command results are written to stdout. Status, progress, warnings, diagnostics, and verbose logs are written to stderr.

Expand Down Expand Up @@ -245,6 +247,27 @@ Account and usage commands return command-specific objects:
}
```

Shared-link commands return command-specific objects built around shared-link metadata:

```json
{
"input": {
"path": "/Reports/old.pdf"
},
"result": {
"type": "file",
"url": "https://www.dropbox.com/s/...",
"name": "old.pdf",
"path_lower": "/reports/old.pdf",
"rev": "...",
"size": 123
},
"existing": false
}
```

`share-link download --output=json <url> -` is not supported because stdout is reserved for downloaded file bytes when the target is `-`.

In JSON mode, command errors are also written to stdout. The process still exits with a non-zero status:

```json
Expand Down
25 changes: 24 additions & 1 deletion cmd/share-list-links.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,16 @@ import (
"github.com/spf13/cobra"
)

type shareLinkListInput struct {
Path string `json:"path,omitempty"`
DirectOnly bool `json:"direct_only"`
}

type shareLinkListOutput struct {
Input shareLinkListInput `json:"input"`
Entries []shareLinkJSONMetadata `json:"entries"`
}

func shareListLinks(cmd *cobra.Command, args []string) (err error) {
return shareLinkList(cmd, args)
}
Expand Down Expand Up @@ -54,8 +64,19 @@ func shareLinkList(cmd *cobra.Command, args []string) error {
commandVerboseStatus(cmd, "Listed %d shared links", len(links))
}

return commandOutput(cmd).RenderText(func(w io.Writer) error {
entries, ok := shareLinkJSONMetadataListFromDropbox(links)
if !ok {
return errors.New("found unknown shared link type")
}

return commandOutput(cmd).Render(func(w io.Writer) error {
return renderSharedLinks(w, links)
}, shareLinkListOutput{
Input: shareLinkListInput{
Path: arg.Path,
DirectOnly: arg.DirectOnly,
},
Entries: entries,
})
}

Expand Down Expand Up @@ -136,4 +157,6 @@ var shareListLinksCmd = &cobra.Command{
func init() {
shareLinkCmd.AddCommand(shareLinkListCmd)
shareListCmd.AddCommand(shareListLinksCmd)
enableStructuredOutput(shareLinkListCmd)
enableStructuredOutput(shareListLinksCmd)
}
49 changes: 48 additions & 1 deletion cmd/share_link_create.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,23 @@ type shareLinkCreateOptions struct {
password sharedLinkPasswordOptions
}

type shareLinkCreateInput struct {
Path string `json:"path"`
Access string `json:"access,omitempty"`
Audience string `json:"audience,omitempty"`
Expires string `json:"expires,omitempty"`
RemoveExpiration bool `json:"remove_expiration,omitempty"`
AllowDownload bool `json:"allow_download,omitempty"`
DisallowDownload bool `json:"disallow_download,omitempty"`
Password bool `json:"password,omitempty"`
}

type shareLinkCreateOutput struct {
Input shareLinkCreateInput `json:"input"`
Result shareLinkJSONMetadata `json:"result"`
Existing bool `json:"existing"`
}

func shareLinkCreate(cmd *cobra.Command, args []string) error {
if len(args) != 1 {
return errors.New("`share-link create` requires a `path` argument")
Expand Down Expand Up @@ -81,12 +98,41 @@ func shareLinkCreate(cmd *cobra.Command, args []string) error {
commandVerboseStatus(cmd, "Created shared link for %s", path)
}

return out.RenderText(func(w io.Writer) error {
result, ok := shareLinkJSONMetadataFromDropbox(link)
if !ok {
return errors.New("found unknown shared link type")
}

return out.Render(func(w io.Writer) error {
_, err := fmt.Fprintln(w, url)
return err
}, shareLinkCreateOutput{
Input: newShareLinkCreateInput(path, opts),
Result: result,
Existing: usedExisting,
})
}

func newShareLinkCreateInput(path string, opts shareLinkCreateOptions) shareLinkCreateInput {
input := shareLinkCreateInput{
Path: path,
RemoveExpiration: opts.removeExpiration,
AllowDownload: opts.allowDownload,
DisallowDownload: opts.disallowDownload,
Password: opts.password.set,
}
if opts.access != nil {
input.Access = opts.access.Tag
}
if opts.audience != nil {
input.Audience = opts.audience.Tag
}
if opts.expires != nil {
input.Expires = opts.expires.UTC().Format(time.RFC3339)
}
return input
}

func createSharedLink(dbx sharedLinkClient, path string, opts shareLinkCreateOptions) (sharing.IsSharedLinkMetadata, error) {
if opts.disallowDownload {
return dbx.CreateSharedLinkWithRawSettings(path, rawSharedLinkSettingsFromCreateOptions(opts))
Expand Down Expand Up @@ -436,4 +482,5 @@ func init() {
shareLinkCreateCmd.Flags().Bool("remove-expiration", false, "Remove expiration when returning an existing shared link")
addSharedLinkPasswordFlags(shareLinkCreateCmd)
shareLinkCmd.AddCommand(shareLinkCreateCmd)
enableStructuredOutput(shareLinkCreateCmd)
}
68 changes: 59 additions & 9 deletions cmd/share_link_download.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"path/filepath"
"strings"

"github.com/dropbox/dbxcli/internal/output"
"github.com/dropbox/dropbox-sdk-go-unofficial/v6/dropbox/files"
"github.com/dropbox/dropbox-sdk-go-unofficial/v6/dropbox/sharing"
"github.com/dustin/go-humanize"
Expand All @@ -36,6 +37,24 @@ type shareLinkDownloadOptions struct {
recursive bool
}

type shareLinkDownloadInput struct {
URL string `json:"url"`
Target string `json:"target,omitempty"`
Path string `json:"path,omitempty"`
Recursive bool `json:"recursive,omitempty"`
Password bool `json:"password,omitempty"`
}

type shareLinkDownloadResult struct {
Target string `json:"target"`
Link shareLinkJSONMetadata `json:"link"`
}

type shareLinkDownloadOutput struct {
Input shareLinkDownloadInput `json:"input"`
Result shareLinkDownloadResult `json:"result"`
}

func shareLinkDownload(cmd *cobra.Command, args []string) error {
if len(args) == 0 || len(args) > 2 {
return errors.New("`share-link download` requires a `url` and optional `target` argument")
Expand All @@ -62,11 +81,14 @@ func shareLinkDownload(cmd *cobra.Command, args []string) error {
if opts.password.set {
arg.LinkPassword = opts.password.password
}
if target == "-" && commandOutputFormat(cmd) == output.FormatJSON {
return errors.New("`share-link download -` cannot be used with --output=json")
}

dbx := newSharedLinkClient(config)
if opts.path != "" {
arg.Path = opts.path
return downloadSharedLinkPath(cmd, dbx, arg, target)
return downloadSharedLinkPath(cmd, dbx, arg, target, opts)
}

link, err := dbx.GetSharedLinkMetadata(arg)
Expand All @@ -90,7 +112,7 @@ func shareLinkDownload(cmd *cobra.Command, args []string) error {
return err
}
commandVerboseStatus(cmd, "Downloaded shared link folder to %s", dst)
return nil
return renderShareLinkDownloadOutput(cmd, newShareLinkDownloadInput(url, target, opts), dst, folder)
}

if target == "-" {
Expand All @@ -104,12 +126,12 @@ func shareLinkDownload(cmd *cobra.Command, args []string) error {
return nil
}

dst, err := downloadSharedLinkToFile(dbx, arg, target, cmd.ErrOrStderr())
dst, downloaded, err := downloadSharedLinkToFile(dbx, arg, target, cmd.ErrOrStderr())
if err != nil {
return err
}
commandVerboseStatus(cmd, "Downloaded shared link to %s", dst)
return nil
return renderShareLinkDownloadOutput(cmd, newShareLinkDownloadInput(url, target, opts), dst, downloaded)
}

func parseShareLinkDownloadOptions(cmd *cobra.Command) (shareLinkDownloadOptions, error) {
Expand Down Expand Up @@ -152,7 +174,7 @@ func parseShareLinkDownloadOptions(cmd *cobra.Command) (shareLinkDownloadOptions
return opts, nil
}

func downloadSharedLinkPath(cmd *cobra.Command, dbx sharedLinkClient, arg *sharing.GetSharedLinkMetadataArg, target string) error {
func downloadSharedLinkPath(cmd *cobra.Command, dbx sharedLinkClient, arg *sharing.GetSharedLinkMetadataArg, target string, opts shareLinkDownloadOptions) error {
if target == "-" {
if err := downloadSharedLinkToStdout(dbx, arg, cmd.OutOrStdout()); err != nil {
return err
Expand All @@ -161,12 +183,12 @@ func downloadSharedLinkPath(cmd *cobra.Command, dbx sharedLinkClient, arg *shari
return nil
}

dst, err := downloadSharedLinkToFile(dbx, arg, target, cmd.ErrOrStderr())
dst, downloaded, err := downloadSharedLinkToFile(dbx, arg, target, cmd.ErrOrStderr())
if err != nil {
return err
}
commandVerboseStatus(cmd, "Downloaded shared link path %s to %s", arg.Path, dst)
return nil
return renderShareLinkDownloadOutput(cmd, newShareLinkDownloadInput(arg.Url, target, opts), dst, downloaded)
}

func sharedLinkFolderDownloadTarget(target string, link *sharing.FolderLinkMetadata) (string, error) {
Expand Down Expand Up @@ -354,8 +376,9 @@ func sharedLinkLocalPath(root, rel string) (string, error) {
return filepath.Join(root, localRel), nil
}

func downloadSharedLinkToFile(dbx sharedLinkClient, arg *sharing.GetSharedLinkMetadataArg, target string, errOut io.Writer) (string, error) {
func downloadSharedLinkToFile(dbx sharedLinkClient, arg *sharing.GetSharedLinkMetadataArg, target string, errOut io.Writer) (string, sharing.IsSharedLinkMetadata, error) {
var dst string
var downloaded sharing.IsSharedLinkMetadata
err := retryWithBackoff(func() error {
link, contents, err := dbx.GetSharedLinkFile(arg)
if err != nil {
Expand All @@ -370,10 +393,11 @@ func downloadSharedLinkToFile(dbx sharedLinkClient, arg *sharing.GetSharedLinkMe
if err != nil {
return err
}
downloaded = link

return copySharedLinkContentToFile(contents, sharedLinkDownloadSize(link), dst, errOut)
})
return dst, err
return dst, downloaded, err
}

func downloadSharedLinkToStdout(dbx sharedLinkClient, arg *sharing.GetSharedLinkMetadataArg, w io.Writer) error {
Expand Down Expand Up @@ -496,6 +520,31 @@ func sharedLinkDownloadSize(link sharing.IsSharedLinkMetadata) uint64 {
return file.Size
}

func newShareLinkDownloadInput(url, target string, opts shareLinkDownloadOptions) shareLinkDownloadInput {
return shareLinkDownloadInput{
URL: url,
Target: target,
Path: opts.path,
Recursive: opts.recursive,
Password: opts.password.set,
}
}

func renderShareLinkDownloadOutput(cmd *cobra.Command, input shareLinkDownloadInput, target string, link sharing.IsSharedLinkMetadata) error {
result, ok := shareLinkJSONMetadataFromDropbox(link)
if !ok {
return errors.New("found unknown shared link type")
}

return commandOutput(cmd).Render(nil, shareLinkDownloadOutput{
Input: input,
Result: shareLinkDownloadResult{
Target: target,
Link: result,
},
})
}

var shareLinkDownloadCmd = &cobra.Command{
Use: "download <url> [target]",
Short: "Download shared link content",
Expand All @@ -518,4 +567,5 @@ func init() {
shareLinkDownloadCmd.Flags().String("path", "", "Download a file path inside a folder shared link")
shareLinkDownloadCmd.Flags().BoolP("recursive", "r", false, "Recursively download a folder shared link")
shareLinkCmd.AddCommand(shareLinkDownloadCmd)
enableStructuredOutput(shareLinkDownloadCmd)
}
26 changes: 25 additions & 1 deletion cmd/share_link_info.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,17 @@ type shareLinkInfoOptions struct {
password sharedLinkPasswordOptions
}

type shareLinkInfoInput struct {
URL string `json:"url"`
Path string `json:"path,omitempty"`
Password bool `json:"password,omitempty"`
}

type shareLinkInfoOutput struct {
Input shareLinkInfoInput `json:"input"`
Result shareLinkJSONMetadata `json:"result"`
}

func shareLinkInfo(cmd *cobra.Command, args []string) error {
if len(args) != 1 {
return errors.New("`share-link info` requires a `url` argument")
Expand Down Expand Up @@ -60,8 +71,20 @@ func shareLinkInfo(cmd *cobra.Command, args []string) error {
return err
}

return commandOutput(cmd).RenderText(func(w io.Writer) error {
result, ok := shareLinkJSONMetadataFromDropbox(link)
if !ok {
return errors.New("found unknown shared link type")
}

return commandOutput(cmd).Render(func(w io.Writer) error {
return renderSharedLinkInfo(w, link)
}, shareLinkInfoOutput{
Input: shareLinkInfoInput{
URL: url,
Path: opts.path,
Password: opts.password.set,
},
Result: result,
})
}

Expand Down Expand Up @@ -166,4 +189,5 @@ func init() {
shareLinkInfoCmd.Flags().String("path", "", "Display metadata for a path inside the shared link")
addSharedLinkPasswordFlags(shareLinkInfoCmd)
shareLinkCmd.AddCommand(shareLinkInfoCmd)
enableStructuredOutput(shareLinkInfoCmd)
}
Loading
Loading