github.com/10XDev/rclone@v1.52.3-0.20200626220027-16af9ab76b2a/backend/tardigrade/object.go (about)

     1  // +build go1.13,!plan9
     2  
     3  package tardigrade
     4  
     5  import (
     6  	"context"
     7  	"io"
     8  	"path"
     9  	"time"
    10  
    11  	"github.com/pkg/errors"
    12  	"github.com/rclone/rclone/fs"
    13  	"github.com/rclone/rclone/fs/hash"
    14  	"github.com/rclone/rclone/lib/bucket"
    15  	"golang.org/x/text/unicode/norm"
    16  
    17  	"storj.io/uplink"
    18  )
    19  
    20  // Object describes a Tardigrade object
    21  type Object struct {
    22  	fs *Fs
    23  
    24  	absolute string
    25  
    26  	size     int64
    27  	created  time.Time
    28  	modified time.Time
    29  }
    30  
    31  // Check the interfaces are satisfied.
    32  var _ fs.Object = &Object{}
    33  
    34  // newObjectFromUplink creates a new object from a Tardigrade uplink object.
    35  func newObjectFromUplink(f *Fs, relative string, object *uplink.Object) *Object {
    36  	// Attempt to use the modified time from the metadata. Otherwise
    37  	// fallback to the server time.
    38  	modified := object.System.Created
    39  
    40  	if modifiedStr, ok := object.Custom["rclone:mtime"]; ok {
    41  		var err error
    42  
    43  		modified, err = time.Parse(time.RFC3339Nano, modifiedStr)
    44  		if err != nil {
    45  			modified = object.System.Created
    46  		}
    47  	}
    48  
    49  	bucketName, _ := bucket.Split(path.Join(f.root, relative))
    50  
    51  	return &Object{
    52  		fs: f,
    53  
    54  		absolute: norm.NFC.String(bucketName + "/" + object.Key),
    55  
    56  		size:     object.System.ContentLength,
    57  		created:  object.System.Created,
    58  		modified: modified,
    59  	}
    60  }
    61  
    62  // String returns a description of the Object
    63  func (o *Object) String() string {
    64  	if o == nil {
    65  		return "<nil>"
    66  	}
    67  
    68  	return o.Remote()
    69  }
    70  
    71  // Remote returns the remote path
    72  func (o *Object) Remote() string {
    73  	// It is possible that we have an empty root (meaning the filesystem is
    74  	// rooted at the project level). In this case the relative path is just
    75  	// the full absolute path to the object (including the bucket name).
    76  	if o.fs.root == "" {
    77  		return o.absolute
    78  	}
    79  
    80  	// At this point we know that the filesystem itself is at least a
    81  	// bucket name (and possibly a prefix path).
    82  	//
    83  	//                               . This is necessary to remove the slash.
    84  	//                               |
    85  	//                               v
    86  	return o.absolute[len(o.fs.root)+1:]
    87  }
    88  
    89  // ModTime returns the modification date of the file
    90  // It should return a best guess if one isn't available
    91  func (o *Object) ModTime(ctx context.Context) time.Time {
    92  	return o.modified
    93  }
    94  
    95  // Size returns the size of the file
    96  func (o *Object) Size() int64 {
    97  	return o.size
    98  }
    99  
   100  // Fs returns read only access to the Fs that this object is part of
   101  func (o *Object) Fs() fs.Info {
   102  	return o.fs
   103  }
   104  
   105  // Hash returns the selected checksum of the file
   106  // If no checksum is available it returns ""
   107  func (o *Object) Hash(ctx context.Context, ty hash.Type) (_ string, err error) {
   108  	fs.Debugf(o, "%s", ty)
   109  
   110  	return "", hash.ErrUnsupported
   111  }
   112  
   113  // Storable says whether this object can be stored
   114  func (o *Object) Storable() bool {
   115  	return true
   116  }
   117  
   118  // SetModTime sets the metadata on the object to set the modification date
   119  func (o *Object) SetModTime(ctx context.Context, t time.Time) (err error) {
   120  	fs.Debugf(o, "touch -d %q sj://%s", t, o.absolute)
   121  
   122  	return fs.ErrorCantSetModTime
   123  }
   124  
   125  // Open opens the file for read. Call Close() on the returned io.ReadCloser
   126  func (o *Object) Open(ctx context.Context, options ...fs.OpenOption) (_ io.ReadCloser, err error) {
   127  	fs.Debugf(o, "cat sj://%s # %+v", o.absolute, options)
   128  
   129  	bucketName, bucketPath := bucket.Split(o.absolute)
   130  
   131  	// Convert the semantics of HTTP range headers to an offset and length
   132  	// that libuplink can use.
   133  	var (
   134  		offset int64 = 0
   135  		length int64 = -1
   136  	)
   137  
   138  	for _, option := range options {
   139  		switch opt := option.(type) {
   140  		case *fs.RangeOption:
   141  			s := opt.Start >= 0
   142  			e := opt.End >= 0
   143  
   144  			switch {
   145  			case s && e:
   146  				offset = opt.Start
   147  				length = (opt.End + 1) - opt.Start
   148  			case s && !e:
   149  				offset = opt.Start
   150  			case !s && e:
   151  				object, err := o.fs.project.StatObject(ctx, bucketName, bucketPath)
   152  				if err != nil {
   153  					return nil, err
   154  				}
   155  
   156  				offset = object.System.ContentLength - opt.End
   157  				length = opt.End
   158  			}
   159  		case *fs.SeekOption:
   160  			offset = opt.Offset
   161  		default:
   162  			if option.Mandatory() {
   163  				fs.Errorf(o, "Unsupported mandatory option: %v", option)
   164  
   165  				return nil, errors.New("unsupported mandatory option")
   166  			}
   167  		}
   168  	}
   169  
   170  	fs.Debugf(o, "range %d + %d", offset, length)
   171  
   172  	return o.fs.project.DownloadObject(ctx, bucketName, bucketPath, &uplink.DownloadOptions{
   173  		Offset: offset,
   174  		Length: length,
   175  	})
   176  }
   177  
   178  // Update in to the object with the modTime given of the given size
   179  //
   180  // When called from outside an Fs by rclone, src.Size() will always be >= 0.
   181  // But for unknown-sized objects (indicated by src.Size() == -1), Upload should either
   182  // return an error or update the object properly (rather than e.g. calling panic).
   183  func (o *Object) Update(ctx context.Context, in io.Reader, src fs.ObjectInfo, options ...fs.OpenOption) (err error) {
   184  	fs.Debugf(o, "cp input ./%s %+v", src.Remote(), options)
   185  
   186  	oNew, err := o.fs.Put(ctx, in, src, options...)
   187  
   188  	if err == nil {
   189  		*o = *(oNew.(*Object))
   190  	}
   191  
   192  	return err
   193  }
   194  
   195  // Remove this object.
   196  func (o *Object) Remove(ctx context.Context) (err error) {
   197  	fs.Debugf(o, "rm sj://%s", o.absolute)
   198  
   199  	bucketName, bucketPath := bucket.Split(o.absolute)
   200  
   201  	_, err = o.fs.project.DeleteObject(ctx, bucketName, bucketPath)
   202  
   203  	return err
   204  }