github.com/artpar/rclone@v1.67.3/backend/storj/object.go (about)

     1  //go:build !plan9
     2  
     3  package storj
     4  
     5  import (
     6  	"context"
     7  	"errors"
     8  	"io"
     9  	"path"
    10  	"time"
    11  
    12  	"github.com/artpar/rclone/fs"
    13  	"github.com/artpar/rclone/fs/hash"
    14  	"github.com/artpar/rclone/lib/bucket"
    15  	"golang.org/x/text/unicode/norm"
    16  
    17  	"storj.io/uplink"
    18  )
    19  
    20  // Object describes a Storj 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 Storj 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
   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  				offset = -opt.End
   152  			}
   153  		case *fs.SeekOption:
   154  			offset = opt.Offset
   155  		default:
   156  			if option.Mandatory() {
   157  				fs.Errorf(o, "Unsupported mandatory option: %v", option)
   158  
   159  				return nil, errors.New("unsupported mandatory option")
   160  			}
   161  		}
   162  	}
   163  
   164  	fs.Debugf(o, "range %d + %d", offset, length)
   165  
   166  	return o.fs.project.DownloadObject(ctx, bucketName, bucketPath, &uplink.DownloadOptions{
   167  		Offset: offset,
   168  		Length: length,
   169  	})
   170  }
   171  
   172  // Update in to the object with the modTime given of the given size
   173  //
   174  // When called from outside an Fs by rclone, src.Size() will always be >= 0.
   175  // But for unknown-sized objects (indicated by src.Size() == -1), Upload should either
   176  // return an error or update the object properly (rather than e.g. calling panic).
   177  func (o *Object) Update(ctx context.Context, in io.Reader, src fs.ObjectInfo, options ...fs.OpenOption) (err error) {
   178  	fs.Debugf(o, "cp input ./%s %+v", o.Remote(), options)
   179  
   180  	oNew, err := o.fs.put(ctx, in, src, o.Remote(), options...)
   181  
   182  	if err == nil {
   183  		*o = *(oNew.(*Object))
   184  	}
   185  
   186  	return err
   187  }
   188  
   189  // Remove this object.
   190  func (o *Object) Remove(ctx context.Context) (err error) {
   191  	fs.Debugf(o, "rm sj://%s", o.absolute)
   192  
   193  	bucketName, bucketPath := bucket.Split(o.absolute)
   194  
   195  	_, err = o.fs.project.DeleteObject(ctx, bucketName, bucketPath)
   196  
   197  	return err
   198  }