2020-07-24 19:08:08 +01:00
|
|
|
// Copyright (C) 2020 Storj Labs, Inc.
|
|
|
|
// See LICENSE for copying information.
|
|
|
|
|
|
|
|
package metabase
|
|
|
|
|
|
|
|
import (
|
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
"github.com/zeebo/errs"
|
|
|
|
|
|
|
|
"storj.io/common/storj"
|
|
|
|
"storj.io/common/uuid"
|
|
|
|
)
|
|
|
|
|
|
|
|
// Error is the default error for metabase.
|
|
|
|
var Error = errs.Class("metabase")
|
|
|
|
|
|
|
|
// Common constants for segment keys.
|
|
|
|
const (
|
|
|
|
LastSegmentName = "l"
|
|
|
|
LastSegmentIndex = -1
|
|
|
|
FirstSegmentIndex = 0
|
|
|
|
)
|
|
|
|
|
|
|
|
// BucketPrefix consists of <project id>/<bucket name>.
|
|
|
|
type BucketPrefix string
|
|
|
|
|
|
|
|
// BucketLocation defines a bucket that belongs to a project.
|
|
|
|
type BucketLocation struct {
|
|
|
|
ProjectID uuid.UUID
|
|
|
|
BucketName string
|
|
|
|
}
|
|
|
|
|
|
|
|
// ParseBucketPrefix parses BucketPrefix.
|
|
|
|
func ParseBucketPrefix(prefix BucketPrefix) (BucketLocation, error) {
|
|
|
|
elements := strings.Split(string(prefix), "/")
|
|
|
|
if len(elements) != 2 {
|
|
|
|
return BucketLocation{}, Error.New("invalid prefix %q", prefix)
|
|
|
|
}
|
|
|
|
|
|
|
|
projectID, err := uuid.FromString(elements[0])
|
|
|
|
if err != nil {
|
|
|
|
return BucketLocation{}, Error.Wrap(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return BucketLocation{
|
|
|
|
ProjectID: projectID,
|
|
|
|
BucketName: elements[1],
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
2021-01-08 16:04:46 +00:00
|
|
|
// ParseCompactBucketPrefix parses BucketPrefix.
|
|
|
|
func ParseCompactBucketPrefix(compactPrefix []byte) (BucketLocation, error) {
|
2021-01-08 17:33:51 +00:00
|
|
|
if len(compactPrefix) < len(uuid.UUID{}) {
|
2021-01-08 16:04:46 +00:00
|
|
|
return BucketLocation{}, Error.New("invalid prefix %q", compactPrefix)
|
|
|
|
}
|
|
|
|
|
|
|
|
var loc BucketLocation
|
|
|
|
copy(loc.ProjectID[:], compactPrefix)
|
2021-01-08 17:33:51 +00:00
|
|
|
loc.BucketName = string(compactPrefix[len(loc.ProjectID):])
|
2021-01-08 16:04:46 +00:00
|
|
|
return loc, nil
|
|
|
|
}
|
|
|
|
|
2020-07-24 19:08:08 +01:00
|
|
|
// Prefix converts bucket location into bucket prefix.
|
|
|
|
func (loc BucketLocation) Prefix() BucketPrefix {
|
|
|
|
return BucketPrefix(loc.ProjectID.String() + "/" + loc.BucketName)
|
|
|
|
}
|
|
|
|
|
2021-01-08 16:04:46 +00:00
|
|
|
// CompactPrefix converts bucket location into bucket prefix with compact project ID.
|
|
|
|
func (loc BucketLocation) CompactPrefix() []byte {
|
2021-01-08 17:33:51 +00:00
|
|
|
xs := make([]byte, 0, len(loc.ProjectID)+len(loc.BucketName))
|
2021-01-08 16:04:46 +00:00
|
|
|
xs = append(xs, loc.ProjectID[:]...)
|
|
|
|
xs = append(xs, []byte(loc.BucketName)...)
|
|
|
|
return xs
|
|
|
|
}
|
|
|
|
|
2020-07-24 19:08:08 +01:00
|
|
|
// ObjectKey is an encrypted object key encoded using Path Component Encoding.
|
|
|
|
// It is not ascii safe.
|
|
|
|
type ObjectKey string
|
|
|
|
|
|
|
|
// ObjectLocation is decoded object key information.
|
|
|
|
type ObjectLocation struct {
|
|
|
|
ProjectID uuid.UUID
|
|
|
|
BucketName string
|
|
|
|
ObjectKey ObjectKey
|
|
|
|
}
|
|
|
|
|
|
|
|
// Bucket returns bucket location this object belongs to.
|
|
|
|
func (obj ObjectLocation) Bucket() BucketLocation {
|
|
|
|
return BucketLocation{
|
|
|
|
ProjectID: obj.ProjectID,
|
|
|
|
BucketName: obj.BucketName,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// LastSegment returns the last segment location.
|
|
|
|
func (obj ObjectLocation) LastSegment() SegmentLocation {
|
|
|
|
return SegmentLocation{
|
|
|
|
ProjectID: obj.ProjectID,
|
|
|
|
BucketName: obj.BucketName,
|
|
|
|
Index: LastSegmentIndex,
|
|
|
|
ObjectKey: obj.ObjectKey,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// FirstSegment returns the first segment location.
|
|
|
|
func (obj ObjectLocation) FirstSegment() SegmentLocation {
|
|
|
|
return SegmentLocation{
|
|
|
|
ProjectID: obj.ProjectID,
|
|
|
|
BucketName: obj.BucketName,
|
|
|
|
Index: FirstSegmentIndex,
|
|
|
|
ObjectKey: obj.ObjectKey,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Segment returns segment location for a given index.
|
|
|
|
func (obj ObjectLocation) Segment(index int64) (SegmentLocation, error) {
|
|
|
|
if index < LastSegmentIndex {
|
|
|
|
return SegmentLocation{}, Error.New("invalid index %v", index)
|
|
|
|
}
|
|
|
|
return SegmentLocation{
|
|
|
|
ProjectID: obj.ProjectID,
|
|
|
|
BucketName: obj.BucketName,
|
|
|
|
Index: index,
|
|
|
|
ObjectKey: obj.ObjectKey,
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// SegmentKey is an encoded metainfo key. This is used as the key in pointerdb key-value store.
|
|
|
|
type SegmentKey []byte
|
|
|
|
|
|
|
|
// SegmentLocation is decoded segment key information.
|
|
|
|
type SegmentLocation struct {
|
|
|
|
ProjectID uuid.UUID
|
|
|
|
BucketName string
|
|
|
|
Index int64
|
|
|
|
ObjectKey ObjectKey
|
|
|
|
}
|
|
|
|
|
|
|
|
// Bucket returns bucket location this segment belongs to.
|
|
|
|
func (seg SegmentLocation) Bucket() BucketLocation {
|
|
|
|
return BucketLocation{
|
|
|
|
ProjectID: seg.ProjectID,
|
|
|
|
BucketName: seg.BucketName,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Object returns the object location associated with this segment location.
|
|
|
|
func (seg SegmentLocation) Object() ObjectLocation {
|
|
|
|
return ObjectLocation{
|
|
|
|
ProjectID: seg.ProjectID,
|
|
|
|
BucketName: seg.BucketName,
|
|
|
|
ObjectKey: seg.ObjectKey,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// IsLast returns whether this corresponds to last segment.
|
|
|
|
func (seg SegmentLocation) IsLast() bool { return seg.Index == LastSegmentIndex }
|
|
|
|
|
|
|
|
// IsFirst returns whether this corresponds to first segment.
|
|
|
|
func (seg SegmentLocation) IsFirst() bool { return seg.Index == FirstSegmentIndex }
|
|
|
|
|
|
|
|
// ParseSegmentKey parses an segment key into segment location.
|
|
|
|
func ParseSegmentKey(encoded SegmentKey) (SegmentLocation, error) {
|
|
|
|
elements := strings.SplitN(string(encoded), "/", 4)
|
|
|
|
if len(elements) < 4 {
|
|
|
|
return SegmentLocation{}, Error.New("invalid key %q", encoded)
|
|
|
|
}
|
|
|
|
|
|
|
|
projectID, err := uuid.FromString(elements[0])
|
|
|
|
if err != nil {
|
|
|
|
return SegmentLocation{}, Error.New("invalid key %q", encoded)
|
|
|
|
}
|
|
|
|
|
|
|
|
var index int64
|
|
|
|
if elements[1] == LastSegmentName {
|
|
|
|
index = LastSegmentIndex
|
|
|
|
} else {
|
|
|
|
numstr := strings.TrimPrefix(elements[1], "s")
|
|
|
|
// remove prefix `s` from segment index we got
|
|
|
|
index, err = strconv.ParseInt(numstr, 10, 64)
|
|
|
|
if err != nil {
|
|
|
|
return SegmentLocation{}, Error.New("invalid %q, segment number %q", string(encoded), elements[1])
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return SegmentLocation{
|
|
|
|
ProjectID: projectID,
|
|
|
|
BucketName: elements[2],
|
|
|
|
Index: index,
|
|
|
|
ObjectKey: ObjectKey(elements[3]),
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Encode converts segment location into a segment key.
|
|
|
|
func (seg SegmentLocation) Encode() SegmentKey {
|
|
|
|
segment := LastSegmentName
|
|
|
|
if seg.Index > LastSegmentIndex {
|
|
|
|
segment = "s" + strconv.FormatInt(seg.Index, 10)
|
|
|
|
}
|
|
|
|
return SegmentKey(storj.JoinPaths(
|
|
|
|
seg.ProjectID.String(),
|
|
|
|
segment,
|
|
|
|
seg.BucketName,
|
|
|
|
string(seg.ObjectKey),
|
|
|
|
))
|
|
|
|
}
|
2020-10-27 06:59:14 +00:00
|
|
|
|
|
|
|
// Pieces defines information for pieces.
|
|
|
|
type Pieces []Piece
|
|
|
|
|
|
|
|
// Piece defines information for a segment piece.
|
|
|
|
type Piece struct {
|
|
|
|
Number uint16
|
|
|
|
StorageNode storj.NodeID
|
|
|
|
}
|