131 lines
3.4 KiB
Go
131 lines
3.4 KiB
Go
// Copyright (C) 2020 Storj Labs, Inc.
|
|
// See LICENSE for copying information.
|
|
|
|
package uplink
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
"unicode/utf8"
|
|
|
|
"github.com/zeebo/errs"
|
|
|
|
"storj.io/common/storj"
|
|
)
|
|
|
|
// ErrObjectKeyInvalid is returned when the object key is invalid.
|
|
var ErrObjectKeyInvalid = errors.New("object key invalid")
|
|
|
|
// ErrObjectNotFound is returned when the object is not found.
|
|
var ErrObjectNotFound = errors.New("object not found")
|
|
|
|
// Object contains information about an object.
|
|
type Object struct {
|
|
Key string
|
|
// IsPrefix indicates whether the Key is a prefix for other objects.
|
|
IsPrefix bool
|
|
|
|
System SystemMetadata
|
|
Custom CustomMetadata
|
|
}
|
|
|
|
// SystemMetadata contains information about the object that cannot be changed directly.
|
|
type SystemMetadata struct {
|
|
Created time.Time
|
|
Expires time.Time
|
|
ContentLength int64
|
|
}
|
|
|
|
// CustomMetadata contains custom user metadata about the object.
|
|
//
|
|
// The keys and values in custom metadata are expected to be valid UTF-8.
|
|
//
|
|
// When choosing a custom key for your application start it with a prefix "app:key",
|
|
// as an example application named "Image Board" might use a key "image-board:title".
|
|
type CustomMetadata map[string]string
|
|
|
|
// Clone makes a deep clone.
|
|
func (meta CustomMetadata) Clone() CustomMetadata {
|
|
r := CustomMetadata{}
|
|
for k, v := range meta {
|
|
r[k] = v
|
|
}
|
|
return r
|
|
}
|
|
|
|
// Verify verifies whether CustomMetadata contains only "utf-8".
|
|
func (meta CustomMetadata) Verify() error {
|
|
var invalid []string
|
|
for k, v := range meta {
|
|
if !utf8.ValidString(k) || !utf8.ValidString(v) {
|
|
invalid = append(invalid, fmt.Sprintf("not utf-8 %q=%q", k, v))
|
|
}
|
|
if strings.IndexByte(k, 0) >= 0 || strings.IndexByte(v, 0) >= 0 {
|
|
invalid = append(invalid, fmt.Sprintf("contains 0 byte: %q=%q", k, v))
|
|
}
|
|
if k == "" {
|
|
invalid = append(invalid, "empty key")
|
|
}
|
|
}
|
|
|
|
if len(invalid) > 0 {
|
|
return errs.New("invalid pairs %v", invalid)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// StatObject returns information about an object at the specific key.
|
|
func (project *Project) StatObject(ctx context.Context, bucket, key string) (info *Object, err error) {
|
|
defer mon.Func().ResetTrace(&ctx)(&err)
|
|
|
|
b := storj.Bucket{Name: bucket}
|
|
obj, err := project.db.GetObject(ctx, b, key)
|
|
if err != nil {
|
|
if storj.ErrNoPath.Has(err) {
|
|
return nil, errwrapf("%w (%q)", ErrObjectKeyInvalid, key)
|
|
} else if storj.ErrObjectNotFound.Has(err) {
|
|
return nil, errwrapf("%w (%q)", ErrObjectNotFound, key)
|
|
}
|
|
return nil, convertKnownErrors(err, bucket)
|
|
}
|
|
|
|
return convertObject(&obj), nil
|
|
}
|
|
|
|
// DeleteObject deletes the object at the specific key.
|
|
func (project *Project) DeleteObject(ctx context.Context, bucket, key string) (deleted *Object, err error) {
|
|
defer mon.Func().ResetTrace(&ctx)(&err)
|
|
|
|
b := storj.Bucket{Name: bucket}
|
|
obj, err := project.db.DeleteObject(ctx, b, key)
|
|
if err != nil {
|
|
if storj.ErrNoPath.Has(err) {
|
|
return nil, errwrapf("%w (%q)", ErrObjectKeyInvalid, key)
|
|
} else if storj.ErrObjectNotFound.Has(err) {
|
|
return nil, errwrapf("%w (%q)", ErrObjectNotFound, key)
|
|
}
|
|
return nil, convertKnownErrors(err, bucket)
|
|
}
|
|
return convertObject(&obj), nil
|
|
}
|
|
|
|
// convertObject converts storj.Object to uplink.Object.
|
|
func convertObject(obj *storj.Object) *Object {
|
|
if obj.Bucket.Name == "" { // zero object
|
|
return nil
|
|
}
|
|
|
|
return &Object{
|
|
Key: obj.Path,
|
|
System: SystemMetadata{
|
|
Created: obj.Created,
|
|
Expires: obj.Expires,
|
|
ContentLength: obj.Size,
|
|
},
|
|
Custom: obj.Metadata,
|
|
}
|
|
}
|