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
14 changes: 7 additions & 7 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ jobs:
- uses: actions/checkout@v7
- uses: actions/setup-go@v7
with:
go-version: "1.25.12"
go-version: "1.25.13"
cache-dependency-path: go.sum
- run: go install golang.org/x/vuln/cmd/govulncheck@latest
- run: govulncheck ./...
Expand All @@ -36,7 +36,7 @@ jobs:
- uses: actions/checkout@v7
- uses: actions/setup-go@v7
with:
go-version: "1.25.12"
go-version: "1.25.13"
cache-dependency-path: go.sum
- run: go test -race ./...

Expand All @@ -47,7 +47,7 @@ jobs:
- uses: actions/checkout@v7
- uses: actions/setup-go@v7
with:
go-version: "1.25.12"
go-version: "1.25.13"
cache-dependency-path: go.sum
- uses: golangci/golangci-lint-action@v9
with:
Expand All @@ -60,7 +60,7 @@ jobs:
- uses: actions/checkout@v7
- uses: actions/setup-go@v7
with:
go-version: "1.25.12"
go-version: "1.25.13"
cache-dependency-path: go.sum
- run: go install github.com/rhysd/actionlint/cmd/actionlint@v1.7.12
- run: actionlint
Expand All @@ -72,7 +72,7 @@ jobs:
- uses: actions/checkout@v7
- uses: actions/setup-go@v7
with:
go-version: "1.25.12"
go-version: "1.25.13"
cache-dependency-path: go.sum
- run: go install golang.org/x/vuln/cmd/govulncheck@latest
- run: govulncheck ./...
Expand All @@ -88,7 +88,7 @@ jobs:
- uses: actions/checkout@v7
- uses: actions/setup-go@v7
with:
go-version: "1.25.12"
go-version: "1.25.13"
cache-dependency-path: go.sum
- run: go run ./tools/gen-docs
- run: go run ./tools/gen-json-schemas
Expand All @@ -104,7 +104,7 @@ jobs:
- uses: actions/checkout@v7
- uses: actions/setup-go@v7
with:
go-version: "1.25.12"
go-version: "1.25.13"
cache-dependency-path: go.sum
- name: Build Windows binary
shell: pwsh
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ jobs:
- uses: actions/checkout@v7
- uses: actions/setup-go@v7
with:
go-version: "1.25.12"
go-version: "1.25.13"
cache-dependency-path: go.sum

