storj.io/uplink@v1.13.0/object.go (about) 1 // Copyright (C) 2020 Storj Labs, Inc. 2 // See LICENSE for copying information. 3 4 package uplink 5 6 import ( 7 "context" 8 "errors" 9 "fmt" 10 "strings" 11 "time" 12 "unicode/utf8" 13 _ "unsafe" // for go:linkname 14 15 "github.com/zeebo/errs" 16 17 "storj.io/uplink/private/metaclient" 18 ) 19 20 // ErrObjectKeyInvalid is returned when the object key is invalid. 21 var ErrObjectKeyInvalid = errors.New("object key invalid") 22 23 // ErrObjectNotFound is returned when the object is not found. 24 var ErrObjectNotFound = errors.New("object not found") 25 26 // Object contains information about an object. 27 type Object struct { 28 Key string 29 // IsPrefix indicates whether the Key is a prefix for other objects. 30 IsPrefix bool 31 32 System SystemMetadata 33 Custom CustomMetadata 34 35 version []byte 36 } 37 38 // SystemMetadata contains information about the object that cannot be changed directly. 39 type SystemMetadata struct { 40 Created time.Time 41 Expires time.Time 42 ContentLength int64 43 } 44 45 // CustomMetadata contains custom user metadata about the object. 46 // 47 // The keys and values in custom metadata are expected to be valid UTF-8. 48 // 49 // When choosing a custom key for your application start it with a prefix "app:key", 50 // as an example application named "Image Board" might use a key "image-board:title". 51 type CustomMetadata map[string]string 52 53 // Clone makes a deep clone. 54 func (meta CustomMetadata) Clone() CustomMetadata { 55 r := CustomMetadata{} 56 for k, v := range meta { 57 r[k] = v 58 } 59 return r 60 } 61 62 // Verify verifies whether CustomMetadata contains only "utf-8". 63 func (meta CustomMetadata) Verify() error { 64 var invalid []string 65 for k, v := range meta { 66 if !utf8.ValidString(k) || !utf8.ValidString(v) { 67 invalid = append(invalid, fmt.Sprintf("not utf-8 %q=%q", k, v)) 68 } 69 if strings.IndexByte(k, 0) >= 0 || strings.IndexByte(v, 0) >= 0 { 70 invalid = append(invalid, fmt.Sprintf("contains 0 byte: %q=%q", k, v)) 71 } 72 if k == "" { 73 invalid = append(invalid, "empty key") 74 } 75 } 76 77 if len(invalid) > 0 { 78 return errs.New("invalid pairs %v", invalid) 79 } 80 81 return nil 82 } 83 84 // StatObject returns information about an object at the specific key. 85 func (project *Project) StatObject(ctx context.Context, bucket, key string) (info *Object, err error) { 86 defer mon.Task()(&ctx)(&err) 87 88 db, err := project.dialMetainfoDB(ctx) 89 if err != nil { 90 return nil, convertKnownErrors(err, bucket, key) 91 } 92 defer func() { err = errs.Combine(err, db.Close()) }() 93 94 obj, err := db.GetObject(ctx, bucket, key, nil) 95 if err != nil { 96 return nil, convertKnownErrors(err, bucket, key) 97 } 98 99 return convertObject(&obj), nil 100 } 101 102 // DeleteObject deletes the object at the specific key. 103 // Returned deleted is not nil when the access grant has read permissions and 104 // the object was deleted. 105 func (project *Project) DeleteObject(ctx context.Context, bucket, key string) (deleted *Object, err error) { 106 defer mon.Task()(&ctx)(&err) 107 108 db, err := project.dialMetainfoDB(ctx) 109 if err != nil { 110 return nil, convertKnownErrors(err, bucket, key) 111 } 112 defer func() { err = errs.Combine(err, db.Close()) }() 113 114 obj, err := db.DeleteObject(ctx, bucket, key, nil) 115 if err != nil { 116 return nil, convertKnownErrors(err, bucket, key) 117 } 118 return convertObject(&obj), nil 119 } 120 121 // UploadObjectMetadataOptions contains additional options for updating object's metadata. 122 // Reserved for future use. 123 type UploadObjectMetadataOptions struct { 124 } 125 126 // UpdateObjectMetadata replaces the custom metadata for the object at the specific key with newMetadata. 127 // Any existing custom metadata will be deleted. 128 func (project *Project) UpdateObjectMetadata(ctx context.Context, bucket, key string, newMetadata CustomMetadata, options *UploadObjectMetadataOptions) (err error) { 129 defer mon.Task()(&ctx)(&err) 130 131 db, err := project.dialMetainfoDB(ctx) 132 if err != nil { 133 return convertKnownErrors(err, bucket, key) 134 } 135 defer func() { err = errs.Combine(err, db.Close()) }() 136 137 err = db.UpdateObjectMetadata(ctx, bucket, key, newMetadata.Clone()) 138 if err != nil { 139 return convertKnownErrors(err, bucket, key) 140 } 141 142 return nil 143 } 144 145 // convertObject converts metaclient.Object to uplink.Object. 146 func convertObject(obj *metaclient.Object) *Object { 147 if obj == nil || obj.Bucket.Name == "" { // nil or zero object 148 return nil 149 } 150 151 object := &Object{ 152 Key: obj.Path, 153 IsPrefix: obj.IsPrefix, 154 System: SystemMetadata{ 155 Created: obj.Created, 156 Expires: obj.Expires, 157 ContentLength: obj.Size, 158 }, 159 Custom: obj.Metadata, 160 161 version: obj.Version, 162 } 163 164 if object.Custom == nil { 165 object.Custom = CustomMetadata{} 166 } 167 168 return object 169 } 170 171 // objectVersion is exposing object version field. 172 // 173 // NB: this is used with linkname in private/object. 174 // It needs to be updated when this is updated. 175 // 176 //lint:ignore U1000, used with linkname 177 //nolint:deadcode,unused 178 //go:linkname objectVersion 179 func objectVersion(object *Object) []byte { 180 return object.version 181 }