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  }