package tree import ( "context" "fmt" "strings" "git.frostfs.info/TrueCloudLab/frostfs-http-gw/internal/api" "git.frostfs.info/TrueCloudLab/frostfs-http-gw/internal/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 GetTimestamp() uint64 } 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: false, AllAttrs: false, } nodes, err := c.service.GetNodes(ctx, p) if err != nil { return nil, err } latestNode, err := getLatestNode(nodes) if err != nil { return nil, err } return newNodeVersion(latestNode) } func getLatestNode(nodes []NodeResponse) (NodeResponse, error) { var ( maxCreationTime uint64 targetIndexNode = -1 ) for i, node := range nodes { currentCreationTime := node.GetTimestamp() if checkExistOID(node.GetMeta()) && currentCreationTime > maxCreationTime { maxCreationTime = currentCreationTime targetIndexNode = i } } if targetIndexNode == -1 { return nil, layer.ErrNodeNotFound } return nodes[targetIndexNode], nil } func checkExistOID(meta []Meta) bool { for _, kv := range meta { if kv.GetKey() == "OID" { return true } } return false } // pathFromName splits name by '/'. func pathFromName(objectName string) []string { return strings.Split(objectName, separator) }