2021-04-06 20:19:11 +01:00
|
|
|
// Copyright (C) 2021 Storj Labs, Inc.
|
|
|
|
// See LICENSE for copying information.
|
|
|
|
|
2021-05-06 17:56:57 +01:00
|
|
|
package ulfs
|
2021-04-06 20:19:11 +01:00
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
"github.com/zeebo/errs"
|
|
|
|
|
2021-06-25 02:55:13 +01:00
|
|
|
"storj.io/storj/cmd/uplinkng/ulloc"
|
2021-04-06 20:19:11 +01:00
|
|
|
"storj.io/uplink"
|
|
|
|
)
|
|
|
|
|
2021-05-06 17:56:57 +01:00
|
|
|
// Remote implements something close to a filesystem but backed by an uplink project.
|
|
|
|
type Remote struct {
|
2021-04-06 20:19:11 +01:00
|
|
|
project *uplink.Project
|
|
|
|
}
|
|
|
|
|
2021-05-06 17:56:57 +01:00
|
|
|
// NewRemote returns something close to a filesystem and returns objects using the project.
|
|
|
|
func NewRemote(project *uplink.Project) *Remote {
|
|
|
|
return &Remote{
|
|
|
|
project: project,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Close releases any resources that the Remote contains.
|
|
|
|
func (r *Remote) Close() error {
|
2021-04-06 20:19:11 +01:00
|
|
|
return r.project.Close()
|
|
|
|
}
|
|
|
|
|
2021-05-06 17:56:57 +01:00
|
|
|
// Open returns a ReadHandle for the object identified by a given bucket and key.
|
2021-10-26 11:49:03 +01:00
|
|
|
func (r *Remote) Open(ctx context.Context, bucket, key string, opts *OpenOptions) (ReadHandle, error) {
|
|
|
|
var downloadOpts *uplink.DownloadOptions
|
|
|
|
if opts != nil {
|
|
|
|
downloadOpts = &uplink.DownloadOptions{
|
|
|
|
Offset: opts.Offset,
|
|
|
|
Length: opts.Length,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
fh, err := r.project.DownloadObject(ctx, bucket, key, downloadOpts)
|
2021-04-06 20:19:11 +01:00
|
|
|
if err != nil {
|
|
|
|
return nil, errs.Wrap(err)
|
|
|
|
}
|
|
|
|
return newUplinkReadHandle(bucket, fh), nil
|
|
|
|
}
|
|
|
|
|
2021-10-26 11:49:03 +01:00
|
|
|
// Stat returns information about an object at the specified key.
|
|
|
|
func (r *Remote) Stat(ctx context.Context, bucket, key string) (*ObjectInfo, error) {
|
|
|
|
fstat, err := r.project.StatObject(ctx, bucket, key)
|
|
|
|
if err != nil {
|
|
|
|
return nil, errs.Wrap(err)
|
|
|
|
}
|
|
|
|
stat := uplinkObjectToObjectInfo(bucket, fstat)
|
|
|
|
return &stat, nil
|
|
|
|
}
|
|
|
|
|
2021-05-06 17:56:57 +01:00
|
|
|
// Create returns a WriteHandle for the object identified by a given bucket and key.
|
|
|
|
func (r *Remote) Create(ctx context.Context, bucket, key string) (WriteHandle, error) {
|
2021-04-06 20:19:11 +01:00
|
|
|
fh, err := r.project.UploadObject(ctx, bucket, key, nil)
|
|
|
|
if err != nil {
|
2021-10-02 00:47:53 +01:00
|
|
|
return nil, errs.Wrap(err)
|
2021-04-06 20:19:11 +01:00
|
|
|
}
|
|
|
|
return newUplinkWriteHandle(fh), nil
|
|
|
|
}
|
|
|
|
|
2021-10-05 17:48:13 +01:00
|
|
|
// Move moves object to provided key and bucket.
|
|
|
|
func (r *Remote) Move(ctx context.Context, oldbucket, oldkey, newbucket, newkey string) error {
|
|
|
|
return errs.Wrap(r.project.MoveObject(ctx, oldbucket, oldkey, newbucket, newkey, nil))
|
|
|
|
}
|
|
|
|
|
2021-05-14 20:20:21 +01:00
|
|
|
// Remove deletes the object at the provided key and bucket.
|
2021-10-02 00:47:53 +01:00
|
|
|
func (r *Remote) Remove(ctx context.Context, bucket, key string, opts *RemoveOptions) error {
|
|
|
|
if !opts.isPending() {
|
|
|
|
_, err := r.project.DeleteObject(ctx, bucket, key)
|
|
|
|
if err != nil {
|
|
|
|
return errs.Wrap(err)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: we may need a dedicated endpoint for deleting pending object streams
|
|
|
|
list := r.project.ListUploads(ctx, bucket, &uplink.ListUploadsOptions{Prefix: key})
|
|
|
|
|
|
|
|
// TODO: modify when we can have several pending objects for the same object key
|
|
|
|
if list.Next() {
|
|
|
|
err := r.project.AbortUpload(ctx, bucket, key, list.Item().UploadID)
|
|
|
|
if err != nil {
|
|
|
|
return errs.Wrap(err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if err := list.Err(); err != nil {
|
|
|
|
return errs.Wrap(err)
|
2021-05-14 20:20:21 +01:00
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2021-10-02 00:47:53 +01:00
|
|
|
// List lists all of the objects in some bucket that begin with the given prefix.
|
|
|
|
func (r *Remote) List(ctx context.Context, bucket, prefix string, opts *ListOptions) ObjectIterator {
|
2021-04-06 20:19:11 +01:00
|
|
|
parentPrefix := ""
|
|
|
|
if idx := strings.LastIndexByte(prefix, '/'); idx >= 0 {
|
|
|
|
parentPrefix = prefix[:idx+1]
|
|
|
|
}
|
|
|
|
|
2021-06-25 02:55:13 +01:00
|
|
|
trim := ulloc.NewRemote(bucket, "")
|
2021-10-02 00:47:53 +01:00
|
|
|
if !opts.isRecursive() {
|
2021-06-25 02:55:13 +01:00
|
|
|
trim = ulloc.NewRemote(bucket, parentPrefix)
|
2021-04-06 20:19:11 +01:00
|
|
|
}
|
|
|
|
|
2021-10-02 00:47:53 +01:00
|
|
|
var iter ObjectIterator
|
|
|
|
if opts.isPending() {
|
|
|
|
iter = newUplinkUploadIterator(
|
|
|
|
bucket,
|
|
|
|
r.project.ListUploads(ctx, bucket, &uplink.ListUploadsOptions{
|
2021-04-06 20:19:11 +01:00
|
|
|
Prefix: parentPrefix,
|
2021-10-02 00:47:53 +01:00
|
|
|
Recursive: opts.Recursive,
|
2021-04-06 20:19:11 +01:00
|
|
|
System: true,
|
2021-10-28 17:51:33 +01:00
|
|
|
Custom: opts.Expanded,
|
2021-10-02 00:47:53 +01:00
|
|
|
}),
|
|
|
|
)
|
|
|
|
} else {
|
|
|
|
iter = newUplinkObjectIterator(
|
|
|
|
bucket,
|
|
|
|
r.project.ListObjects(ctx, bucket, &uplink.ListObjectsOptions{
|
|
|
|
Prefix: parentPrefix,
|
|
|
|
Recursive: opts.Recursive,
|
|
|
|
System: true,
|
2021-10-28 17:51:33 +01:00
|
|
|
Custom: opts.Expanded,
|
2021-10-02 00:47:53 +01:00
|
|
|
}),
|
|
|
|
)
|
2021-04-06 20:19:11 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
return &filteredObjectIterator{
|
|
|
|
trim: trim,
|
2021-06-25 02:55:13 +01:00
|
|
|
filter: ulloc.NewRemote(bucket, prefix),
|
2021-10-02 00:47:53 +01:00
|
|
|
iter: iter,
|
2021-04-06 20:19:11 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// uplinkObjectIterator implements objectIterator for *uplink.ObjectIterator.
|
|
|
|
type uplinkObjectIterator struct {
|
|
|
|
bucket string
|
|
|
|
iter *uplink.ObjectIterator
|
|
|
|
}
|
|
|
|
|
|
|
|
// newUplinkObjectIterator constructs an *uplinkObjectIterator from an *uplink.ObjectIterator.
|
|
|
|
func newUplinkObjectIterator(bucket string, iter *uplink.ObjectIterator) *uplinkObjectIterator {
|
|
|
|
return &uplinkObjectIterator{
|
|
|
|
bucket: bucket,
|
|
|
|
iter: iter,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (u *uplinkObjectIterator) Next() bool { return u.iter.Next() }
|
|
|
|
func (u *uplinkObjectIterator) Err() error { return u.iter.Err() }
|
2021-05-06 17:56:57 +01:00
|
|
|
func (u *uplinkObjectIterator) Item() ObjectInfo {
|
2021-04-06 20:19:11 +01:00
|
|
|
return uplinkObjectToObjectInfo(u.bucket, u.iter.Item())
|
|
|
|
}
|
|
|
|
|
|
|
|
// uplinkUploadIterator implements objectIterator for *multipart.UploadIterators.
|
|
|
|
type uplinkUploadIterator struct {
|
|
|
|
bucket string
|
|
|
|
iter *uplink.UploadIterator
|
|
|
|
}
|
|
|
|
|
|
|
|
// newUplinkUploadIterator constructs a *uplinkUploadIterator from a *uplink.UploadIterator.
|
|
|
|
func newUplinkUploadIterator(bucket string, iter *uplink.UploadIterator) *uplinkUploadIterator {
|
|
|
|
return &uplinkUploadIterator{
|
|
|
|
bucket: bucket,
|
|
|
|
iter: iter,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (u *uplinkUploadIterator) Next() bool { return u.iter.Next() }
|
|
|
|
func (u *uplinkUploadIterator) Err() error { return u.iter.Err() }
|
2021-05-06 17:56:57 +01:00
|
|
|
func (u *uplinkUploadIterator) Item() ObjectInfo {
|
2021-04-06 20:19:11 +01:00
|
|
|
return uplinkUploadInfoToObjectInfo(u.bucket, u.iter.Item())
|
|
|
|
}
|