mirror of
https://github.com/rclone/rclone.git
synced 2024-11-27 10:50:03 +08:00
e43b5ce5e5
This is possible now that we no longer support go1.12 and brings rclone into line with standard practices in the Go world. This also removes errors.New and errors.Errorf from lib/errors and prefers the stdlib errors package over lib/errors.
200 lines
4.9 KiB
Go
200 lines
4.9 KiB
Go
//go:build !plan9
|
|
// +build !plan9
|
|
|
|
package tardigrade
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"io"
|
|
"path"
|
|
"time"
|
|
|
|
"github.com/rclone/rclone/fs"
|
|
"github.com/rclone/rclone/fs/hash"
|
|
"github.com/rclone/rclone/lib/bucket"
|
|
"golang.org/x/text/unicode/norm"
|
|
|
|
"storj.io/uplink"
|
|
)
|
|
|
|
// Object describes a Tardigrade object
|
|
type Object struct {
|
|
fs *Fs
|
|
|
|
absolute string
|
|
|
|
size int64
|
|
created time.Time
|
|
modified time.Time
|
|
}
|
|
|
|
// Check the interfaces are satisfied.
|
|
var _ fs.Object = &Object{}
|
|
|
|
// newObjectFromUplink creates a new object from a Tardigrade uplink object.
|
|
func newObjectFromUplink(f *Fs, relative string, object *uplink.Object) *Object {
|
|
// Attempt to use the modified time from the metadata. Otherwise
|
|
// fallback to the server time.
|
|
modified := object.System.Created
|
|
|
|
if modifiedStr, ok := object.Custom["rclone:mtime"]; ok {
|
|
var err error
|
|
|
|
modified, err = time.Parse(time.RFC3339Nano, modifiedStr)
|
|
if err != nil {
|
|
modified = object.System.Created
|
|
}
|
|
}
|
|
|
|
bucketName, _ := bucket.Split(path.Join(f.root, relative))
|
|
|
|
return &Object{
|
|
fs: f,
|
|
|
|
absolute: norm.NFC.String(bucketName + "/" + object.Key),
|
|
|
|
size: object.System.ContentLength,
|
|
created: object.System.Created,
|
|
modified: modified,
|
|
}
|
|
}
|
|
|
|
// String returns a description of the Object
|
|
func (o *Object) String() string {
|
|
if o == nil {
|
|
return "<nil>"
|
|
}
|
|
|
|
return o.Remote()
|
|
}
|
|
|
|
// Remote returns the remote path
|
|
func (o *Object) Remote() string {
|
|
// It is possible that we have an empty root (meaning the filesystem is
|
|
// rooted at the project level). In this case the relative path is just
|
|
// the full absolute path to the object (including the bucket name).
|
|
if o.fs.root == "" {
|
|
return o.absolute
|
|
}
|
|
|
|
// At this point we know that the filesystem itself is at least a
|
|
// bucket name (and possibly a prefix path).
|
|
//
|
|
// . This is necessary to remove the slash.
|
|
// |
|
|
// v
|
|
return o.absolute[len(o.fs.root)+1:]
|
|
}
|
|
|
|
// ModTime returns the modification date of the file
|
|
// It should return a best guess if one isn't available
|
|
func (o *Object) ModTime(ctx context.Context) time.Time {
|
|
return o.modified
|
|
}
|
|
|
|
// Size returns the size of the file
|
|
func (o *Object) Size() int64 {
|
|
return o.size
|
|
}
|
|
|
|
// Fs returns read only access to the Fs that this object is part of
|
|
func (o *Object) Fs() fs.Info {
|
|
return o.fs
|
|
}
|
|
|
|
// Hash returns the selected checksum of the file
|
|
// If no checksum is available it returns ""
|
|
func (o *Object) Hash(ctx context.Context, ty hash.Type) (_ string, err error) {
|
|
fs.Debugf(o, "%s", ty)
|
|
|
|
return "", hash.ErrUnsupported
|
|
}
|
|
|
|
// Storable says whether this object can be stored
|
|
func (o *Object) Storable() bool {
|
|
return true
|
|
}
|
|
|
|
// SetModTime sets the metadata on the object to set the modification date
|
|
func (o *Object) SetModTime(ctx context.Context, t time.Time) (err error) {
|
|
fs.Debugf(o, "touch -d %q sj://%s", t, o.absolute)
|
|
|
|
return fs.ErrorCantSetModTime
|
|
}
|
|
|
|
// Open opens the file for read. Call Close() on the returned io.ReadCloser
|
|
func (o *Object) Open(ctx context.Context, options ...fs.OpenOption) (_ io.ReadCloser, err error) {
|
|
fs.Debugf(o, "cat sj://%s # %+v", o.absolute, options)
|
|
|
|
bucketName, bucketPath := bucket.Split(o.absolute)
|
|
|
|
// Convert the semantics of HTTP range headers to an offset and length
|
|
// that libuplink can use.
|
|
var (
|
|
offset int64
|
|
length int64 = -1
|
|
)
|
|
|
|
for _, option := range options {
|
|
switch opt := option.(type) {
|
|
case *fs.RangeOption:
|
|
s := opt.Start >= 0
|
|
e := opt.End >= 0
|
|
|
|
switch {
|
|
case s && e:
|
|
offset = opt.Start
|
|
length = (opt.End + 1) - opt.Start
|
|
case s && !e:
|
|
offset = opt.Start
|
|
case !s && e:
|
|
offset = -opt.End
|
|
}
|
|
case *fs.SeekOption:
|
|
offset = opt.Offset
|
|
default:
|
|
if option.Mandatory() {
|
|
fs.Errorf(o, "Unsupported mandatory option: %v", option)
|
|
|
|
return nil, errors.New("unsupported mandatory option")
|
|
}
|
|
}
|
|
}
|
|
|
|
fs.Debugf(o, "range %d + %d", offset, length)
|
|
|
|
return o.fs.project.DownloadObject(ctx, bucketName, bucketPath, &uplink.DownloadOptions{
|
|
Offset: offset,
|
|
Length: length,
|
|
})
|
|
}
|
|
|
|
// Update in to the object with the modTime given of the given size
|
|
//
|
|
// When called from outside an Fs by rclone, src.Size() will always be >= 0.
|
|
// But for unknown-sized objects (indicated by src.Size() == -1), Upload should either
|
|
// return an error or update the object properly (rather than e.g. calling panic).
|
|
func (o *Object) Update(ctx context.Context, in io.Reader, src fs.ObjectInfo, options ...fs.OpenOption) (err error) {
|
|
fs.Debugf(o, "cp input ./%s %+v", src.Remote(), options)
|
|
|
|
oNew, err := o.fs.Put(ctx, in, src, options...)
|
|
|
|
if err == nil {
|
|
*o = *(oNew.(*Object))
|
|
}
|
|
|
|
return err
|
|
}
|
|
|
|
// Remove this object.
|
|
func (o *Object) Remove(ctx context.Context) (err error) {
|
|
fs.Debugf(o, "rm sj://%s", o.absolute)
|
|
|
|
bucketName, bucketPath := bucket.Split(o.absolute)
|
|
|
|
_, err = o.fs.project.DeleteObject(ctx, bucketName, bucketPath)
|
|
|
|
return err
|
|
}
|