From af57809d60ab64810b308e005ff41cc546744375 Mon Sep 17 00:00:00 2001 From: Andrey Markelov Date: Mon, 17 Aug 2026 15:29:41 -0700 Subject: [PATCH 1/2] Use reliable SDK downloads --- cmd/get.go | 79 ++++++++++++++++++------------------------------- cmd/get_test.go | 12 ++++---- contrib/test.sh | 10 +++++-- 3 files changed, 41 insertions(+), 60 deletions(-) diff --git a/cmd/get.go b/cmd/get.go index bcf54aba..4d3323b1 100644 --- a/cmd/get.go +++ b/cmd/get.go @@ -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" @@ -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 } @@ -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( @@ -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 && diff --git a/cmd/get_test.go b/cmd/get_test.go index a85640d7..79481c44 100644 --- a/cmd/get_test.go +++ b/cmd/get_test.go @@ -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" @@ -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 }, } @@ -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) diff --git a/contrib/test.sh b/contrib/test.sh index 5e7964de..44d5e499 100755 --- a/contrib/test.sh +++ b/contrib/test.sh @@ -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" @@ -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} From 7eaecd966c66135bd3dd5e7a2c338bf5afff85f8 Mon Sep 17 00:00:00 2001 From: Andrey Markelov Date: Mon, 17 Aug 2026 16:36:05 -0700 Subject: [PATCH 2/2] Update go to 1.25.13 --- .github/workflows/ci.yml | 14 +++++++------- .github/workflows/codeql.yml | 2 +- .github/workflows/release.yml | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 713ef80d..29c6e8b4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -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 ./... @@ -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 ./... @@ -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: @@ -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 @@ -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 ./... @@ -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 @@ -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 diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index c8549aa0..01bffa6e 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -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 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 32c3a401..25b12e45 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -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