From 42384730a076e7e72fd954c6673eb07ee3a93f55 Mon Sep 17 00:00:00 2001
From: Denis Kirillov <denis@nspcc.ru>
Date: Thu, 1 Jul 2021 13:54:51 +0300
Subject: [PATCH] [#121] Fixed directory listing

Signed-off-by: Denis Kirillov <denis@nspcc.ru>
---
 api/handler/list.go    |  1 +
 api/layer/container.go |  1 +
 api/layer/layer.go     | 24 ++++++++++++++++++++++--
 3 files changed, 24 insertions(+), 2 deletions(-)

diff --git a/api/handler/list.go b/api/handler/list.go
index 3212f49b1..ec55228ae 100644
--- a/api/handler/list.go
+++ b/api/handler/list.go
@@ -126,6 +126,7 @@ func (h *handler) listObjects(w http.ResponseWriter, r *http.Request) (*listObje
 		MaxKeys:   arg.MaxKeys,
 		Delimiter: arg.Delimiter,
 		Marker:    marker,
+		Version:   arg.APIVersion,
 	})
 	if err != nil {
 		h.log.Error("something went wrong",
diff --git a/api/layer/container.go b/api/layer/container.go
index 130ca5835..db3a130fb 100644
--- a/api/layer/container.go
+++ b/api/layer/container.go
@@ -32,6 +32,7 @@ type (
 		Delimiter string
 		MaxKeys   int
 		Marker    string
+		Version   int
 	}
 )
 
diff --git a/api/layer/layer.go b/api/layer/layer.go
index 30593e503..675384ac5 100644
--- a/api/layer/layer.go
+++ b/api/layer/layer.go
@@ -8,6 +8,7 @@ import (
 	"io"
 	"net/url"
 	"sort"
+	"strings"
 	"time"
 
 	"github.com/nspcc-dev/neofs-api-go/pkg/client"
@@ -116,6 +117,9 @@ var (
 	ErrObjectNotExists = errors.New("object not exists")
 )
 
+// ETag (hex encoded md5sum) of empty string.
+const emptyETag = "d41d8cd98f00b204e9800998ecf8427e"
+
 // NewLayer creates instance of layer. It checks credentials
 // and establishes gRPC connection with node.
 func NewLayer(log *zap.Logger, conns pool.Pool) Client {
@@ -216,6 +220,8 @@ func (n *layer) ListObjects(ctx context.Context, p *ListObjectsParams) (*ListObj
 		ln = p.MaxKeys
 	}
 
+	mostRecentModified := time.Time{}
+	needDirectoryAsKey := p.Version == 2 && len(p.Prefix) > 0 && len(p.Delimiter) > 0 && strings.HasSuffix(p.Prefix, p.Delimiter)
 	result.Objects = make([]*ObjectInfo, 0, ln)
 
 	for _, id := range ids {
@@ -251,6 +257,9 @@ func (n *layer) ListObjects(ctx context.Context, p *ListObjectsParams) (*ListObj
 		// sub-entities, then it is a file, else directory.
 
 		if oi := objectInfoFromMeta(bkt, meta, p.Prefix, p.Delimiter); oi != nil {
+			if needDirectoryAsKey && oi.Created.After(mostRecentModified) {
+				mostRecentModified = oi.Created
+			}
 			// use only unique dir names
 			if _, ok := uniqNames[oi.Name]; ok {
 				continue
@@ -275,12 +284,23 @@ func (n *layer) ListObjects(ctx context.Context, p *ListObjectsParams) (*ListObj
 		result.NextMarker = result.Objects[len(result.Objects)-1].Name
 	}
 
-	for i, oi := range result.Objects {
+	index := 0
+	for _, oi := range result.Objects {
 		if isDir := uniqNames[oi.Name]; isDir {
-			result.Objects = append(result.Objects[:i], result.Objects[i+1:]...)
+			result.Objects = append(result.Objects[:index], result.Objects[index+1:]...)
 			result.Prefixes = append(result.Prefixes, oi.Name)
+		} else {
+			index++
 		}
 	}
+	if needDirectoryAsKey {
+		res := []*ObjectInfo{{
+			Name:    p.Prefix,
+			Created: mostRecentModified,
+			HashSum: emptyETag,
+		}}
+		result.Objects = append(res, result.Objects...)
+	}
 
 	return &result, nil
 }