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 }