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
23 changes: 23 additions & 0 deletions blob/blob.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ import (
"fmt"
"hash"
"io"
"iter"
"log"
"mime"
"net/http"
Expand Down Expand Up @@ -611,6 +612,28 @@ func (i *ListIterator) Next(ctx context.Context) (*ListObject, error) {
return i.Next(ctx)
}

// All iterates over the iterator, returning a *ListObject and a download function for each entry.
func (i *ListIterator) All(ctx context.Context, err *error) iter.Seq2[*ListObject, func(io.Writer, *ReaderOptions) error] {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You need to say more about the err arg.
But there is a better way: instead of the arg, return a func() error.

  • The compiler won't let you ignore it, as it would with the arg.
  • You can catch early calls (before the iterator is done).

return func(yield func(*ListObject, func(io.Writer, *ReaderOptions) error) bool) {
for {
obj, itErr := i.Next(ctx)
if itErr == io.EOF {
return
}
if itErr != nil {
*err = itErr
return
}
downloadFunc := func(w io.Writer, opts *ReaderOptions) error {
return i.b.Download(ctx, obj.Key, w, opts)
}
if !yield(obj, downloadFunc) {
return
}
}
}
}

// ListObject represents a single blob returned from List.
type ListObject struct {
// Key is the key for this blob.
Expand Down
46 changes: 46 additions & 0 deletions blob/blob_iter_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package blob_test

import (
"bytes"
"context"
"fmt"
"testing"

"github.com/google/go-cmp/cmp"
"gocloud.dev/blob/memblob"
)

// Verify ListIterator.All.
func TestListIterator_All(t *testing.T) {
ctx := context.Background()
b := memblob.OpenBucket(nil)
defer b.Close()

// Initialize the bucket with some keys.
want := map[string]string{}
for _, key := range []string{"a", "b", "c"} {
contents := fmt.Sprintf("%s-contents", key)
if err := b.WriteAll(ctx, key, []byte(contents), nil); err != nil {
t.Fatalf("failed to initialize key %q: %v", key, err)
}
want[key] = contents
}

// Iterate over the bucket using iter.All.
var err error
got := map[string]string{}
iter := b.List(nil)
for obj, download := range iter.All(ctx, &err) {
var buf bytes.Buffer
if dErr := download(&buf, nil); dErr != nil {
t.Errorf("failed to download %q: %v", obj.Key, dErr)
}
got[obj.Key] = string(buf.Bytes())
}
if err != nil {
t.Fatalf("iteration failed: %v", err)
}
if diff := cmp.Diff(got, want); diff != "" {
t.Errorf("got %v, want %v, diff %s", got, want, diff)
}
}
18 changes: 18 additions & 0 deletions blob/example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
package blob_test

import (
"bytes"
"context"
"fmt"
"io"
Expand Down Expand Up @@ -219,12 +220,29 @@ func ExampleBucket_List() {
fmt.Println(obj.Key)
}

// Alternatively, use All to iterate (and optionally download):
fmt.Println()
fmt.Println("Now, using an iterator:")
iter = bucket.List(nil)
for obj, download := range iter.All(ctx, &err) {
var buf bytes.Buffer
_ = download(&buf, nil) // ignore error and use default ReaderOptions
fmt.Printf("%s: %s\n", obj.Key, string(buf.Bytes()))
}

// Output:
// foo0.txt
// foo1.txt
// foo2.txt
// foo3.txt
// foo4.txt
//
// Now, using an iterator:
// foo0.txt: Go Cloud Development Kit
// foo1.txt: Go Cloud Development Kit
// foo2.txt: Go Cloud Development Kit
// foo3.txt: Go Cloud Development Kit
// foo4.txt: Go Cloud Development Kit
}

func ExampleBucket_List_withDelimiter() {
Expand Down
Loading