// Copyright 2018, Google // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package b2 import ( "context" "io" "sync" ) // List returns an iterator for selecting objects in a bucket. The default // behavior, with no options, is to list all currently un-hidden objects. func (b *Bucket) List(opts ...ListOption) *ObjectIterator { o := &ObjectIterator{ bucket: b, } for _, opt := range opts { opt(&o.opts) } return o } // ObjectIterator abtracts away the tricky bits of iterating over a bucket's // contents. // // It is intended to be called in a loop: // for iter.Next(ctx) { // obj := iter.Object() // // act on obj // } // if err := iter.Err(); err != nil { // // handle err // } type ObjectIterator struct { bucket *Bucket final bool err error idx int c *Cursor opts objectIteratorOptions objs []*Object init sync.Once l lister count int } type lister func(context.Context, int, *Cursor) ([]*Object, *Cursor, error) func (o *ObjectIterator) frame(ctx context.Context) error { objs, c, err := o.l(ctx, o.count, o.c) if err != nil && err != io.EOF { if bNotExist.MatchString(err.Error()) { return b2err{ err: err, notFoundErr: true, } } return err } o.c = c o.objs = objs o.idx = 0 if err == io.EOF { o.final = true } return nil } // Next advances the iterator to the next object. It should be called before // any calls to Object(). If Next returns true, then the next call to Object() // will be valid. Once Next returns false, it is important to check the return // value of Err(). func (o *ObjectIterator) Next(ctx context.Context) bool { o.init.Do(func() { o.count = 1000 switch { case o.opts.unfinished: o.l = o.bucket.ListUnfinishedLargeFiles o.count = 100 case o.opts.hidden: o.l = o.bucket.ListObjects default: o.l = o.bucket.ListCurrentObjects } o.c = &Cursor{ Prefix: o.opts.prefix, Delimiter: o.opts.delimiter, } }) if o.err != nil { return false } if o.idx >= len(o.objs) { if o.final { o.err = io.EOF return false } if err := o.frame(ctx); err != nil { o.err = err return false } return o.Next(ctx) } o.idx++ return true } // Object returns the current object. func (o *ObjectIterator) Object() *Object { return o.objs[o.idx-1] } // Err returns the current error or nil. If Next() returns false and Err() is // nil, then all objects have been seen. func (o *ObjectIterator) Err() error { if o.err == io.EOF { return nil } return o.err } type objectIteratorOptions struct { hidden bool unfinished bool prefix string delimiter string } // A ListOption alters the default behavor of List. type ListOption func(*objectIteratorOptions) // ListHidden will include hidden objects in the output. func ListHidden() ListOption { return func(o *objectIteratorOptions) { o.hidden = true } } // ListUnfinished will list unfinished large file operations instead of // existing objects. func ListUnfinished() ListOption { return func(o *objectIteratorOptions) { o.unfinished = true } } // ListPrefix will restrict the output to objects whose names begin with // prefix. func ListPrefix(pfx string) ListOption { return func(o *objectIteratorOptions) { o.prefix = pfx } } // ListDelimiter denotes the path separator. If set, object listings will be // truncated at this character. // // For example, if the bucket contains objects foo/bar, foo/baz, and foo, // then a delimiter of "/" will cause the listing to return "foo" and "foo/". // Otherwise, the listing would have returned all object names. // // Note that objects returned that end in the delimiter may not be actual // objects, e.g. you cannot read from (or write to, or delete) an object // "foo/", both because no actual object exists and because B2 disallows object // names that end with "/". If you want to ensure that all objects returned // are actual objects, leave this unset. func ListDelimiter(delimiter string) ListOption { return func(o *objectIteratorOptions) { o.delimiter = delimiter } }