2020-05-11 20:57:46 +02:00
|
|
|
// Copyright (C) 2020 Storj Labs, Inc.
|
|
|
|
// See LICENSE for copying information.
|
|
|
|
|
|
|
|
package uplink
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
|
|
|
|
"storj.io/common/storj"
|
|
|
|
)
|
|
|
|
|
|
|
|
// ListObjectsOptions defines object listing options.
|
|
|
|
type ListObjectsOptions struct {
|
|
|
|
// Prefix allows to filter objects by a key prefix. If not empty, it must end with slash.
|
|
|
|
Prefix string
|
|
|
|
// Cursor sets the starting position of the iterator. The first item listed will be the one after the cursor.
|
|
|
|
Cursor string
|
|
|
|
// Recursive iterates the objects without collapsing prefixes.
|
|
|
|
Recursive bool
|
|
|
|
|
|
|
|
// System includes SystemMetadata in the results.
|
|
|
|
System bool
|
|
|
|
// Custom includes CustomMetadata in the results.
|
|
|
|
Custom bool
|
|
|
|
}
|
|
|
|
|
|
|
|
// ListObjects returns an iterator over the objects.
|
|
|
|
func (project *Project) ListObjects(ctx context.Context, bucket string, options *ListObjectsOptions) *ObjectIterator {
|
2020-05-29 15:08:11 +02:00
|
|
|
defer mon.Func().RestartTrace(&ctx)(nil)
|
2020-05-11 20:57:46 +02:00
|
|
|
|
|
|
|
b := storj.Bucket{Name: bucket, PathCipher: storj.EncAESGCM}
|
|
|
|
opts := storj.ListOptions{
|
|
|
|
Direction: storj.After,
|
|
|
|
}
|
|
|
|
|
|
|
|
if options != nil {
|
|
|
|
opts.Prefix = options.Prefix
|
|
|
|
opts.Cursor = options.Cursor
|
|
|
|
opts.Recursive = options.Recursive
|
|
|
|
}
|
|
|
|
|
|
|
|
objects := ObjectIterator{
|
|
|
|
ctx: ctx,
|
|
|
|
project: project,
|
|
|
|
bucket: b,
|
|
|
|
options: opts,
|
|
|
|
}
|
|
|
|
|
|
|
|
if options != nil {
|
|
|
|
objects.objOptions = *options
|
|
|
|
}
|
|
|
|
|
|
|
|
return &objects
|
|
|
|
}
|
|
|
|
|
|
|
|
// ObjectIterator is an iterator over a collection of objects or prefixes.
|
|
|
|
type ObjectIterator struct {
|
|
|
|
ctx context.Context
|
|
|
|
project *Project
|
|
|
|
bucket storj.Bucket
|
|
|
|
options storj.ListOptions
|
|
|
|
objOptions ListObjectsOptions
|
|
|
|
list *storj.ObjectList
|
|
|
|
position int
|
|
|
|
completed bool
|
|
|
|
err error
|
|
|
|
}
|
|
|
|
|
|
|
|
// Next prepares next Object for reading.
|
|
|
|
// It returns false if the end of the iteration is reached and there are no more objects, or if there is an error.
|
|
|
|
func (objects *ObjectIterator) Next() bool {
|
|
|
|
if objects.err != nil {
|
|
|
|
objects.completed = true
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
if objects.list == nil {
|
|
|
|
more := objects.loadNext()
|
|
|
|
objects.completed = !more
|
|
|
|
return more
|
|
|
|
}
|
|
|
|
|
|
|
|
if objects.position >= len(objects.list.Items)-1 {
|
|
|
|
if !objects.list.More {
|
|
|
|
objects.completed = true
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
more := objects.loadNext()
|
|
|
|
objects.completed = !more
|
|
|
|
return more
|
|
|
|
}
|
|
|
|
|
|
|
|
objects.position++
|
|
|
|
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
func (objects *ObjectIterator) loadNext() bool {
|
2020-06-04 15:39:42 +02:00
|
|
|
list, err := objects.project.db.ListObjects(objects.ctx, objects.bucket, objects.options)
|
2020-05-11 20:57:46 +02:00
|
|
|
if err != nil {
|
2020-05-29 15:08:11 +02:00
|
|
|
objects.err = convertKnownErrors(err, objects.bucket.Name, "")
|
2020-05-11 20:57:46 +02:00
|
|
|
return false
|
|
|
|
}
|
|
|
|
objects.list = &list
|
|
|
|
if list.More {
|
|
|
|
objects.options = objects.options.NextPage(list)
|
|
|
|
}
|
|
|
|
objects.position = 0
|
|
|
|
return len(list.Items) > 0
|
|
|
|
}
|
|
|
|
|
|
|
|
// Err returns error, if one happened during iteration.
|
|
|
|
func (objects *ObjectIterator) Err() error {
|
|
|
|
return packageError.Wrap(objects.err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Item returns the current object in the iterator.
|
|
|
|
func (objects *ObjectIterator) Item() *Object {
|
|
|
|
item := objects.item()
|
|
|
|
if item == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
key := item.Path
|
|
|
|
if len(objects.options.Prefix) > 0 {
|
|
|
|
key = objects.options.Prefix + item.Path
|
|
|
|
}
|
|
|
|
|
|
|
|
obj := Object{
|
|
|
|
Key: key,
|
|
|
|
IsPrefix: item.IsPrefix,
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: Make this filtering on the satellite
|
|
|
|
if objects.objOptions.System {
|
|
|
|
obj.System = SystemMetadata{
|
|
|
|
Created: item.Created,
|
|
|
|
Expires: item.Expires,
|
|
|
|
ContentLength: item.Size,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: Make this filtering on the satellite
|
|
|
|
if objects.objOptions.Custom {
|
|
|
|
obj.Custom = item.Metadata
|
|
|
|
}
|
|
|
|
|
|
|
|
return &obj
|
|
|
|
}
|
|
|
|
|
|
|
|
func (objects *ObjectIterator) item() *storj.Object {
|
|
|
|
if objects.completed {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
if objects.err != nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
if objects.list == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(objects.list.Items) == 0 {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return &objects.list.Items[objects.position]
|
|
|
|
}
|