34890c9195
Change-Id: I57b98b5e1406e7b38edf3bc65907d9796a1a663b
235 lines
6.3 KiB
Go
235 lines
6.3 KiB
Go
// Copyright (C) 2021 Storj Labs, Inc.
|
|
// See LICENSE for copying information.
|
|
|
|
package ulfs
|
|
|
|
import (
|
|
"context"
|
|
"io"
|
|
"os"
|
|
"time"
|
|
|
|
"github.com/zeebo/clingy"
|
|
"github.com/zeebo/errs"
|
|
|
|
"storj.io/storj/cmd/uplinkng/ulloc"
|
|
"storj.io/uplink"
|
|
)
|
|
|
|
// ListOptions describes options to the List command.
|
|
type ListOptions struct {
|
|
Recursive bool
|
|
Pending bool
|
|
Expanded bool
|
|
}
|
|
|
|
func (lo *ListOptions) isRecursive() bool { return lo != nil && lo.Recursive }
|
|
func (lo *ListOptions) isPending() bool { return lo != nil && lo.Pending }
|
|
|
|
// RemoveOptions describes options to the Remove command.
|
|
type RemoveOptions struct {
|
|
Pending bool
|
|
}
|
|
|
|
func (ro *RemoveOptions) isPending() bool { return ro != nil && ro.Pending }
|
|
|
|
// Filesystem represents either the local Filesystem or the data backed by a project.
|
|
type Filesystem interface {
|
|
Close() error
|
|
Open(ctx clingy.Context, loc ulloc.Location) (MultiReadHandle, error)
|
|
Create(ctx clingy.Context, loc ulloc.Location) (WriteHandle, error)
|
|
Move(ctx clingy.Context, source, dest ulloc.Location) error
|
|
Remove(ctx context.Context, loc ulloc.Location, opts *RemoveOptions) error
|
|
List(ctx context.Context, prefix ulloc.Location, opts *ListOptions) (ObjectIterator, error)
|
|
IsLocalDir(ctx context.Context, loc ulloc.Location) bool
|
|
Stat(ctx context.Context, loc ulloc.Location) (*ObjectInfo, error)
|
|
}
|
|
|
|
//
|
|
// object info
|
|
//
|
|
|
|
// ObjectInfo is a simpler *uplink.Object that contains the minimal information the
|
|
// uplink command needs that multiple types can be converted to.
|
|
type ObjectInfo struct {
|
|
Loc ulloc.Location
|
|
IsPrefix bool
|
|
Created time.Time
|
|
ContentLength int64
|
|
Expires time.Time
|
|
Metadata uplink.CustomMetadata
|
|
}
|
|
|
|
// uplinkObjectToObjectInfo returns an objectInfo converted from an *uplink.Object.
|
|
func uplinkObjectToObjectInfo(bucket string, obj *uplink.Object) ObjectInfo {
|
|
return ObjectInfo{
|
|
Loc: ulloc.NewRemote(bucket, obj.Key),
|
|
IsPrefix: obj.IsPrefix,
|
|
Created: obj.System.Created,
|
|
ContentLength: obj.System.ContentLength,
|
|
Expires: obj.System.Expires,
|
|
Metadata: obj.Custom,
|
|
}
|
|
}
|
|
|
|
// uplinkUploadInfoToObjectInfo returns an objectInfo converted from an *uplink.Object.
|
|
func uplinkUploadInfoToObjectInfo(bucket string, upl *uplink.UploadInfo) ObjectInfo {
|
|
return ObjectInfo{
|
|
Loc: ulloc.NewRemote(bucket, upl.Key),
|
|
IsPrefix: upl.IsPrefix,
|
|
Created: upl.System.Created,
|
|
ContentLength: upl.System.ContentLength,
|
|
Expires: upl.System.Expires,
|
|
Metadata: upl.Custom,
|
|
}
|
|
}
|
|
|
|
//
|
|
// read handles
|
|
//
|
|
|
|
// MultiReadHandle allows one to read different sections of something.
|
|
// The offset parameter can be negative to signal that the offset should
|
|
// start that many bytes back from the end. Any negative value for length
|
|
// indicates to read up to the end.
|
|
//
|
|
// TODO: A negative offset requires a negative length, but there is no
|
|
// reason why that must be so.
|
|
type MultiReadHandle interface {
|
|
io.Closer
|
|
SetOffset(offset int64) error
|
|
NextPart(ctx context.Context, length int64) (ReadHandle, error)
|
|
Info(ctx context.Context) (*ObjectInfo, error)
|
|
}
|
|
|
|
// ReadHandle is something that can be read from distinct parts possibly
|
|
// in parallel.
|
|
type ReadHandle interface {
|
|
io.Closer
|
|
io.Reader
|
|
Info() ObjectInfo
|
|
}
|
|
|
|
//
|
|
// write handles
|
|
//
|
|
|
|
// WriteHandle is anything that can be written to with commit/abort semantics.
|
|
type WriteHandle interface {
|
|
io.Writer
|
|
Commit() error
|
|
Abort() error
|
|
}
|
|
|
|
// uplinkWriteHandle implements writeHandle for *uplink.Uploads.
|
|
type uplinkWriteHandle uplink.Upload
|
|
|
|
// newUplinkWriteHandle constructs an *uplinkWriteHandle from an *uplink.Upload.
|
|
func newUplinkWriteHandle(dl *uplink.Upload) *uplinkWriteHandle {
|
|
return (*uplinkWriteHandle)(dl)
|
|
}
|
|
|
|
func (u *uplinkWriteHandle) raw() *uplink.Upload {
|
|
return (*uplink.Upload)(u)
|
|
}
|
|
|
|
func (u *uplinkWriteHandle) Write(p []byte) (int, error) { return u.raw().Write(p) }
|
|
func (u *uplinkWriteHandle) Commit() error { return u.raw().Commit() }
|
|
func (u *uplinkWriteHandle) Abort() error { return u.raw().Abort() }
|
|
|
|
// osWriteHandle implements writeHandle for *os.Files.
|
|
type osWriteHandle struct {
|
|
fh *os.File
|
|
done bool
|
|
}
|
|
|
|
// newOSWriteHandle constructs an *osWriteHandle from an *os.File.
|
|
func newOSWriteHandle(fh *os.File) *osWriteHandle {
|
|
return &osWriteHandle{fh: fh}
|
|
}
|
|
|
|
func (o *osWriteHandle) Write(p []byte) (int, error) { return o.fh.Write(p) }
|
|
|
|
func (o *osWriteHandle) Commit() error {
|
|
if o.done {
|
|
return nil
|
|
}
|
|
o.done = true
|
|
|
|
return o.fh.Close()
|
|
}
|
|
|
|
func (o *osWriteHandle) Abort() error {
|
|
if o.done {
|
|
return nil
|
|
}
|
|
o.done = true
|
|
|
|
return errs.Combine(
|
|
o.fh.Close(),
|
|
os.Remove(o.fh.Name()),
|
|
)
|
|
}
|
|
|
|
// genericWriteHandle implements writeHandle for an io.Writer.
|
|
type genericWriteHandle struct{ w io.Writer }
|
|
|
|
// newGenericWriteHandle constructs a *genericWriteHandle from an io.Writer.
|
|
func newGenericWriteHandle(w io.Writer) *genericWriteHandle {
|
|
return &genericWriteHandle{w: w}
|
|
}
|
|
|
|
func (g *genericWriteHandle) Write(p []byte) (int, error) { return g.w.Write(p) }
|
|
func (g *genericWriteHandle) Commit() error { return nil }
|
|
func (g *genericWriteHandle) Abort() error { return nil }
|
|
|
|
//
|
|
// object iteration
|
|
//
|
|
|
|
// ObjectIterator is an interface type for iterating over objectInfo values.
|
|
type ObjectIterator interface {
|
|
Next() bool
|
|
Err() error
|
|
Item() ObjectInfo
|
|
}
|
|
|
|
// filteredObjectIterator removes any iteration entries that do not begin with the filter.
|
|
// all entries must begin with the trim string which is removed before checking for the
|
|
// filter.
|
|
type filteredObjectIterator struct {
|
|
trim ulloc.Location
|
|
filter ulloc.Location
|
|
iter ObjectIterator
|
|
}
|
|
|
|
func (f *filteredObjectIterator) Next() bool {
|
|
for {
|
|
if !f.iter.Next() {
|
|
return false
|
|
}
|
|
loc := f.iter.Item().Loc
|
|
if !loc.HasPrefix(f.trim) {
|
|
return false
|
|
}
|
|
if loc.HasPrefix(f.filter.AsDirectoryish()) || loc == f.filter {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
|
|
func (f *filteredObjectIterator) Err() error { return f.iter.Err() }
|
|
|
|
func (f *filteredObjectIterator) Item() ObjectInfo {
|
|
item := f.iter.Item()
|
|
item.Loc = item.Loc.RemovePrefix(f.trim)
|
|
return item
|
|
}
|
|
|
|
// emptyObjectIterator is an objectIterator that has no objects.
|
|
type emptyObjectIterator struct{}
|
|
|
|
func (emptyObjectIterator) Next() bool { return false }
|
|
func (emptyObjectIterator) Err() error { return nil }
|
|
func (emptyObjectIterator) Item() ObjectInfo { return ObjectInfo{} }
|