storj.io/uplink@v1.13.0/private/object/object.go (about) 1 // Copyright (C) 2020 Storj Labs, Inc. 2 // See LICENSE for copying information. 3 4 package object 5 6 import ( 7 "context" 8 "errors" 9 _ "unsafe" // for go:linkname 10 11 "github.com/spacemonkeygo/monkit/v3" 12 "github.com/zeebo/errs" 13 14 "storj.io/common/errs2" 15 "storj.io/common/rpc/rpcstatus" 16 "storj.io/common/storj" 17 "storj.io/uplink" 18 "storj.io/uplink/internal/expose" 19 "storj.io/uplink/private/metaclient" 20 ) 21 22 var mon = monkit.Package() 23 24 // Error is default error class for uplink. 25 var packageError = errs.Class("object") 26 27 // ErrMethodNotAllowed is returned when method is not allowed against specified entity (e.g. object). 28 var ErrMethodNotAllowed = errors.New("method not allowed") 29 30 // IPSummary contains information about the object IP-s. 31 type IPSummary = metaclient.GetObjectIPsResponse 32 33 // VersionedObject represents object with version. 34 // TODO find better place of name for this and related things. 35 type VersionedObject struct { 36 uplink.Object 37 Version []byte 38 IsDeleteMarker bool 39 Retention *metaclient.Retention 40 } 41 42 // VersionedUpload represents upload which returnes object version at the end. 43 type VersionedUpload struct { 44 upload *uplink.Upload 45 } 46 47 // ListObjectVersionsOptions defines listing options for versioned objects. 48 type ListObjectVersionsOptions struct { 49 Prefix string 50 Cursor string 51 VersionCursor []byte 52 Recursive bool 53 System bool 54 Custom bool 55 Limit int 56 } 57 58 // Info returns the last information about the uploaded object. 59 func (upload *VersionedUpload) Info() *VersionedObject { 60 info := upload.upload.Info() 61 return convertUplinkObject(info) 62 } 63 64 // Write uploads len(p) bytes from p to the object's data stream. 65 // It returns the number of bytes written from p (0 <= n <= len(p)) 66 // and any error encountered that caused the write to stop early. 67 func (upload *VersionedUpload) Write(p []byte) (n int, err error) { 68 return upload.upload.Write(p) 69 } 70 71 // Commit commits data to the store. 72 // 73 // Returns ErrUploadDone when either Abort or Commit has already been called. 74 func (upload *VersionedUpload) Commit() error { 75 return upload.upload.Commit() 76 } 77 78 // SetCustomMetadata updates custom metadata to be included with the object. 79 // If it is nil, it won't be modified. 80 func (upload *VersionedUpload) SetCustomMetadata(ctx context.Context, custom uplink.CustomMetadata) error { 81 return upload.upload.SetCustomMetadata(ctx, custom) 82 } 83 84 // Abort aborts the upload. 85 // 86 // Returns ErrUploadDone when either Abort or Commit has already been called. 87 func (upload *VersionedUpload) Abort() error { 88 return upload.upload.Abort() 89 } 90 91 // VersionedDownload is a download from Storj Network. 92 type VersionedDownload struct { 93 download *uplink.Download 94 } 95 96 // Info returns the last information about the object. 97 func (download *VersionedDownload) Info() *VersionedObject { 98 return convertObject(download_getMetaclientObject(download.download)) 99 } 100 101 // Read downloads up to len(p) bytes into p from the object's data stream. 102 // It returns the number of bytes read (0 <= n <= len(p)) and any error encountered. 103 func (download *VersionedDownload) Read(p []byte) (n int, err error) { 104 return download.download.Read(p) 105 } 106 107 // Close closes the reader of the download. 108 func (download *VersionedDownload) Close() error { 109 return download.download.Close() 110 } 111 112 // GetObjectIPs returns the IP-s for a given object. 113 // 114 // TODO: delete, once we have stopped using it. 115 func GetObjectIPs(ctx context.Context, config uplink.Config, access *uplink.Access, bucket, key string) (_ [][]byte, err error) { 116 summary, err := GetObjectIPSummary(ctx, config, access, bucket, key) 117 if err != nil { 118 return nil, packageError.Wrap(err) 119 } 120 return summary.IPPorts, nil 121 } 122 123 // GetObjectIPSummary returns the object IP summary. 124 func GetObjectIPSummary(ctx context.Context, config uplink.Config, access *uplink.Access, bucket, key string) (_ *IPSummary, err error) { 125 defer mon.Task()(&ctx)(&err) 126 127 dialer, err := expose.ConfigGetDialer(config, ctx) 128 if err != nil { 129 return nil, packageError.Wrap(err) 130 } 131 defer func() { err = errs.Combine(err, dialer.Pool.Close()) }() 132 133 metainfoClient, err := metaclient.DialNodeURL(ctx, dialer, access.SatelliteAddress(), expose.AccessGetAPIKey(access), config.UserAgent) 134 if err != nil { 135 return nil, packageError.Wrap(err) 136 } 137 defer func() { err = errs.Combine(err, metainfoClient.Close()) }() 138 139 db := metaclient.New(metainfoClient, storj.EncryptionParameters{}, expose.AccessGetEncAccess(access).Store) 140 141 summary, err := db.GetObjectIPs(ctx, metaclient.Bucket{Name: bucket}, key) 142 return summary, packageError.Wrap(err) 143 } 144 145 // StatObject returns information about an object at the specific key and version. 146 func StatObject(ctx context.Context, project *uplink.Project, bucket, key string, version []byte) (info *VersionedObject, err error) { 147 defer mon.Task()(&ctx)(&err) 148 149 db, err := dialMetainfoDB(ctx, project) 150 if err != nil { 151 return nil, packageConvertKnownErrors(err, bucket, key) 152 } 153 defer func() { err = errs.Combine(err, db.Close()) }() 154 155 obj, err := db.GetObject(ctx, bucket, key, version) 156 if err != nil { 157 return nil, packageConvertKnownErrors(err, bucket, key) 158 } 159 160 return convertObject(&obj), nil 161 } 162 163 // DeleteObject deletes the object at the specific key. 164 // Returned deleted is not nil when the access grant has read permissions and 165 // the object was deleted. 166 // TODO(ver) currently we are returning object that was returned by satellite 167 // if its regular object (status != delete marker) it means no delete marker 168 // was created. 169 func DeleteObject(ctx context.Context, project *uplink.Project, bucket, key string, version []byte) (info *VersionedObject, err error) { 170 defer mon.Task()(&ctx)(&err) 171 172 db, err := dialMetainfoDB(ctx, project) 173 if err != nil { 174 return nil, packageConvertKnownErrors(err, bucket, key) 175 } 176 defer func() { err = errs.Combine(err, db.Close()) }() 177 178 obj, err := db.DeleteObject(ctx, bucket, key, version) 179 if err != nil { 180 return nil, packageConvertKnownErrors(err, bucket, key) 181 } 182 183 return convertObject(&obj), nil 184 } 185 186 // ListObjectVersions returns a list of objects and their versions. 187 func ListObjectVersions(ctx context.Context, project *uplink.Project, bucket string, options *ListObjectVersionsOptions) (_ []*VersionedObject, more bool, err error) { 188 defer mon.Task()(&ctx)(&err) 189 190 db, err := dialMetainfoDB(ctx, project) 191 if err != nil { 192 return nil, false, convertKnownErrors(err, bucket, "") 193 } 194 defer func() { err = errs.Combine(err, db.Close()) }() 195 196 opts := metaclient.ListOptions{ 197 Direction: metaclient.After, 198 IncludeAllVersions: true, 199 } 200 201 if options != nil { 202 opts.Prefix = options.Prefix 203 opts.Cursor = options.Cursor 204 opts.VersionCursor = options.VersionCursor 205 opts.Recursive = options.Recursive 206 opts.IncludeCustomMetadata = options.Custom 207 opts.IncludeSystemMetadata = options.System 208 opts.Limit = options.Limit 209 } 210 211 obj, err := db.ListObjects(ctx, bucket, opts) 212 if err != nil { 213 return nil, false, convertKnownErrors(err, bucket, "") 214 } 215 216 var versions []*VersionedObject 217 for _, o := range obj.Items { 218 versions = append(versions, convertObject(&o)) 219 } 220 221 return versions, obj.More, nil 222 } 223 224 // UploadObject starts an upload to the specific key. 225 // 226 // It is not guaranteed that the uncommitted object is visible through ListUploads while uploading. 227 func UploadObject(ctx context.Context, project *uplink.Project, bucket, key string, options *uplink.UploadOptions) (_ *VersionedUpload, err error) { 228 defer mon.Task()(&ctx)(&err) 229 230 upload, err := project.UploadObject(ctx, bucket, key, options) 231 if err != nil { 232 return 233 } 234 return &VersionedUpload{ 235 upload: upload, 236 }, nil 237 } 238 239 // DownloadObject starts a download from the specific key and version. If version is empty latest object will be downloaded. 240 func DownloadObject(ctx context.Context, project *uplink.Project, bucket, key string, version []byte, options *uplink.DownloadOptions) (_ *VersionedDownload, err error) { 241 defer mon.Task()(&ctx)(&err) 242 243 download, err := downloadObjectWithVersion(ctx, project, bucket, key, version, options) 244 if err != nil { 245 return nil, packageConvertKnownErrors(err, bucket, key) 246 } 247 return &VersionedDownload{ 248 download: download, 249 }, nil 250 } 251 252 // CommitUpload commits a multipart upload to bucket and key started with BeginUpload. 253 // 254 // uploadID is an upload identifier returned by BeginUpload. 255 func CommitUpload(ctx context.Context, project *uplink.Project, bucket, key, uploadID string, opts *uplink.CommitUploadOptions) (info *VersionedObject, err error) { 256 defer mon.Task()(&ctx)(&err) 257 258 obj, err := project.CommitUpload(ctx, bucket, key, uploadID, opts) 259 if err != nil { 260 return nil, err 261 } 262 263 return convertUplinkObject(obj), nil 264 } 265 266 // CopyObject atomically copies object to a different bucket or/and key. 267 func CopyObject(ctx context.Context, project *uplink.Project, sourceBucket, sourceKey string, sourceVersion []byte, targetBucket, targetKey string, options *uplink.CopyObjectOptions) (_ *VersionedObject, err error) { 268 defer mon.Task()(&ctx)(&err) 269 270 db, err := dialMetainfoDB(ctx, project) 271 if err != nil { 272 return nil, packageConvertKnownErrors(err, sourceBucket, sourceKey) 273 } 274 defer func() { err = errs.Combine(err, db.Close()) }() 275 276 obj, err := db.CopyObject(ctx, sourceBucket, sourceKey, sourceVersion, targetBucket, targetKey) 277 if err != nil { 278 return nil, packageConvertKnownErrors(err, sourceBucket, sourceKey) 279 } 280 281 return convertObject(obj), nil 282 } 283 284 // convertObject converts metainfo.Object to Version. 285 func convertObject(obj *metaclient.Object) *VersionedObject { 286 if obj == nil || obj.Bucket.Name == "" { // nil or zero object 287 return nil 288 } 289 290 return &VersionedObject{ 291 Object: uplink.Object{ 292 Key: obj.Path, 293 IsPrefix: obj.IsPrefix, 294 System: uplink.SystemMetadata{ 295 Created: obj.Created, 296 Expires: obj.Expires, 297 ContentLength: obj.Size, 298 }, 299 Custom: obj.Metadata, 300 }, 301 Version: obj.Version, 302 IsDeleteMarker: obj.IsDeleteMarker, 303 Retention: obj.Retention, 304 } 305 } 306 307 // convertObject converts metainfo.Object to Version. 308 func convertUplinkObject(obj *uplink.Object) *VersionedObject { 309 if obj == nil { 310 return nil 311 } 312 313 return &VersionedObject{ 314 Object: *obj, 315 Version: objectVersion(obj), 316 } 317 } 318 319 func packageConvertKnownErrors(err error, bucket, key string) error { 320 if errs2.IsRPC(err, rpcstatus.MethodNotAllowed) { 321 return ErrMethodNotAllowed 322 } 323 return convertKnownErrors(err, bucket, key) 324 } 325 326 //go:linkname convertKnownErrors storj.io/uplink.convertKnownErrors 327 func convertKnownErrors(err error, bucket, key string) error 328 329 //go:linkname dialMetainfoDB storj.io/uplink.dialMetainfoDB 330 func dialMetainfoDB(ctx context.Context, project *uplink.Project) (_ *metaclient.DB, err error) 331 332 //go:linkname encryptionParameters storj.io/uplink.encryptionParameters 333 func encryptionParameters(project *uplink.Project) storj.EncryptionParameters 334 335 //go:linkname objectVersion storj.io/uplink.objectVersion 336 func objectVersion(object *uplink.Object) []byte 337 338 //go:linkname downloadObjectWithVersion storj.io/uplink.downloadObjectWithVersion 339 func downloadObjectWithVersion(ctx context.Context, project *uplink.Project, bucket, key string, version []byte, options *uplink.DownloadOptions) (_ *uplink.Download, err error) 340 341 //go:linkname download_getMetaclientObject storj.io/uplink.download_getMetaclientObject 342 func download_getMetaclientObject(dl *uplink.Download) *metaclient.Object