storj/cmd/uplink/ulfs/mixed.go
Jeff Wendling 89ccfe2dd7 cmd/uplink: fix recursive copy and improve tests
recursive copy had a bug with relative local paths.

this fixes that bug and changes the test framework
to use more of the code that actually runs in uplink
and only mocks out the direct interaction with the
operating system.

Change-Id: I9da2a80bfda8f86a8d05879b87171f299f759c7e
2022-05-11 15:17:16 -04:00

120 lines
4.0 KiB
Go

// Copyright (C) 2021 Storj Labs, Inc.
// See LICENSE for copying information.
package ulfs
import (
"context"
"github.com/zeebo/clingy"
"github.com/zeebo/errs"
"storj.io/storj/cmd/uplink/ulloc"
)
// Mixed dispatches to either the local or remote filesystem depending on the location.
type Mixed struct {
local FilesystemLocal
remote FilesystemRemote
}
// NewMixed returns a Mixed backed by the provided local and remote filesystems.
func NewMixed(local FilesystemLocal, remote FilesystemRemote) *Mixed {
return &Mixed{
local: local,
remote: remote,
}
}
// Close releases any resources that the Mixed contails.
func (m *Mixed) Close() error {
return m.remote.Close()
}
// Open returns a MultiReadHandle to either a local file, remote object, or stdin.
func (m *Mixed) Open(ctx clingy.Context, loc ulloc.Location) (MultiReadHandle, error) {
if bucket, key, ok := loc.RemoteParts(); ok {
return m.remote.Open(ctx, bucket, key)
} else if path, ok := loc.LocalParts(); ok {
return m.local.Open(ctx, path)
}
return newStdMultiReadHandle(ctx.Stdin()), nil
}
// Create returns a WriteHandle to either a local file, remote object, or stdout.
func (m *Mixed) Create(ctx clingy.Context, loc ulloc.Location, opts *CreateOptions) (MultiWriteHandle, error) {
if bucket, key, ok := loc.RemoteParts(); ok {
return m.remote.Create(ctx, bucket, key, opts)
} else if path, ok := loc.LocalParts(); ok {
return m.local.Create(ctx, path)
}
return newStdMultiWriteHandle(ctx.Stdout()), nil
}
// Move moves either a local file or remote object.
func (m *Mixed) Move(ctx clingy.Context, source, dest ulloc.Location) error {
if oldbucket, oldkey, ok := source.RemoteParts(); ok {
if newbucket, newkey, ok := dest.RemoteParts(); ok {
return m.remote.Move(ctx, oldbucket, oldkey, newbucket, newkey)
}
} else if oldpath, ok := source.LocalParts(); ok {
if newpath, ok := dest.LocalParts(); ok {
return m.local.Move(ctx, oldpath, newpath)
}
}
return errs.New("moving objects between local and remote is not supported")
}
// Copy copies either a local file or remote object.
func (m *Mixed) Copy(ctx clingy.Context, source, dest ulloc.Location) error {
if oldbucket, oldkey, ok := source.RemoteParts(); ok {
if newbucket, newkey, ok := dest.RemoteParts(); ok {
return m.remote.Copy(ctx, oldbucket, oldkey, newbucket, newkey)
}
} else if oldpath, ok := source.LocalParts(); ok {
if newpath, ok := dest.LocalParts(); ok {
return m.local.Copy(ctx, oldpath, newpath)
}
}
return errs.New("copying objects between local and remote is not supported")
}
// Remove deletes either a local file or remote object.
func (m *Mixed) Remove(ctx context.Context, loc ulloc.Location, opts *RemoveOptions) error {
if bucket, key, ok := loc.RemoteParts(); ok {
return m.remote.Remove(ctx, bucket, key, opts)
} else if path, ok := loc.LocalParts(); ok {
return m.local.Remove(ctx, path, opts)
}
return nil
}
// List lists either files and directories with some local path prefix or remote objects
// with a given bucket and key.
func (m *Mixed) List(ctx context.Context, prefix ulloc.Location, opts *ListOptions) (ObjectIterator, error) {
if bucket, key, ok := prefix.RemoteParts(); ok {
return m.remote.List(ctx, bucket, key, opts), nil
} else if path, ok := prefix.LocalParts(); ok {
return m.local.List(ctx, path, opts)
}
return nil, errs.New("unable to list objects for prefix %q", prefix)
}
// IsLocalDir returns true if the location is a directory that is local.
func (m *Mixed) IsLocalDir(ctx context.Context, loc ulloc.Location) bool {
if path, ok := loc.LocalParts(); ok {
return m.local.IsLocalDir(ctx, path)
}
return false
}
// Stat returns information about an object at the specified Location.
func (m *Mixed) Stat(ctx context.Context, loc ulloc.Location) (*ObjectInfo, error) {
if bucket, key, ok := loc.RemoteParts(); ok {
return m.remote.Stat(ctx, bucket, key)
} else if path, ok := loc.LocalParts(); ok {
return m.local.Stat(ctx, path)
}
return nil, errs.New("unable to stat loc %q", loc.Loc())
}