- name: Initialize CodeQL
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ jobs:
- uses: actions/checkout@v7
- uses: actions/setup-go@v7
with:
go-version: "1.25.12"
go-version: "1.25.13"
cache-dependency-path: go.sum
- name: Set release version
id: version
Expand Down
79 changes: 28 additions & 51 deletions cmd/get.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import (

"github.com/dropbox/dbxcli/v3/internal/output"
"github.com/dropbox/dropbox-sdk-go-unofficial/v6/dropbox/files"
"github.com/dropbox/dropbox-sdk-go-unofficial/v6/dropbox/filetransfer"
"github.com/dustin/go-humanize"
"github.com/mitchellh/ioprogress"
"github.com/spf13/cobra"
Expand Down Expand Up @@ -416,20 +417,18 @@ func downloadFileWithMetadata(
dstExplicit bool,
errOut io.Writer,
) (*files.FileMetadata, string, error) {
if !isExportOnlyFile(metadata) {
result, err := downloadFileOnce(dbx, src, dst, errOut)
return result, dst, err
}

var result *files.FileMetadata
actualDst := dst

err := retryWithBackoff(func() error {
var err error
if isExportOnlyFile(metadata) {
result, actualDst, err = exportFileToPath(dbx, src, dst, dstExplicit)
} else {
arg := files.NewDownloadArg(src)
result, err = downloadFileOnce(dbx, arg, dst, errOut)
}
result, actualDst, err = exportFileToPath(dbx, src, dst, dstExplicit)
return err
})

return result, actualDst, err
}

Expand Down Expand Up @@ -473,51 +472,36 @@ func downloadDestinationPath(dst string) (string, error) {
return "", fmt.Errorf("too many symlinks resolving %s", dst)
}

func downloadFileOnce(dbx filesClient, arg *files.DownloadArg, dst string, errOut io.Writer) (*files.FileMetadata, error) {
res, contents, err := dbx.DownloadContext(currentContext(), arg)
if err != nil {
return nil, err
}
defer func() { _ = contents.Close() }()

func downloadFileOnce(dbx filesClient, src string, dst string, errOut io.Writer) (*files.FileMetadata, error) {
finalDst, err := downloadDestinationPath(dst)
if err != nil {
return nil, err
}

f, tmp, err := createDownloadTemp(finalDst)
if err != nil {
return nil, err
if errOut == nil {
errOut = io.Discard
}
removeTemp := true
defer func() {
if removeTemp {
_ = os.Remove(tmp)
}
}()

progressbar := &ioprogress.Reader{
Reader: contents,
DrawFunc: ioprogress.DrawTerminalf(errOut, func(progress, total int64) string {
return fmt.Sprintf("Downloading %s/%s",
humanize.IBytes(uint64(progress)), humanize.IBytes(uint64(total)))
}),
Size: downloadMetadataSize(res),
}
draw := ioprogress.DrawTerminalf(errOut, func(progress, total int64) string {
return fmt.Sprintf("Downloading %s/%s",
humanize.IBytes(uint64(progress)), humanize.IBytes(uint64(total)))
})
defer func() { _ = draw(-1, -1) }()

_, copyErr := io.Copy(f, progressbar)
closeErr := f.Close()
if copyErr != nil {
return nil, copyErr
}
if closeErr != nil {
return nil, closeErr
}
if err := os.Rename(tmp, finalDst); err != nil {
result, err := filetransfer.NewDownloader(dbx).Download(
currentContext(),
src,
filetransfer.File(finalDst),
filetransfer.DownloadOptions{
MaxAttempts: maxRetries + 1,
Progress: func(progress filetransfer.DownloadProgress) {
_ = draw(progress.BytesCommitted, progress.TotalBytes)
},
},
)
if err != nil {
return nil, err
}
removeTemp = false
return res, nil
return result.Metadata, nil
}

func exportFile(
Expand Down Expand Up @@ -576,13 +560,6 @@ func exportFileToPath(dbx filesClient, src string, dst string, dstExplicit bool)
return res.FileMetadata, dst, nil
}

func downloadMetadataSize(metadata *files.FileMetadata) int64 {
if metadata == nil {
return 0
}
return int64(metadata.Size)
}

func isExportOnlyFile(metadata *files.FileMetadata) bool {
return metadata != nil &&
metadata.ExportInfo != nil &&
Expand Down
12 changes: 5 additions & 7 deletions cmd/get_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,6 @@ func TestGetDownloadWithRetry(t *testing.T) {
}

func TestGetDownloadRetriesBodyReadError(t *testing.T) {
delays := stubRetrySleep(t)
tmpDir := t.TempDir()
dst := filepath.Join(tmpDir, "downloaded.txt")
content := "complete file content"
Expand All @@ -234,9 +233,12 @@ func TestGetDownloadRetriesBodyReadError(t *testing.T) {
Size: uint64(len(content)),
}
if calls == 1 {
return meta, &failingReadCloser{data: []byte("partial")}, nil
return meta, &failingReadCloser{data: []byte(content[:7])}, nil
}
return meta, io.NopCloser(strings.NewReader(content)), nil
if got := arg.ExtraHeaders["Range"]; got != "bytes=7-" {
t.Errorf("retry range = %q, want bytes=7-", got)
}
return meta, io.NopCloser(strings.NewReader(content[7:])), nil
},
}

Expand All @@ -247,10 +249,6 @@ func TestGetDownloadRetriesBodyReadError(t *testing.T) {
if calls != 2 {
t.Errorf("expected 2 calls, got %d", calls)
}
if len(*delays) != 1 {
t.Fatalf("expected 1 sleep, got %d", len(*delays))
}

got, err := os.ReadFile(dst)
if err != nil {
t.Fatalf("failed to read downloaded file: %v", err)
Expand Down
10 changes: 8 additions & 2 deletions contrib/test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
set -e

dbxcli=$(realpath $1)
tmpdir=$(mktemp -d)
trap 'rm -rf "${tmpdir}"' EXIT
echo "Testing binary at ${dbxcli}"

echo "Testing version"
Expand All @@ -19,9 +21,13 @@ echo "Testing put"
${dbxcli} put ${dbxcli} ${d}/dbxcli

echo "Testing get"
${dbxcli} get ${d}/dbxcli /tmp/dbxcli
${dbxcli} get ${d}/dbxcli "${tmpdir}/dbxcli"
# Make sure files are the same
cmp --silent ${dbxcli} /tmp/dbxcli
cmp --silent "${dbxcli}" "${tmpdir}/dbxcli"

echo "Testing get to stdout"
${dbxcli} get ${d}/dbxcli - > "${tmpdir}/dbxcli-stdout"
cmp --silent "${dbxcli}" "${tmpdir}/dbxcli-stdout"

echo "Testing ls -l"
${dbxcli} ls -l ${d}
Expand Down