2021-05-06 17:56:57 +01:00
|
|
|
// 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/uplinkng/ulloc"
|
|
|
|
)
|
|
|
|
|
|
|
|
// Mixed dispatches to either the local or remote filesystem depending on the location.
|
|
|
|
type Mixed struct {
|
|
|
|
local *Local
|
|
|
|
remote *Remote
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewMixed returns a Mixed backed by the provided local and remote filesystems.
|
|
|
|
func NewMixed(local *Local, remote *Remote) *Mixed {
|
|
|
|
return &Mixed{
|
|
|
|
local: local,
|
|
|
|
remote: remote,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Close releases any resources that the Mixed contails.
|
|
|
|
func (m *Mixed) Close() error {
|
|
|
|
return m.remote.Close()
|
|
|
|
}
|
|
|
|
|
2021-12-09 18:36:11 +00:00
|
|
|
// 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) {
|
2021-05-06 17:56:57 +01:00
|
|
|
if bucket, key, ok := loc.RemoteParts(); ok {
|
2021-12-09 18:36:11 +00:00
|
|
|
return m.remote.Open(ctx, bucket, key)
|
2021-05-06 17:56:57 +01:00
|
|
|
} else if path, ok := loc.LocalParts(); ok {
|
2021-12-09 18:36:11 +00:00
|
|
|
return m.local.Open(ctx, path)
|
2021-05-06 17:56:57 +01:00
|
|
|
}
|
2021-12-09 18:36:11 +00:00
|
|
|
return newStdMultiReadHandle(ctx.Stdin()), nil
|
2021-05-06 17:56:57 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Create returns a WriteHandle to either a local file, remote object, or stdout.
|
|
|
|
func (m *Mixed) Create(ctx clingy.Context, loc ulloc.Location) (WriteHandle, error) {
|
|
|
|
if bucket, key, ok := loc.RemoteParts(); ok {
|
|
|
|
return m.remote.Create(ctx, bucket, key)
|
|
|
|
} else if path, ok := loc.LocalParts(); ok {
|
|
|
|
return m.local.Create(ctx, path)
|
|
|
|
}
|
|
|
|
return newGenericWriteHandle(ctx.Stdout()), nil
|
|
|
|
}
|
|
|
|
|
2021-10-05 17:48:13 +01:00
|
|
|
// 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")
|
|
|
|
}
|
|
|
|
|
2021-05-14 20:20:21 +01:00
|
|
|
// Remove deletes either a local file or remote object.
|
2021-10-02 00:47:53 +01:00
|
|
|
func (m *Mixed) Remove(ctx context.Context, loc ulloc.Location, opts *RemoveOptions) error {
|
2021-05-14 20:20:21 +01:00
|
|
|
if bucket, key, ok := loc.RemoteParts(); ok {
|
2021-10-02 00:47:53 +01:00
|
|
|
return m.remote.Remove(ctx, bucket, key, opts)
|
2021-05-14 20:20:21 +01:00
|
|
|
} else if path, ok := loc.LocalParts(); ok {
|
2021-10-02 00:47:53 +01:00
|
|
|
return m.local.Remove(ctx, path, opts)
|
2021-05-14 20:20:21 +01:00
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2021-10-02 00:47:53 +01:00
|
|
|
// List lists either files and directories with some local path prefix or remote objects
|
2021-05-06 17:56:57 +01:00
|
|
|
// with a given bucket and key.
|
2021-10-02 00:47:53 +01:00
|
|
|
func (m *Mixed) List(ctx context.Context, prefix ulloc.Location, opts *ListOptions) (ObjectIterator, error) {
|
2021-05-06 17:56:57 +01:00
|
|
|
if bucket, key, ok := prefix.RemoteParts(); ok {
|
2021-10-02 00:47:53 +01:00
|
|
|
return m.remote.List(ctx, bucket, key, opts), nil
|
2021-05-06 17:56:57 +01:00
|
|
|
} else if path, ok := prefix.LocalParts(); ok {
|
2021-10-02 00:47:53 +01:00
|
|
|
return m.local.List(ctx, path, opts)
|
2021-05-06 17:56:57 +01:00
|
|
|
}
|
|
|
|
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
|
|
|
|
}
|
2021-10-26 11:49:03 +01:00
|
|
|
|
|
|
|
// 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())
|
|
|
|
}
|