From 663f37cb999f55cc18cc27b71c4df83afdc8fa10 Mon Sep 17 00:00:00 2001
From: "W. Trevor King" <wking@tremily.us>
Date: Tue, 9 Jun 2015 14:06:33 -0700
Subject: [PATCH] core/commands/unixfs/ls: Don't recurse into chunked files

Folks operating at the Unix-filesystem level shouldn't care about that
level of Merkle-DAG detail.  Before this commit we had:

  $ ipfs unixfs ls /ipfs/QmSRCHG21Sbqm3EJG9aEBo4vS7Fqu86pAjqf99MyCdNxZ4/busybox
  /ipfs/QmSRCHG21Sbqm3EJG9aEBo4vS7Fqu86pAjqf99MyCdNxZ4/busybox:
  ... several lines of empty-string names ...

And with this commit we have:

  $ ipfs unixfs ls /ipfs/QmSRCHG21Sbqm3EJG9aEBo4vS7Fqu86pAjqf99MyCdNxZ4/busybox
  /ipfs/QmSRCHG21Sbqm3EJG9aEBo4vS7Fqu86pAjqf99MyCdNxZ4/busybox

I also reworked the argument-prefixing (object.Argument) in the output
marshaller to avoid redundancies like:

  $ ipfs unixfs ls /ipfs/QmSRCHG21Sbqm3EJG9aEBo4vS7Fqu86pAjqf99MyCdNxZ4/busybox
  /ipfs/QmSRCHG21Sbqm3EJG9aEBo4vS7Fqu86pAjqf99MyCdNxZ4/busybox:
  /ipfs/QmSRCHG21Sbqm3EJG9aEBo4vS7Fqu86pAjqf99MyCdNxZ4/busybox

As a side-effect of this rework, we no longer have the trailing blank
line that we used to have after the final directory listing.

The new ErrImplementation is like Python's NotImplementedError, and is
mostly a way to guard against external changes that would need
associated updates in this code.  For example, once we see something
that's neither a file nor a directory, we'll have to update the switch
statement to handle those objects.

License: MIT
Signed-off-by: W. Trevor King <wking@tremily.us>
---
 commands/response.go             |  5 +-
 core/commands/unixfs/ls.go       | 89 ++++++++++++++++++++++----------
 test/sharness/t0200-unixfs-ls.sh | 13 ++++-
 3 files changed, 76 insertions(+), 31 deletions(-)

diff --git a/commands/response.go b/commands/response.go
index a720a8214..657dd0403 100644
--- a/commands/response.go
+++ b/commands/response.go
@@ -15,8 +15,9 @@ type ErrorType uint
 
 // ErrorTypes convey what category of error ocurred
 const (
-	ErrNormal ErrorType = iota // general errors
-	ErrClient                  // error was caused by the client, (e.g. invalid CLI usage)
+	ErrNormal         ErrorType = iota // general errors
+	ErrClient                          // error was caused by the client, (e.g. invalid CLI usage)
+	ErrImplementation                  // programmer error in the server
 	// TODO: add more types of errors for better error-specific handling
 )
 
