forked from TrueCloudLab/restic
b9f0f031b6
Closes #2129
331 lines
8 KiB
Go
331 lines
8 KiB
Go
// Copyright 2018, the Blazer authors
|
|
//
|
|
// 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(ctx context.Context, opts ...ListOption) *ObjectIterator {
|
|
o := &ObjectIterator{
|
|
bucket: b,
|
|
ctx: ctx,
|
|
}
|
|
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() {
|
|
// obj := iter.Object()
|
|
// // act on obj
|
|
// }
|
|
// if err := iter.Err(); err != nil {
|
|
// // handle err
|
|
// }
|
|
type ObjectIterator struct {
|
|
bucket *Bucket
|
|
ctx context.Context
|
|
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) page(ctx context.Context) error {
|
|
if o.opts.locker != nil {
|
|
o.opts.locker.Lock()
|
|
defer o.opts.locker.Unlock()
|
|
}
|
|
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() bool {
|
|
o.init.Do(func() {
|
|
o.count = o.opts.pageSize
|
|
if o.count < 0 || o.count > 1000 {
|
|
o.count = 1000
|
|
}
|
|
switch {
|
|
case o.opts.unfinished:
|
|
o.l = o.bucket.listUnfinishedLargeFiles
|
|
if o.count > 100 {
|
|
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.ctx.Err() != nil {
|
|
o.err = o.ctx.Err()
|
|
return false
|
|
}
|
|
if o.idx >= len(o.objs) {
|
|
if o.final {
|
|
o.err = io.EOF
|
|
return false
|
|
}
|
|
if err := o.page(o.ctx); err != nil {
|
|
o.err = err
|
|
return false
|
|
}
|
|
return o.Next()
|
|
}
|
|
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
|
|
pageSize int
|
|
locker sync.Locker
|
|
}
|
|
|
|
// 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
|
|
}
|
|
}
|
|
|
|
// ListPageSize configures the iterator to request the given number of objects
|
|
// per network round-trip. The default (and maximum) is 1000 objects, except
|
|
// for unfinished large files, which is 100.
|
|
func ListPageSize(count int) ListOption {
|
|
return func(o *objectIteratorOptions) {
|
|
o.pageSize = count
|
|
}
|
|
}
|
|
|
|
// ListLocker passes the iterator a lock which will be held during network
|
|
// round-trips.
|
|
func ListLocker(l sync.Locker) ListOption {
|
|
return func(o *objectIteratorOptions) {
|
|
o.locker = l
|
|
}
|
|
}
|
|
|
|
type cursor struct {
|
|
// Prefix limits the listed objects to those that begin with this string.
|
|
prefix string
|
|
|
|
// Delimiter 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 by
|
|
// ListObjects and ListCurrentObjects are actual objects, leave this unset.
|
|
delimiter string
|
|
|
|
name string
|
|
id string
|
|
}
|
|
|
|
func (b *Bucket) listObjects(ctx context.Context, count int, c *cursor) ([]*Object, *cursor, error) {
|
|
if c == nil {
|
|
c = &cursor{}
|
|
}
|
|
fs, name, id, err := b.b.listFileVersions(ctx, count, c.name, c.id, c.prefix, c.delimiter)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
var next *cursor
|
|
if name != "" && id != "" {
|
|
next = &cursor{
|
|
prefix: c.prefix,
|
|
delimiter: c.delimiter,
|
|
name: name,
|
|
id: id,
|
|
}
|
|
}
|
|
var objects []*Object
|
|
for _, f := range fs {
|
|
objects = append(objects, &Object{
|
|
name: f.name(),
|
|
f: f,
|
|
b: b,
|
|
})
|
|
}
|
|
var rtnErr error
|
|
if len(objects) == 0 || next == nil {
|
|
rtnErr = io.EOF
|
|
}
|
|
return objects, next, rtnErr
|
|
}
|
|
|
|
func (b *Bucket) listCurrentObjects(ctx context.Context, count int, c *cursor) ([]*Object, *cursor, error) {
|
|
if c == nil {
|
|
c = &cursor{}
|
|
}
|
|
fs, name, err := b.b.listFileNames(ctx, count, c.name, c.prefix, c.delimiter)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
var next *cursor
|
|
if name != "" {
|
|
next = &cursor{
|
|
prefix: c.prefix,
|
|
delimiter: c.delimiter,
|
|
name: name,
|
|
}
|
|
}
|
|
var objects []*Object
|
|
for _, f := range fs {
|
|
objects = append(objects, &Object{
|
|
name: f.name(),
|
|
f: f,
|
|
b: b,
|
|
})
|
|
}
|
|
var rtnErr error
|
|
if len(objects) == 0 || next == nil {
|
|
rtnErr = io.EOF
|
|
}
|
|
return objects, next, rtnErr
|
|
}
|
|
|
|
func (b *Bucket) listUnfinishedLargeFiles(ctx context.Context, count int, c *cursor) ([]*Object, *cursor, error) {
|
|
if c == nil {
|
|
c = &cursor{}
|
|
}
|
|
fs, name, err := b.b.listUnfinishedLargeFiles(ctx, count, c.name)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
var next *cursor
|
|
if name != "" {
|
|
next = &cursor{
|
|
name: name,
|
|
}
|
|
}
|
|
var objects []*Object
|
|
for _, f := range fs {
|
|
objects = append(objects, &Object{
|
|
name: f.name(),
|
|
f: f,
|
|
b: b,
|
|
})
|
|
}
|
|
var rtnErr error
|
|
if len(objects) == 0 || next == nil {
|
|
rtnErr = io.EOF
|
|
}
|
|
return objects, next, rtnErr
|
|
}
|