157 lines
3.3 KiB
Go
157 lines
3.3 KiB
Go
|
package tree
|
||
|
|
||
|
import (
|
||
|
"context"
|
||
|
"fmt"
|
||
|
"strings"
|
||
|
|
||
|
"git.frostfs.info/TrueCloudLab/frostfs-http-gw/api"
|
||
|
"git.frostfs.info/TrueCloudLab/frostfs-http-gw/api/layer"
|
||
|
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
||
|
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
||
|
)
|
||
|
|
||
|
type (
|
||
|
Tree struct {
|
||
|
service ServiceClient
|
||
|
}
|
||
|
|
||
|
// ServiceClient is a client to interact with tree service.
|
||
|
// Each method must return ErrNodeNotFound or ErrNodeAccessDenied if relevant.
|
||
|
ServiceClient interface {
|
||
|
GetNodes(ctx context.Context, p *GetNodesParams) ([]NodeResponse, error)
|
||
|
}
|
||
|
|
||
|
treeNode struct {
|
||
|
ObjID oid.ID
|
||
|
Meta map[string]string
|
||
|
}
|
||
|
|
||
|
GetNodesParams struct {
|
||
|
CnrID cid.ID
|
||
|
TreeID string
|
||
|
Path []string
|
||
|
Meta []string
|
||
|
LatestOnly bool
|
||
|
AllAttrs bool
|
||
|
}
|
||
|
)
|
||
|
|
||
|
var (
|
||
|
// ErrNodeNotFound is returned from ServiceClient in case of not found error.
|
||
|
ErrNodeNotFound = layer.ErrNodeNotFound
|
||
|
|
||
|
// ErrNodeAccessDenied is returned from ServiceClient service in case of access denied error.
|
||
|
ErrNodeAccessDenied = layer.ErrNodeAccessDenied
|
||
|
)
|
||
|
|
||
|
const (
|
||
|
FileNameKey = "FileName"
|
||
|
)
|
||
|
|
||
|
const (
|
||
|
oidKV = "OID"
|
||
|
|
||
|
// keys for delete marker nodes.
|
||
|
isDeleteMarkerKV = "IsDeleteMarker"
|
||
|
|
||
|
// versionTree -- ID of a tree with object versions.
|
||
|
versionTree = "version"
|
||
|
|
||
|
separator = "/"
|
||
|
)
|
||
|
|
||
|
// NewTree creates instance of Tree using provided address and create grpc connection.
|
||
|
func NewTree(service ServiceClient) *Tree {
|
||
|
return &Tree{service: service}
|
||
|
}
|
||
|
|
||
|
type Meta interface {
|
||
|
GetKey() string
|
||
|
GetValue() []byte
|
||
|
}
|
||
|
|
||
|
type NodeResponse interface {
|
||
|
GetMeta() []Meta
|
||
|
}
|
||
|
|
||
|
func newTreeNode(nodeInfo NodeResponse) (*treeNode, error) {
|
||
|
treeNode := &treeNode{
|
||
|
Meta: make(map[string]string, len(nodeInfo.GetMeta())),
|
||
|
}
|
||
|
|
||
|
for _, kv := range nodeInfo.GetMeta() {
|
||
|
switch kv.GetKey() {
|
||
|
case oidKV:
|
||
|
if err := treeNode.ObjID.DecodeString(string(kv.GetValue())); err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
default:
|
||
|
treeNode.Meta[kv.GetKey()] = string(kv.GetValue())
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return treeNode, nil
|
||
|
}
|
||
|
|
||
|
func (n *treeNode) Get(key string) (string, bool) {
|
||
|
value, ok := n.Meta[key]
|
||
|
return value, ok
|
||
|
}
|
||
|
|
||
|
func (n *treeNode) FileName() (string, bool) {
|
||
|
value, ok := n.Meta[FileNameKey]
|
||
|
return value, ok
|
||
|
}
|
||
|
|
||
|
func newNodeVersion(node NodeResponse) (*api.NodeVersion, error) {
|
||
|
treeNode, err := newTreeNode(node)
|
||
|
if err != nil {
|
||
|
return nil, fmt.Errorf("invalid tree node: %w", err)
|
||
|
}
|
||
|
|
||
|
return newNodeVersionFromTreeNode(treeNode), nil
|
||
|
}
|
||
|
|
||
|
func newNodeVersionFromTreeNode(treeNode *treeNode) *api.NodeVersion {
|
||
|
_, isDeleteMarker := treeNode.Get(isDeleteMarkerKV)
|
||
|
|
||
|
version := &api.NodeVersion{
|
||
|
BaseNodeVersion: api.BaseNodeVersion{
|
||
|
OID: treeNode.ObjID,
|
||
|
},
|
||
|
DeleteMarker: isDeleteMarker,
|
||
|
}
|
||
|
|
||
|
return version
|
||
|
}
|
||
|
|
||
|
func (c *Tree) GetLatestVersion(ctx context.Context, cnrID *cid.ID, objectName string) (*api.NodeVersion, error) {
|
||
|
meta := []string{oidKV, isDeleteMarkerKV}
|
||
|
path := pathFromName(objectName)
|
||
|
|
||
|
p := &GetNodesParams{
|
||
|
CnrID: *cnrID,
|
||
|
TreeID: versionTree,
|
||
|
Path: path,
|
||
|
Meta: meta,
|
||
|
LatestOnly: true,
|
||
|
AllAttrs: false,
|
||
|
}
|
||
|
nodes, err := c.service.GetNodes(ctx, p)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
if len(nodes) == 0 {
|
||
|
return nil, layer.ErrNodeNotFound
|
||
|
}
|
||
|
|
||
|
return newNodeVersion(nodes[0])
|
||
|
}
|
||
|
|
||
|
// pathFromName splits name by '/'.
|
||
|
func pathFromName(objectName string) []string {
|
||
|
return strings.Split(objectName, separator)
|
||
|
}
|