diff --git a/core/commands/unixfs/ls.go b/core/commands/unixfs/ls.go
index 58ad2f811..9d9cdbdbb 100644
--- a/core/commands/unixfs/ls.go
+++ b/core/commands/unixfs/ls.go
@@ -59,40 +59,66 @@ directories, the child size is the IPFS link size.
 
 		output := make([]*LsObject, len(paths))
 		for i, fpath := range paths {
-			dagnode, err := core.Resolve(req.Context().Context, node, path.Path(fpath))
+			ctx := req.Context().Context
+			merkleNode, err := core.Resolve(ctx, node, path.Path(fpath))
 			if err != nil {
 				res.SetError(err, cmds.ErrNormal)
 				return
 			}
 
-			output[i] = &LsObject{
-				Argument: fpath,
-				Links:    make([]LsLink, len(dagnode.Links)),
+			unixFSNode, err := unixfs.FromBytes(merkleNode.Data)
+			if err != nil {
+				res.SetError(err, cmds.ErrNormal)
+				return
 			}
-			for j, link := range dagnode.Links {
-				ctx, cancel := context.WithTimeout(context.TODO(), time.Minute)
-				defer cancel()
-				link.Node, err = link.GetNode(ctx, node.DAG)
-				if err != nil {
-					res.SetError(err, cmds.ErrNormal)
-					return
-				}
-				d, err := unixfs.FromBytes(link.Node.Data)
+
+			output[i] = &LsObject{}
+
+			t := unixFSNode.GetType()
+			switch t {
+			default:
+				res.SetError(fmt.Errorf("unrecognized type: %s", t), cmds.ErrImplementation)
+				return
+			case unixfspb.Data_File:
+				key, err := merkleNode.Key()
 				if err != nil {
 					res.SetError(err, cmds.ErrNormal)
 					return
 				}
-				lsLink := LsLink{
-					Name: link.Name,
-					Hash: link.Hash.B58String(),
-					Type: d.GetType(),
+				output[i].Links = []LsLink{LsLink{
+					Name: fpath,
+					Hash: key.String(),
+					Type: t,
+					Size: unixFSNode.GetFilesize(),
+				}}
+			case unixfspb.Data_Directory:
+				output[i].Argument = fpath
+				output[i].Links = make([]LsLink, len(merkleNode.Links))
+				for j, link := range merkleNode.Links {
+					getCtx, cancel := context.WithTimeout(context.TODO(), time.Minute)
+					defer cancel()
+					link.Node, err = link.GetNode(getCtx, node.DAG)
+					if err != nil {
+						res.SetError(err, cmds.ErrNormal)
+						return
+					}
+					d, err := unixfs.FromBytes(link.Node.Data)
+					if err != nil {
+						res.SetError(err, cmds.ErrNormal)
+						return
+					}
+					lsLink := LsLink{
+						Name: link.Name,
+						Hash: link.Hash.B58String(),
+						Type: d.GetType(),
+					}
+					if lsLink.Type == unixfspb.Data_File {
+						lsLink.Size = d.GetFilesize()
+					} else {
+						lsLink.Size = link.Size
+					}
+					output[i].Links[j] = lsLink
 				}
-				if lsLink.Type == unixfspb.Data_File {
-					lsLink.Size = d.GetFilesize()
-				} else {
-					lsLink.Size = link.Size
-				}
-				output[i].Links[j] = lsLink
 			}
 		}
 
@@ -104,16 +130,23 @@ directories, the child size is the IPFS link size.
 			output := res.Output().(*LsOutput)
 			buf := new(bytes.Buffer)
 			w := tabwriter.NewWriter(buf, 1, 2, 1, ' ', 0)
-			for _, object := range output.Objects {
-				if len(output.Objects) > 1 {
+			lastObjectDirHeader := false
+			for i, object := range output.Objects {
+				if len(output.Objects) > 1 && object.Argument != "" {
+					if i > 0 {
+						fmt.Fprintln(w)
+					}
 					fmt.Fprintf(w, "%s:\n", object.Argument)
+					lastObjectDirHeader = true
+				} else {
+					if lastObjectDirHeader {
+						fmt.Fprintln(w)
+					}
+					lastObjectDirHeader = false
 				}
 				for _, link := range object.Links {
 					fmt.Fprintf(w, "%s\n", link.Name)
 				}
-				if len(output.Objects) > 1 {
-					fmt.Fprintln(w)
-				}
 			}
 			w.Flush()
 
diff --git a/test/sharness/t0200-unixfs-ls.sh b/test/sharness/t0200-unixfs-ls.sh
index f5dc20d30..4d980e44e 100755
--- a/test/sharness/t0200-unixfs-ls.sh
+++ b/test/sharness/t0200-unixfs-ls.sh
@@ -57,10 +57,21 @@ test_ls_cmd() {
 			QmSix55yz8CzWXf5ZVM9vgEvijnEeeXiTSarVtsqiiCJss:
 			128
 			a
-
 		EOF
 		test_cmp expected_ls actual_ls
 	'
+
+	test_expect_success "'ipfs unixfs ls <file hashes>' succeeds" '
+		ipfs unixfs ls /ipfs/QmR3jhV4XpxxPjPT3Y8vNnWvWNvakdcT3H6vqpRBsX1MLy/1024 QmQNd6ubRXaNG6Prov8o6vk3bn6eWsj9FxLGrAVDUAGkGe >actual_ls_file
+	'
+
+	test_expect_success "'ipfs unixfs ls <file hashes>' output looks good" '
+		cat <<-\EOF >expected_ls_file &&
+			/ipfs/QmR3jhV4XpxxPjPT3Y8vNnWvWNvakdcT3H6vqpRBsX1MLy/1024
+			QmQNd6ubRXaNG6Prov8o6vk3bn6eWsj9FxLGrAVDUAGkGe
+		EOF
+		test_cmp expected_ls_file actual_ls_file
+	'
 }
 
 
-- 
GitLab